diff --git a/app/api/work-review/[[...path]]/route.ts b/app/api/work-review/[[...path]]/route.ts index 6767619..4c7df7c 100644 --- a/app/api/work-review/[[...path]]/route.ts +++ b/app/api/work-review/[[...path]]/route.ts @@ -6,6 +6,7 @@ import { WorkReviewApiError, createWorkReviewProject, createWorkReviewVersion, + deleteWorkReviewProject, getWorkReviewBoard, getWorkReviewReviewerOptions, submitWorkReview, @@ -74,3 +75,16 @@ export async function POST(request: Request, context: RouteContext) { return errorResponse(error) } } + +export async function DELETE(_request: Request, context: RouteContext) { + try { + const session = await getServerSession(authOptions) + const path = getPath(context) + if (path[0] === 'projects' && path[1] && path.length === 2) { + return ok(await deleteWorkReviewProject(session, path[1])) + } + return NextResponse.json({ success: false, error: '接口不存在' }, { status: 404 }) + } catch (error) { + return errorResponse(error) + } +} diff --git a/components/work-review/WorkReviewBoard.tsx b/components/work-review/WorkReviewBoard.tsx index 5f71e16..aae4981 100644 --- a/components/work-review/WorkReviewBoard.tsx +++ b/components/work-review/WorkReviewBoard.tsx @@ -8,6 +8,7 @@ import { useToast } from '@/hooks/useToast' import { createWorkReviewProject, createWorkReviewVersion, + deleteWorkReviewProject, getWorkReviewBoard, submitWorkReview, WorkReviewRequestError, @@ -19,7 +20,7 @@ import type { WorkReviewStatus, WorkReviewSubmissionInput, } from '@/types/work-review' -import { WorkReviewSubmissionDialog } from './WorkReviewDialogs' +import { WorkReviewDeleteConfirmationDialog, WorkReviewSubmissionDialog } from './WorkReviewDialogs' import { WorkReviewHeader } from './WorkReviewHeader' import { WorkReviewProjectCard } from './WorkReviewProjectCard' import { WorkReviewReviewerWorkspace } from './WorkReviewReviewerWorkspace' @@ -84,6 +85,9 @@ export default function WorkReviewBoard() { mode: 'create' | 'resubmit' project?: WorkReviewProject } | null>(null) + const [deletionDialogProject, setDeletionDialogProject] = useState(null) + const [deletionBusy, setDeletionBusy] = useState(false) + const [deletionError, setDeletionError] = useState(null) const { toast, showToast } = useToast() useEffect(() => { @@ -222,6 +226,41 @@ export default function WorkReviewBoard() { } } + const confirmDeleteProject = async () => { + if (!deletionDialogProject || deletionBusy) return + const project = deletionDialogProject + setDeletionBusy(true) + setDeletionError(null) + try { + await deleteWorkReviewProject(project.id) + const shouldGoToPreviousPage = Boolean(data && data.items.length === 1 && page > 1) + setData((current) => current ? { + ...current, + items: current.items.filter((item) => item.id !== project.id), + pagination: { + ...current.pagination, + total: Math.max(0, current.pagination.total - 1), + totalPages: Math.max(1, Math.ceil(Math.max(0, current.pagination.total - 1) / current.pagination.pageSize)), + }, + } : current) + if (expandedProjectId === project.id) { + setExpandedProjectId('') + updateUrl({ project: '', replaceProject: true }) + } + setDeletionDialogProject(null) + showToast(`作品「${project.title}」已删除`) + if (shouldGoToPreviousPage) { + setPage((current) => Math.max(1, current - 1)) + } else { + void load() + } + } catch (deleteError) { + setDeletionError(deleteError instanceof Error ? deleteError.message : '删除失败,请重试') + } finally { + setDeletionBusy(false) + } + } + const pageStats = useMemo(() => { const items = data?.items || [] return { @@ -239,6 +278,7 @@ export default function WorkReviewBoard() { const me = data?.me const canSubmit = Boolean(me?.permissions.canSubmit) + const canDeleteProjects = Boolean(me?.permissions.canDeleteProjects) const requestedProject = expandedProjectId ? data?.items.find((item) => item.id === expandedProjectId) : null @@ -420,11 +460,17 @@ export default function WorkReviewBoard() { key={project.id} project={project} expanded={expandedProjectId === project.id} + canDelete={canDeleteProjects} + deleteBusy={deletionBusy && deletionDialogProject?.id === project.id} onToggle={() => { const nextId = expandedProjectId === project.id ? '' : project.id setExpandedProjectId(nextId) updateUrl({ project: nextId, replaceProject: true }) }} + onDelete={() => { + setDeletionError(null) + setDeletionDialogProject(project) + }} onResubmit={() => setSubmissionDialog({ mode: 'resubmit', project })} onReview={() => { setExpandedProjectId(project.id) @@ -513,6 +559,19 @@ export default function WorkReviewBoard() { : submitVersion(submissionDialog.project!.id, input)} /> )} + {deletionDialogProject && ( + { + if (deletionBusy) return + setDeletionDialogProject(null) + setDeletionError(null) + }} + onConfirm={() => void confirmDeleteProject()} + /> + )} {toast?.show && } ) diff --git a/components/work-review/WorkReviewDialogs.tsx b/components/work-review/WorkReviewDialogs.tsx index 2239048..c92bf42 100644 --- a/components/work-review/WorkReviewDialogs.tsx +++ b/components/work-review/WorkReviewDialogs.tsx @@ -147,6 +147,63 @@ function WorkReviewDialogShell({ ) } +export function WorkReviewDeleteConfirmationDialog({ + project, + busy, + error, + onClose, + onConfirm, +}: { + project: WorkReviewProject + busy: boolean + error: string | null + onClose: () => void + onConfirm: () => void +}) { + return ( + + + + + )} + > +
+
即将删除
+
{project.title}
+

+ 删除后无法恢复,提交人如有需要只能重新发起作品评审。 +

+
+ {error && ( +
+ {error} +
+ )} +
+ ) +} + export function WorkReviewSubmissionDialog({ mode, project, diff --git a/components/work-review/WorkReviewProjectCard.tsx b/components/work-review/WorkReviewProjectCard.tsx index 4d586e1..c962ba2 100644 --- a/components/work-review/WorkReviewProjectCard.tsx +++ b/components/work-review/WorkReviewProjectCard.tsx @@ -109,13 +109,19 @@ function ReviewProgress({ export function WorkReviewProjectCard({ project, expanded, + canDelete, + deleteBusy, onToggle, + onDelete, onResubmit, onReview, }: { project: WorkReviewProject expanded: boolean + canDelete: boolean + deleteBusy?: boolean onToggle: () => void + onDelete: () => void onResubmit: () => void onReview: () => void }) { @@ -151,53 +157,78 @@ export function WorkReviewProjectCard({ expanded ? 'border-[#9ebeff] shadow-[0_14px_38px_rgba(20,52,102,0.10)]' : 'border-[#e1e7ef] hover:shadow-[0_8px_24px_rgba(15,23,42,0.07)]' }`} > - + +
+ {canDelete && ( + )} - - V{project.latestVersion} - +
- -
-
-

{project.title}

- -
-
- 提交人:{project.submitter.name} - 作品日期:{formatDateOnly(project.workDate)} - 最近提交:{formatDateTime(latestVersion.submittedAt)} -
-
- -
-
- - - ⌄ - - + {expanded && ( -
+