From 9f22a20a2ad6ca0cbdb3435ecc8b2855540f3f6a Mon Sep 17 00:00:00 2001 From: zhangxiaohua <827885272@qq.com> Date: Thu, 22 Jan 2026 15:43:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BD=9C=E5=93=81=E9=99=84?= =?UTF-8?q?=E4=BB=B6=E4=B8=8A=E4=BC=A0=E5=92=8C=E6=98=BE=E7=A4=BA=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 后端 DTO 添加 attachments 字段 2. 后端 submit 方法使用事务创建作品和附件记录 3. 前端 SubmitWorkForm 添加 attachments 类型 4. 前端上传时将附件信息单独传递(不再合并到 files 中) Co-Authored-By: Claude Opus 4.5 --- .../src/contests/works/dto/submit-work.dto.ts | 25 +++++++- backend/src/contests/works/works.service.ts | 60 +++++++++++++------ frontend/src/api/contests.ts | 8 +++ .../contests/components/SubmitWorkDrawer.vue | 19 ++++-- 4 files changed, 90 insertions(+), 22 deletions(-) diff --git a/backend/src/contests/works/dto/submit-work.dto.ts b/backend/src/contests/works/dto/submit-work.dto.ts index e0bd917..1541396 100644 --- a/backend/src/contests/works/dto/submit-work.dto.ts +++ b/backend/src/contests/works/dto/submit-work.dto.ts @@ -1,4 +1,21 @@ -import { IsString, IsInt, IsOptional, IsObject, IsArray } from 'class-validator'; +import { IsString, IsInt, IsOptional, IsObject, IsArray, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class AttachmentDto { + @IsString() + fileName: string; + + @IsString() + fileUrl: string; + + @IsString() + @IsOptional() + fileType?: string; + + @IsString() + @IsOptional() + size?: string; +} export class SubmitWorkDto { @IsInt() @@ -28,5 +45,11 @@ export class SubmitWorkDto { @IsObject() @IsOptional() aiModelMeta?: any; + + @IsArray() + @ValidateNested({ each: true }) + @Type(() => AttachmentDto) + @IsOptional() + attachments?: AttachmentDto[]; } diff --git a/backend/src/contests/works/works.service.ts b/backend/src/contests/works/works.service.ts index 2c92a1b..87122fe 100644 --- a/backend/src/contests/works/works.service.ts +++ b/backend/src/contests/works/works.service.ts @@ -120,28 +120,54 @@ export class WorksService { creator: submitterUserId, }; - return this.prisma.contestWork.create({ - data, - include: { - contest: { - select: { - id: true, - contestName: true, + // 使用事务创建作品和附件 + return this.prisma.$transaction(async (tx) => { + const work = await tx.contestWork.create({ + data, + }); + + // 创建附件记录 + if (submitWorkDto.attachments && submitWorkDto.attachments.length > 0) { + for (const attachment of submitWorkDto.attachments) { + await tx.contestWorkAttachment.create({ + data: { + tenantId, + contestId: registration.contestId, + workId: work.id, + fileName: attachment.fileName, + fileUrl: attachment.fileUrl, + fileType: attachment.fileType, + size: attachment.size, + creator: submitterUserId, + }, + }); + } + } + + // 返回完整的作品信息 + return tx.contestWork.findUnique({ + where: { id: work.id }, + include: { + contest: { + select: { + id: true, + contestName: true, + }, }, - }, - registration: { - include: { - user: { - select: { - id: true, - username: true, - nickname: true, + registration: { + include: { + user: { + select: { + id: true, + username: true, + nickname: true, + }, }, }, }, + attachments: true, }, - attachments: true, - }, + }); }); } diff --git a/frontend/src/api/contests.ts b/frontend/src/api/contests.ts index 1f591ef..5244f60 100644 --- a/frontend/src/api/contests.ts +++ b/frontend/src/api/contests.ts @@ -367,6 +367,13 @@ export interface ContestWorkAttachment { modifyTime?: string; } +export interface SubmitWorkAttachment { + fileName: string; + fileUrl: string; + fileType?: string; + size?: string; +} + export interface SubmitWorkForm { registrationId: number; title: string; @@ -375,6 +382,7 @@ export interface SubmitWorkForm { previewUrl?: string; previewUrls?: string[]; aiModelMeta?: any; + attachments?: SubmitWorkAttachment[]; } export interface QueryWorkParams extends PaginationParams { diff --git a/frontend/src/views/contests/components/SubmitWorkDrawer.vue b/frontend/src/views/contests/components/SubmitWorkDrawer.vue index a948857..4d26056 100644 --- a/frontend/src/views/contests/components/SubmitWorkDrawer.vue +++ b/frontend/src/views/contests/components/SubmitWorkDrawer.vue @@ -553,7 +553,6 @@ const handleSubmit = async () => { let modelFiles: string[] = [] let previewUrl = "" let previewUrlsList: string[] = [] - const attachmentUrls: string[] = [] if (uploadMode.value === "history") { // 从创作历史选择模式 @@ -611,11 +610,22 @@ const handleSubmit = async () => { } } - // 上传附件 + // 上传附件并收集附件信息 + const attachments: Array<{ + fileName: string + fileUrl: string + fileType?: string + size?: string + }> = [] for (const file of form.attachmentFiles) { try { const url = await uploadFile(file) - attachmentUrls.push(url) + attachments.push({ + fileName: file.name, + fileUrl: url, + fileType: file.type || undefined, + size: String(file.size), + }) } catch (error: any) { console.error("附件上传失败:", error) } @@ -625,9 +635,10 @@ const handleSubmit = async () => { registrationId: registrationIdRef.value, title: form.title, description: form.description, - files: [...modelFiles, ...attachmentUrls], + files: modelFiles, // 只包含模型文件,不包含附件 previewUrl: previewUrl, previewUrls: previewUrlsList.length > 0 ? previewUrlsList : undefined, + attachments: attachments.length > 0 ? attachments : undefined, } await worksApi.submit(submitData)