diff --git a/app/api/performance-survey/[...path]/route.ts b/app/api/performance-survey/[...path]/route.ts index b3b8cb23..7b5f778b 100644 --- a/app/api/performance-survey/[...path]/route.ts +++ b/app/api/performance-survey/[...path]/route.ts @@ -25,6 +25,7 @@ import { retryPerformanceSurveyNotificationById, runPerformanceSurveyNotificationMaintenance, searchPerformanceSurveyEmployees, + submitPerformanceSurveyWorkItemScores, submitPerformanceSurveyWorkItemScore, submitPerformanceSurveyResponse, syncPerformanceSurveyEmployeeProfiles, @@ -189,6 +190,10 @@ export async function POST(request: Request, context: RouteContext) { return ok(await submitPerformanceSurveyResponse(session, path[1], body)) } + if (path[0] === 'surveys' && path[1] && path[2] === 'work-item-scores' && path.length === 3) { + return ok(await submitPerformanceSurveyWorkItemScores(session, path[1], body)) + } + if (path[0] === 'surveys' && path[1] && path[2] === 'work-items' && path[3] && path[4] === 'scores') { return ok(await submitPerformanceSurveyWorkItemScore(session, path[1], path[3], body)) } diff --git a/app/performance-survey/[[...path]]/page.tsx b/app/performance-survey/[[...path]]/page.tsx index 0d44c9b9..2d5d9aac 100644 --- a/app/performance-survey/[[...path]]/page.tsx +++ b/app/performance-survey/[[...path]]/page.tsx @@ -4385,14 +4385,33 @@ function VisualCaseSurvey({ const requiredCount = Number(template?.requiredWorkItemCount || 3) const workItems = activeTarget?.workItems || [] const canUpload = Boolean(activeTarget?.canUploadWorkItems && ['draft', 'open'].includes(detail.data?.status)) + const canCreateWorkItem = activeTarget?.canCreateWorkItem === true + || (activeTarget?.canCreateWorkItem == null && canUpload && workItems.length < requiredCount) const canScore = Boolean(activeTarget?.currentAssignment && detail.data?.status === 'open') const submitted = activeTarget?.currentAssignment?.status === 'submitted' const completedScores = workItems.filter((item: any) => item.scores?.length > 0).length const [publishing, setPublishing] = useState(false) const [publishError, setPublishError] = useState(null) + const [scoreDrafts, setScoreDrafts] = useState; comment: string }>>({}) + const [submittingAllScores, setSubmittingAllScores] = useState(false) + const [scoreSubmitMessage, setScoreSubmitMessage] = useState(null) const draftReadyToPublish = detail.data?.status === 'draft' && Boolean(detail.data?.canManage) && workItems.length >= requiredCount + const questions = template?.questions || [] + const unscoredWorkItems = workItems.filter((item: any) => !item.scores?.[0]) + const allUnscoredWorkItemsRated = unscoredWorkItems.length > 0 + && unscoredWorkItems.every((item: any) => questions.every((question) => ( + Number.isFinite(scoreDrafts[item.id]?.scores?.[question.id]) + ))) + const canSubmitAllScores = canScore + && workItems.length === requiredCount + && unscoredWorkItems.length > 0 + + const updateScoreDraft = (workItemId: string, next: { scores: Record; comment: string }) => { + setScoreDrafts((current) => ({ ...current, [workItemId]: next })) + setScoreSubmitMessage(null) + } const publishDraft = async () => { if (!draftReadyToPublish || publishing) return @@ -4411,6 +4430,36 @@ function VisualCaseSurvey({ } } + const submitAllScores = async () => { + if (!canSubmitAllScores || submittingAllScores) return + if (!allUnscoredWorkItemsRated) { + setScoreSubmitMessage('请完成全部案例的每一项评分后再统一提交') + return + } + + setSubmittingAllScores(true) + setScoreSubmitMessage(null) + try { + await apiRequest(`/surveys/${surveyId}/work-item-scores`, { + method: 'POST', + body: JSON.stringify({ + items: unscoredWorkItems.map((item: any) => ({ + workItemId: item.id, + scores: scoreDrafts[item.id].scores, + comment: scoreDrafts[item.id].comment, + })), + finalComment: '', + }), + }) + await detail.refresh() + setScoreSubmitMessage('全部案例评分已提交') + } catch (error) { + setScoreSubmitMessage(`提交失败:${error instanceof Error ? error.message : '请重试'}`) + } finally { + setSubmittingAllScores(false) + } + } + return (
@@ -4458,12 +4507,11 @@ function VisualCaseSurvey({ )} - {canUpload && ( + {canCreateWorkItem && ( = requiredCount} onUploaded={detail.refresh} /> )} @@ -4501,17 +4549,51 @@ function VisualCaseSurvey({
{workItems.map((item: any, index: number) => ( - - ))} -
+ index={index} + template={template} + canScore={canScore && !submitted} + scoreDraft={scoreDrafts[item.id] || { scores: {}, comment: '' }} + onScoreDraftChange={(next) => updateScoreDraft(item.id, next)} + /> + ))} + + + {canSubmitAllScores && ( +
+
+ 统一提交全部案例评分 + + {allUnscoredWorkItemsRated ? '评分已完整' : '请完成全部评分'} + +
+

+ 将一次性提交 {unscoredWorkItems.length} 个尚未评分的案例。 +

+ {scoreSubmitMessage && ( +
+ {scoreSubmitMessage} +
+ )} + +
+ )}
@@ -4549,12 +4631,10 @@ function VisualCaseSurvey({ function WorkItemUploadForm({ surveyId, targetId, - disabled, onUploaded, }: { surveyId: string targetId: string - disabled: boolean onUploaded: () => Promise }) { const [title, setTitle] = useState('') @@ -4581,7 +4661,6 @@ function WorkItemUploadForm({ } const submit = async () => { - if (disabled) return const validationError = validatePerformanceSurveyImageFiles(files) if (validationError) { setMessageTone('error') @@ -4628,20 +4707,18 @@ function WorkItemUploadForm({ value={title} onChange={(event) => setTitle(event.target.value)} placeholder="案例名称" - disabled={disabled} className="rounded-[10px] border border-[#d9e0ea] px-3 py-2 text-sm outline-none focus:border-[#1677ff] focus:ring-4 focus:ring-[#1677ff]/10 disabled:bg-[#f6f8fb]" />