修复作品附件上传和显示功能

1. 后端 DTO 添加 attachments 字段
2. 后端 submit 方法使用事务创建作品和附件记录
3. 前端 SubmitWorkForm 添加 attachments 类型
4. 前端上传时将附件信息单独传递(不再合并到 files 中)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
zhangxiaohua 2026-01-22 15:43:10 +08:00
parent 1010c764cc
commit 9f22a20a2a
4 changed files with 90 additions and 22 deletions

View File

@ -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 { export class SubmitWorkDto {
@IsInt() @IsInt()
@ -28,5 +45,11 @@ export class SubmitWorkDto {
@IsObject() @IsObject()
@IsOptional() @IsOptional()
aiModelMeta?: any; aiModelMeta?: any;
@IsArray()
@ValidateNested({ each: true })
@Type(() => AttachmentDto)
@IsOptional()
attachments?: AttachmentDto[];
} }

View File

@ -120,8 +120,33 @@ export class WorksService {
creator: submitterUserId, creator: submitterUserId,
}; };
return this.prisma.contestWork.create({ // 使用事务创建作品和附件
return this.prisma.$transaction(async (tx) => {
const work = await tx.contestWork.create({
data, 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: { include: {
contest: { contest: {
select: { select: {
@ -143,6 +168,7 @@ export class WorksService {
attachments: true, attachments: true,
}, },
}); });
});
} }
/** /**

View File

@ -367,6 +367,13 @@ export interface ContestWorkAttachment {
modifyTime?: string; modifyTime?: string;
} }
export interface SubmitWorkAttachment {
fileName: string;
fileUrl: string;
fileType?: string;
size?: string;
}
export interface SubmitWorkForm { export interface SubmitWorkForm {
registrationId: number; registrationId: number;
title: string; title: string;
@ -375,6 +382,7 @@ export interface SubmitWorkForm {
previewUrl?: string; previewUrl?: string;
previewUrls?: string[]; previewUrls?: string[];
aiModelMeta?: any; aiModelMeta?: any;
attachments?: SubmitWorkAttachment[];
} }
export interface QueryWorkParams extends PaginationParams { export interface QueryWorkParams extends PaginationParams {

View File

@ -553,7 +553,6 @@ const handleSubmit = async () => {
let modelFiles: string[] = [] let modelFiles: string[] = []
let previewUrl = "" let previewUrl = ""
let previewUrlsList: string[] = [] let previewUrlsList: string[] = []
const attachmentUrls: string[] = []
if (uploadMode.value === "history") { 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) { for (const file of form.attachmentFiles) {
try { try {
const url = await uploadFile(file) 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) { } catch (error: any) {
console.error("附件上传失败:", error) console.error("附件上传失败:", error)
} }
@ -625,9 +635,10 @@ const handleSubmit = async () => {
registrationId: registrationIdRef.value, registrationId: registrationIdRef.value,
title: form.title, title: form.title,
description: form.description, description: form.description,
files: [...modelFiles, ...attachmentUrls], files: modelFiles, //
previewUrl: previewUrl, previewUrl: previewUrl,
previewUrls: previewUrlsList.length > 0 ? previewUrlsList : undefined, previewUrls: previewUrlsList.length > 0 ? previewUrlsList : undefined,
attachments: attachments.length > 0 ? attachments : undefined,
} }
await worksApi.submit(submitData) await worksApi.submit(submitData)