diff --git a/src/views/product/requirements-doc.vue b/src/views/product/requirements-doc.vue index 319d4cc..c7a5e95 100644 --- a/src/views/product/requirements-doc.vue +++ b/src/views/product/requirements-doc.vue @@ -171,8 +171,9 @@
- + +
@@ -293,6 +294,7 @@ const draggingPurposeId = ref(""); const dragOverModuleId = ref(""); const checking = ref(false); const saving = ref(false); +const submitting = ref(false); const stepOnePassed = ref(false); const checkResult = ref(null); @@ -305,6 +307,7 @@ const progressItems = computed(() => trainingSteps.value.map((text, index) => ({ const unassignedPurposes = computed(() => PURPOSES.filter((purpose) => !Object.values(assignments.value).includes(purpose.id))); const allAssigned = computed(() => MODULES.every((module) => Boolean(assignments.value[module.id])) && Object.keys(assignments.value).length === MODULES.length); const isDocumentReady = computed(() => Boolean(stepThreeDocument.value.name && stepThreeDocument.value.url)); +const canSubmitTask = computed(() => stepOnePassed.value && isDocumentReady.value); const sidebarStudyTip = computed(() => currentStep.value === 3 ? "先依据第 2 步的模块信息整理成完整 PRD,再上传 .docx 或 .pdf。上传完成后请打开在线预览,确认文档内容和版本无误。" : currentStep.value === 2 @@ -382,6 +385,7 @@ function buildStepThreeAnswer() { return { document: { ...stepThreeDocument.valu function buildSavePayload(current = currentStep.value) { return { currentStep: current, step1Answer: JSON.stringify(buildStepOneAnswer()), step2Answer: JSON.stringify(buildStepTwoAnswer()), step3Answer: JSON.stringify(buildStepThreeAnswer()), submitted: false, saveAction: "SAVE" }; } +function buildSubmitPayload() { return { ...buildSavePayload(3), submitted: true, saveAction: "SUBMIT" }; } async function persist(current = currentStep.value) { await saveStudentTrainingAnswer(TASK_KEY, buildSavePayload(current)); } async function saveCurrentMatching() { if (saving.value) return; @@ -454,6 +458,13 @@ async function saveStepThree() { saving.value = true; try { await persist(3); proxy?.$modal?.msgSuccess("产品需求文档已保存"); } catch (error) { proxy?.$modal?.msgError(error?.message || "保存失败,请稍后重试"); } finally { saving.value = false; } } +async function submitTask() { + if (!canSubmitTask.value || saving.value || submitting.value || documentUploading.value) return; + submitting.value = true; + try { await saveStudentTrainingAnswer(TASK_KEY, buildSubmitPayload()); proxy?.$modal?.msgSuccess("产品需求文档已提交"); } + catch (error) { proxy?.$modal?.msgError(error?.message || "提交失败,请稍后重试"); } + finally { submitting.value = false; } +} async function exportStepTwoTable() { try { const XLSX = await import("xlsx"); diff --git a/tests/requirements-doc-submit.static.test.cjs b/tests/requirements-doc-submit.static.test.cjs new file mode 100644 index 0000000..453feda --- /dev/null +++ b/tests/requirements-doc-submit.static.test.cjs @@ -0,0 +1,20 @@ +const fs = require("node:fs"); +const path = require("node:path"); +const test = require("node:test"); +const assert = require("node:assert/strict"); + +const page = fs.readFileSync(path.join(__dirname, "../src/views/product/requirements-doc.vue"), "utf8"); + +test("产品需求说明书在已完成匹配且文档就绪时可以提交任务", () => { + assert.match(page, /const canSubmitTask = computed\(\(\) => stepOnePassed\.value && isDocumentReady\.value\);/); + assert.match(page, /@click="submitTask"/); + assert.match(page, /提交任务/); + assert.match(page, /:disabled="!canSubmitTask \|\| saving \|\| submitting \|\| documentUploading"/); +}); + +test("产品需求说明书最终提交保存完整答案并标记为已提交", () => { + assert.match(page, /function buildSubmitPayload\(\) \{ return \{ \.\.\.buildSavePayload\(3\), submitted: true, saveAction: "SUBMIT" \}; \}/); + assert.match(page, /async function submitTask\(\)[\s\S]*?await saveStudentTrainingAnswer\(TASK_KEY, buildSubmitPayload\(\)\)/); + assert.match(page, /产品需求文档已提交/); + assert.match(page, /提交失败,请稍后重试/); +});