feat(review): 支持多候选稿验收轮次

- 支持每轮提交 1–5 个候选稿并按指定稿验收
- 保留历史轮次只读并兼容单候选稿版本接口
- 同步 SQLite/PostgreSQL schema、迁移验证、测试与项目文档
This commit is contained in:
yuzhe
2026-07-21 20:25:52 +08:00
parent 721e971dd8
commit 6091d61612
26 changed files with 586 additions and 97 deletions

View File

@@ -68,6 +68,12 @@ export const api = {
payload.images.forEach((file) => form.append('images', file));
return request<Note>(`/api/notes/${noteId}/versions`, { method: 'POST', body: form });
},
createReviewRound: (noteId: number, candidates: Array<{ candidate_name: string; title: string; description: string; tags: string[]; images: File[] }>) => {
const form = new FormData();
form.append('candidates', JSON.stringify(candidates.map((candidate) => ({ ...candidate, images: undefined, image_count: candidate.images.length }))));
candidates.forEach((candidate) => candidate.images.forEach((file) => form.append('images', file)));
return request<Note>(`/api/notes/${noteId}/review-rounds`, { method: 'POST', body: form });
},
setReviewStatus: (id: number, status: ReviewStatus) => request<{ success: true; status: ReviewStatus }>(`/api/notes/${id}/status`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status }) }),
reopenWork: (id: number, reason: string) => request<{ success: true; status: ReviewStatus }>(`/api/notes/${id}/reopen`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reason }) }),
addAnnotation: (imageId: number, data: { x: number; y: number; content: string; author_name?: string }) => request<Annotation>(`/api/images/${imageId}/annotations`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }),
@@ -82,5 +88,5 @@ export const api = {
addCustomerComment: (slug: string, noteId: number, content: string) => request<WorkComment>(`/api/review/${slug}/works/${noteId}/comments`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ content }) }),
addCustomerAnnotation: (slug: string, imageId: number, data: { x: number; y: number; content: string }) => request<Annotation>(`/api/review/${slug}/images/${imageId}/annotations`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }),
addCustomerTextAnnotation: (slug: string, noteId: number, data: { version_number: number; target: 'title' | 'description'; content: string }) => request<TextAnnotation>(`/api/review/${slug}/works/${noteId}/text-annotations`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }),
submitCustomerDecision: (slug: string, noteId: number, decision: 'approved' | 'changes_requested', reason?: string) => request<{ success: true; status: ReviewStatus }>(`/api/review/${slug}/works/${noteId}/decision`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ decision, reason }) }),
submitCustomerDecision: (slug: string, noteId: number, versionNumber: number, decision: 'approved' | 'changes_requested', reason?: string) => request<{ success: true; status: ReviewStatus; version_number: number }>(`/api/review/${slug}/works/${noteId}/decision`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ version_number: versionNumber, decision, reason }) }),
};