From 3b91cbd07d2234b4a2e1e65c096f97977fd0bac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=B2=85?= <907037276@qq.com> Date: Mon, 31 Aug 2026 12:28:39 +0800 Subject: [PATCH] fix: complete multi-step training submissions --- src/views/assessment/optimization.vue | 8 ++++-- src/views/product/price.vue | 9 +++--- tests/multistep-task-submit.static.test.cjs | 31 +++++++++++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 tests/multistep-task-submit.static.test.cjs diff --git a/src/views/assessment/optimization.vue b/src/views/assessment/optimization.vue index 04c0b5c..22f4f42 100644 --- a/src/views/assessment/optimization.vue +++ b/src/views/assessment/optimization.vue @@ -211,6 +211,7 @@ const uploadingPareto = ref(false); const paretoImage = ref({ url: "", name: "" }); const stepFour = ref(createStepFour()); const strategyRows = ref(createStrategyRows()); +const dynamicStepAnswers = ref({}); const modules = [ { name: "帕累托 / ABC 分析", component: defineAsyncComponent(() => import("../portraitModel/components/abcAnalysis.vue")) }, { name: "购物篮分析", component: defineAsyncComponent(() => import("../portraitModel/components/basketAnalysis.vue")) }, @@ -241,6 +242,7 @@ function buildStepTwoProgress() { return { version: 1, rows: categoryRows.value. function buildStepThreeProgress() { return { version: 1, imageUrl: paretoImage.value.url, imageName: paretoImage.value.name, updatedAt: new Date().toISOString() }; } function buildStepFourProgress() { return { version: 1, ...stepFour.value, updatedAt: new Date().toISOString() }; } function buildStepFiveProgress() { return { version: 1, rows: strategyRows.value.map(({ layer, operationStrategy, inventoryStrategy, resourcePriority }) => ({ layer, operationStrategy, inventoryStrategy, resourcePriority })), updatedAt: new Date().toISOString() }; } +function parseSavedJson(value) { try { const parsed = typeof value === "string" ? JSON.parse(value) : value; return parsed && typeof parsed === "object" ? parsed : null; } catch (error) { return null; } } function applyProgress(value) { if (!value || typeof value !== "object") return; activeStep.value = normalizeActiveStep(value.activeStep); @@ -251,7 +253,7 @@ function applyStepTwoProgress(value) { const classifications = new Map((Array.is function applyStepThreeProgress(value) { paretoImage.value = { url: String(value?.imageUrl || ""), name: String(value?.imageName || "") }; } function applyStepFourProgress(value) { stepFour.value = createStepFour(value); } function applyStepFiveProgress(value) { strategyRows.value = createStrategyRows(value?.rows); } -async function persistProgress(saveAction = "SAVE") { await saveStudentTrainingAnswer(TASK_KEY, { saveAction, currentStep: activeStep.value, step1Answer: JSON.stringify(buildProgress()), step2Answer: JSON.stringify(buildStepTwoProgress()), step3Answer: JSON.stringify(buildStepThreeProgress()), step4Answer: JSON.stringify(buildStepFourProgress()), step5Answer: JSON.stringify(buildStepFiveProgress()) }); } +async function persistProgress(saveAction = "SAVE") { dynamicStepAnswers.value = { ...dynamicStepAnswers.value, step5: buildStepFiveProgress() }; await saveStudentTrainingAnswer(TASK_KEY, { saveAction, currentStep: activeStep.value, submitted: saveAction === "SUBMIT", step1Answer: JSON.stringify(buildProgress()), step2Answer: JSON.stringify(buildStepTwoProgress()), step3Answer: JSON.stringify(buildStepThreeProgress()), step4Answer: JSON.stringify(buildStepFourProgress()), dynamicStepAnswers: JSON.stringify(dynamicStepAnswers.value) }); } function addRow() { if (categoryRows.value.length >= MAX_ROWS) return; categoryRows.value.push(createRow()); } function removeLastRow() { categoryRows.value.pop(); } async function validateStepOne() { @@ -349,7 +351,7 @@ async function saveCurrentProgress() { async function submitTask() { if (saving.value || !(await validateStepOne()) || (hasConfiguredSteps.value && configuredSteps.value.length > 1 && !(await validateStepTwo())) || (hasConfiguredSteps.value && configuredSteps.value.length > 2 && !(await validateStepThree())) || (hasConfiguredSteps.value && configuredSteps.value.length > 3 && !(await validateStepFour())) || (hasConfiguredSteps.value && configuredSteps.value.length > 4 && !(await validateStepFive()))) return; saving.value = true; - try { await businessComponent.value?.submitData?.(); await persistProgress("SAVE"); ElMessage.success("任务已提交"); } finally { saving.value = false; } + try { await businessComponent.value?.submitData?.(); await persistProgress("SUBMIT"); ElMessage.success("任务已提交"); } finally { saving.value = false; } } async function resetTask() { if (saving.value) return; @@ -374,7 +376,7 @@ function exportOutcome() { } onMounted(async () => { try { const res = await getTrainingTaskByKey(TASK_KEY); taskConfig.value = res?.data || null; activeStep.value = normalizeActiveStep(activeStep.value); } catch (error) { taskConfig.value = null; } - try { const res = await getStudentTrainingAnswer(TASK_KEY); if (res?.data?.step1Answer) applyProgress(JSON.parse(res.data.step1Answer)); if (res?.data?.step2Answer) applyStepTwoProgress(JSON.parse(res.data.step2Answer)); if (res?.data?.step3Answer) applyStepThreeProgress(JSON.parse(res.data.step3Answer)); if (res?.data?.step4Answer) applyStepFourProgress(JSON.parse(res.data.step4Answer)); if (res?.data?.step5Answer) applyStepFiveProgress(JSON.parse(res.data.step5Answer)); } catch (error) {} + try { const res = await getStudentTrainingAnswer(TASK_KEY); if (res?.data?.step1Answer) applyProgress(JSON.parse(res.data.step1Answer)); if (res?.data?.step2Answer) applyStepTwoProgress(JSON.parse(res.data.step2Answer)); if (res?.data?.step3Answer) applyStepThreeProgress(JSON.parse(res.data.step3Answer)); if (res?.data?.step4Answer) applyStepFourProgress(JSON.parse(res.data.step4Answer)); dynamicStepAnswers.value = parseSavedJson(res?.data?.dynamicStepAnswers) || {}; if (dynamicStepAnswers.value.step5) applyStepFiveProgress(dynamicStepAnswers.value.step5); } catch (error) {} }); diff --git a/src/views/product/price.vue b/src/views/product/price.vue index d6ddc7c..b48da80 100644 --- a/src/views/product/price.vue +++ b/src/views/product/price.vue @@ -523,9 +523,9 @@ const labels = { measuredResult: "测算结果", finalAnalysisTitle: "最终量产定价分析", finalAnalysisPlaceholder: "请综合成本、竞品、用户感知价值和 PSM 价格敏感度,说明最终量产定价、定价依据与利润空间。", - finalPricingSave: "保存最终量产定价", - finalPricingSaving: "保存中...", - finalPricingSaveSuccess: "最终量产定价已保存", + finalPricingSave: "提交并完成实训", + finalPricingSaving: "提交中...", + finalPricingSaveSuccess: "实训任务提交成功", finalPricingChannelRequired: "请选择用于汇总的成本加成定价渠道。", finalPricingPrerequisiteRequired: "请先完成前四步定价测算,确保四种定价方法均有测算结果和毛利润率。", finalPricingAnalysisRequired: "请填写最终量产定价分析。", @@ -1077,8 +1077,9 @@ async function saveStepFive() { finalPricingAnalysis.value = validated.finalAnalysis ?? finalPricingAnalysis.value; dynamicStepAnswers.value = { ...dynamicStepAnswers.value, step5: buildStepFiveProgress() }; await saveStudentTrainingAnswer(TASK_KEY, { - saveAction: "SAVE", + saveAction: "SUBMIT", currentStep: 5, + submitted: true, dynamicStepAnswers: JSON.stringify(dynamicStepAnswers.value), }); hasSavedAnswer.value = true; diff --git a/tests/multistep-task-submit.static.test.cjs b/tests/multistep-task-submit.static.test.cjs new file mode 100644 index 0000000..fa52da1 --- /dev/null +++ b/tests/multistep-task-submit.static.test.cjs @@ -0,0 +1,31 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); +const test = require("node:test"); + +function readPage(relativePath) { + return fs.readFileSync(path.resolve(__dirname, "..", relativePath), "utf8"); +} + +test("实训19第五步提交动态答案并完成任务", () => { + const page = readPage("src/views/product/price.vue"); + + assert.match(page, /finalPricingSave:\s*"提交并完成实训"/); + assert.match( + page, + /async function saveStepFive\(\)\s*\{[\s\S]*?saveAction:\s*"SUBMIT",[\s\S]*?currentStep:\s*5,[\s\S]*?submitted:\s*true,[\s\S]*?dynamicStepAnswers:/ + ); +}); + +test("实训20第五步使用动态答案保存并在提交时完成任务", () => { + const page = readPage("src/views/assessment/optimization.vue"); + + assert.match(page, /const dynamicStepAnswers = ref\(\{\}\);/); + assert.match( + page, + /async function persistProgress\(saveAction = "SAVE"\)\s*\{[\s\S]*?step5:\s*buildStepFiveProgress\(\)[\s\S]*?dynamicStepAnswers:\s*JSON\.stringify\(dynamicStepAnswers\.value\)/ + ); + assert.match(page, /async function submitTask\(\)\s*\{[\s\S]*?persistProgress\("SUBMIT"\)/); + assert.match(page, /dynamicStepAnswers\.value = parseSavedJson\(res\?\.data\?\.dynamicStepAnswers\) \|\| \{\};/); + assert.doesNotMatch(page, /step5Answer:/); +});