merge: fix product diagnosis step two persistence

bugfix/ai-assessment-evidence-rescore
chenyuan 1 month ago
commit 8ed33b19f6

@ -135,7 +135,26 @@ function buildStepOne() {
anomalyRows: anomalyRows.value.map((row) => ({ ...row, channel: channelSnapshot.find((channel) => channel.id === row.channelId)?.name || row.channel || "" })), anomalyRows: anomalyRows.value.map((row) => ({ ...row, channel: channelSnapshot.find((channel) => channel.id === row.channelId)?.name || row.channel || "" })),
}; };
} }
function buildStepTwo() { return { sourceFileName: commentPreview.value.fileName, topIssues: [...topIssues.value], sentiments: sentiments.value.map((row) => ({ ...row })), rootCauseAnalysis: rootCauseAnalysis.value, wordCloud: wordCloud.value, previewComments: commentPreview.value.comments }; } function buildStepTwo() {
return {
sourceFileName: commentPreview.value.fileName,
totalComments: commentPreview.value.totalComments,
topIssues: [...topIssues.value],
sentiments: sentiments.value.map((row) => ({ ...row })),
rootCauseAnalysis: rootCauseAnalysis.value,
wordCloud: wordCloud.value,
previewComments: commentPreview.value.comments,
analysisResult: analysisResult.value ? { ...analysisResult.value } : null,
};
}
function buildStepTwoValidationPayload(stepTwo = buildStepTwo()) {
return {
sourceFileName: stepTwo.sourceFileName,
topIssues: stepTwo.topIssues,
sentiments: stepTwo.sentiments,
rootCauseAnalysis: stepTwo.rootCauseAnalysis,
};
}
function buildStepThree() { return { testPlan: abTestPlan.value, rows: abRows.value.map((row) => ({ ...row })), comparisonChartUrl: abChart.value.url, comparisonChartName: abChart.value.name, iterationRecommendation: iterationRecommendation.value }; } function buildStepThree() { return { testPlan: abTestPlan.value, rows: abRows.value.map((row) => ({ ...row })), comparisonChartUrl: abChart.value.url, comparisonChartName: abChart.value.name, iterationRecommendation: iterationRecommendation.value }; }
function addFunnelChannel() { function addFunnelChannel() {
channelSerial += 1; channelSerial += 1;
@ -173,7 +192,7 @@ function runSentimentAnalysis() {
})); }));
proxy?.$modal?.msgSuccess?.("情感评论数量与占比已自动计算"); proxy?.$modal?.msgSuccess?.("情感评论数量与占比已自动计算");
} }
async function saveStepTwoDraft() { if (saving.value) return false; saving.value = true; try { await saveStudentTrainingAnswer(TASK_KEY, { step2Answer: JSON.stringify(buildStepTwo()), currentStep: 2, submitted: false, saveAction: "SAVE" }); proxy?.$modal?.msgSuccess?.("当前步骤已保存"); return true; } catch (error) { proxy?.$modal?.msgError?.(error?.message || "保存失败,请重试"); return false; } finally { saving.value = false; } } async function validateStepTwo() { if (saving.value) return false; saving.value = true; try { const data = buildStepTwo(); await validateProductDiagnosisIterationStepTwo(data); await saveStudentTrainingAnswer(TASK_KEY, { step2Answer: JSON.stringify(data), currentStep: 2, submitted: false, saveAction: "SAVE" }); proxy?.$modal?.msgSuccess?.("第 2 步校验通过,已保存"); return true; } catch (error) { proxy?.$modal?.msgError?.(diagnosisErrorMessage(error, "请检查词云、情感占比和根因分析")); return false; } finally { saving.value = false; } } async function saveStepTwoDraft() { if (saving.value) return false; saving.value = true; try { await saveStudentTrainingAnswer(TASK_KEY, { step2Answer: JSON.stringify(buildStepTwo()), currentStep: 2, submitted: false, saveAction: "SAVE" }); proxy?.$modal?.msgSuccess?.("当前步骤已保存"); return true; } catch (error) { proxy?.$modal?.msgError?.(error?.message || "保存失败,请重试"); return false; } finally { saving.value = false; } } async function validateStepTwo() { if (saving.value) return false; saving.value = true; try { const data = buildStepTwo(); await validateProductDiagnosisIterationStepTwo(buildStepTwoValidationPayload(data)); await saveStudentTrainingAnswer(TASK_KEY, { step2Answer: JSON.stringify(data), currentStep: 2, submitted: false, saveAction: "SAVE" }); proxy?.$modal?.msgSuccess?.("第 2 步校验通过,已保存"); return true; } catch (error) { proxy?.$modal?.msgError?.(diagnosisErrorMessage(error, "请检查词云、情感占比和根因分析")); return false; } finally { saving.value = false; } }
function clearAbChart() { if (abChart.value.url.startsWith("blob:")) URL.revokeObjectURL(abChart.value.url); abChart.value = { url: "", name: "" }; } function clearAbChart() { if (abChart.value.url.startsWith("blob:")) URL.revokeObjectURL(abChart.value.url); abChart.value = { url: "", name: "" }; }
async function handleAbChartUpload(event) { const file = event.target?.files?.[0]; if (!file) return; const accepted = ["image/jpeg", "image/png", "image/webp"]; if (!accepted.includes(file.type) || file.size > 5 * 1024 * 1024) { proxy?.$modal?.msgError?.("请上传不超过 5MB 的 PNG、JPG 或 WEBP 图片"); event.target.value = ""; return; } uploading.value = true; try { let url = ""; if (isStudentDemo()) url = URL.createObjectURL(file); else { const data = new FormData(); data.append("file", file); const response = await uploadStudentTrainingFile(data); url = response?.url || response?.data?.url || ""; } if (!url) throw new Error("上传成功但未返回图片地址"); clearAbChart(); abChart.value = { url, name: file.name }; proxy?.$modal?.msgSuccess?.("AB 测试结果对比图上传成功,可点击图片预览"); } catch (error) { proxy?.$modal?.msgError?.(error?.message || "图片上传失败,请重试"); } finally { uploading.value = false; event.target.value = ""; } } async function handleAbChartUpload(event) { const file = event.target?.files?.[0]; if (!file) return; const accepted = ["image/jpeg", "image/png", "image/webp"]; if (!accepted.includes(file.type) || file.size > 5 * 1024 * 1024) { proxy?.$modal?.msgError?.("请上传不超过 5MB 的 PNG、JPG 或 WEBP 图片"); event.target.value = ""; return; } uploading.value = true; try { let url = ""; if (isStudentDemo()) url = URL.createObjectURL(file); else { const data = new FormData(); data.append("file", file); const response = await uploadStudentTrainingFile(data); url = response?.url || response?.data?.url || ""; } if (!url) throw new Error("上传成功但未返回图片地址"); clearAbChart(); abChart.value = { url, name: file.name }; proxy?.$modal?.msgSuccess?.("AB 测试结果对比图上传成功,可点击图片预览"); } catch (error) { proxy?.$modal?.msgError?.(error?.message || "图片上传失败,请重试"); } finally { uploading.value = false; event.target.value = ""; } }
async function saveStepThreeDraft() { if (saving.value) return; saving.value = true; try { await saveStudentTrainingAnswer(TASK_KEY, { step3Answer: JSON.stringify(buildStepThree()), currentStep: 3, submitted: false, saveAction: "SAVE" }); proxy?.$modal?.msgSuccess?.("草稿已保存"); } catch (error) { proxy?.$modal?.msgError?.(error?.message || "保存失败,请重试"); } finally { saving.value = false; } } async function saveStepThreeDraft() { if (saving.value) return; saving.value = true; try { await saveStudentTrainingAnswer(TASK_KEY, { step3Answer: JSON.stringify(buildStepThree()), currentStep: 3, submitted: false, saveAction: "SAVE" }); proxy?.$modal?.msgSuccess?.("草稿已保存"); } catch (error) { proxy?.$modal?.msgError?.(error?.message || "保存失败,请重试"); } finally { saving.value = false; } }
@ -195,7 +214,7 @@ async function submitTask() {
await validateProductDiagnosisIterationStepOne(stepOne); await validateProductDiagnosisIterationStepOne(stepOne);
await saveStudentTrainingAnswer(TASK_KEY, { step1Answer: JSON.stringify(stepOne), currentStep: 1, submitted: false, saveAction: "SAVE" }); await saveStudentTrainingAnswer(TASK_KEY, { step1Answer: JSON.stringify(stepOne), currentStep: 1, submitted: false, saveAction: "SAVE" });
const stepTwo = buildStepTwo(); const stepTwo = buildStepTwo();
await validateProductDiagnosisIterationStepTwo(stepTwo); await validateProductDiagnosisIterationStepTwo(buildStepTwoValidationPayload(stepTwo));
await saveStudentTrainingAnswer(TASK_KEY, { step2Answer: JSON.stringify(stepTwo), currentStep: 2, submitted: false, saveAction: "SAVE" }); await saveStudentTrainingAnswer(TASK_KEY, { step2Answer: JSON.stringify(stepTwo), currentStep: 2, submitted: false, saveAction: "SAVE" });
const stepThree = buildStepThree(); const stepThree = buildStepThree();
await validateProductDiagnosisIterationStepThree(stepThree); await validateProductDiagnosisIterationStepThree(stepThree);
@ -262,16 +281,49 @@ function restoreStepOne(stepOne) {
} }
if (stepOne.funnelChartUrl) funnelChart.value = { url: stepOne.funnelChartUrl, name: stepOne.funnelChartName || "节点转化漏斗对比图" }; if (stepOne.funnelChartUrl) funnelChart.value = { url: stepOne.funnelChartUrl, name: stepOne.funnelChartName || "节点转化漏斗对比图" };
} }
function parseSavedStep(value) {
if (!value) return {};
if (typeof value === "object") return value;
try { return JSON.parse(value); } catch (error) { return {}; }
}
function restoreStepTwo(stepTwo) {
if (!stepTwo || typeof stepTwo !== "object" || !Object.keys(stepTwo).length) return;
const savedAnalysis = stepTwo.analysisResult && typeof stepTwo.analysisResult === "object" ? stepTwo.analysisResult : {};
const restoredWordCloud = Array.isArray(stepTwo.wordCloud) ? stepTwo.wordCloud : Array.isArray(savedAnalysis.wordCloud) ? savedAnalysis.wordCloud : [];
const restoredComments = Array.isArray(stepTwo.previewComments) ? stepTwo.previewComments : Array.isArray(savedAnalysis.previewComments) ? savedAnalysis.previewComments : [];
const restoredTopIssues = [0, 1, 2].map((index) => stepTwo.topIssues?.[index] || savedAnalysis.topIssues?.[index] || "");
const savedSentiments = Array.isArray(stepTwo.sentiments) ? stepTwo.sentiments : [];
sentiments.value = createSentiments().map((row) => ({ ...row, ...(savedSentiments.find((item) => item?.type === row.type) || {}) }));
const count = (type, analysisKey) => {
const analysisCount = Number(savedAnalysis[analysisKey]);
if (Number.isFinite(analysisCount)) return analysisCount;
const sentimentCount = Number(sentiments.value.find((row) => row.type === type)?.count);
return Number.isFinite(sentimentCount) ? sentimentCount : 0;
};
const positiveCount = count("正面", "positiveCount");
const neutralCount = count("中性", "neutralCount");
const negativeCount = count("负面", "negativeCount");
const sourceFileName = String(stepTwo.sourceFileName || savedAnalysis.sourceFileName || "");
const inferredTotal = positiveCount + neutralCount + negativeCount;
const savedTotal = Number(stepTwo.totalComments ?? savedAnalysis.totalComments);
const totalComments = Number.isFinite(savedTotal) ? savedTotal : inferredTotal;
commentPreview.value = { fileName: sourceFileName, totalComments, comments: restoredComments };
wordCloud.value = restoredWordCloud;
topIssues.value = restoredTopIssues;
rootCauseAnalysis.value = stepTwo.rootCauseAnalysis || "";
if (sourceFileName || restoredWordCloud.length || restoredComments.length || restoredTopIssues.some(Boolean)) {
analysisResult.value = { sourceFileName, totalComments, positiveCount, neutralCount, negativeCount, topIssues: restoredTopIssues, wordCloud: restoredWordCloud, previewComments: restoredComments };
}
nextTick(renderWordCloud);
}
function restore(answer) { function restore(answer) {
try { const stepOne = parseSavedStep(answer?.step1Answer);
const stepOne = JSON.parse(answer?.step1Answer || "{}");
restoreStepOne(stepOne); restoreStepOne(stepOne);
const stepTwo = JSON.parse(answer?.step2Answer || "{}"); restoreStepTwo(parseSavedStep(answer?.step2Answer));
if (stepTwo.sourceFileName) { commentPreview.value = { fileName: stepTwo.sourceFileName, totalComments: 0, comments: stepTwo.previewComments || [] }; wordCloud.value = stepTwo.wordCloud || []; topIssues.value = [0, 1, 2].map((index) => stepTwo.topIssues?.[index] || ""); sentiments.value = (stepTwo.sentiments || createSentiments()).map((row) => ({ ...createSentiments().find((item) => item.type === row.type), ...row })); rootCauseAnalysis.value = stepTwo.rootCauseAnalysis || ""; nextTick(renderWordCloud); } const stepThree = parseSavedStep(answer?.step3Answer);
const stepThree = JSON.parse(answer?.step3Answer || "{}");
if (stepThree.testPlan || stepThree.comparisonChartUrl || stepThree.iterationRecommendation) { abTestPlan.value = stepThree.testPlan || ""; if (Array.isArray(stepThree.rows) && stepThree.rows.length === createAbRows().length) abRows.value = createAbRows().map((row) => ({ ...row, ...(stepThree.rows.find((item) => item?.metric === row.metric) || {}) })); if (stepThree.comparisonChartUrl) abChart.value = { url: stepThree.comparisonChartUrl, name: stepThree.comparisonChartName || "AB 测试结果对比图" }; iterationRecommendation.value = stepThree.iterationRecommendation || ""; } if (stepThree.testPlan || stepThree.comparisonChartUrl || stepThree.iterationRecommendation) { abTestPlan.value = stepThree.testPlan || ""; if (Array.isArray(stepThree.rows) && stepThree.rows.length === createAbRows().length) abRows.value = createAbRows().map((row) => ({ ...row, ...(stepThree.rows.find((item) => item?.metric === row.metric) || {}) })); if (stepThree.comparisonChartUrl) abChart.value = { url: stepThree.comparisonChartUrl, name: stepThree.comparisonChartName || "AB 测试结果对比图" }; iterationRecommendation.value = stepThree.iterationRecommendation || ""; }
currentStep.value = [1, 2, 3].includes(Number(answer?.currentStep)) ? Number(answer.currentStep) : 1; currentStep.value = [1, 2, 3].includes(Number(answer?.currentStep)) ? Number(answer.currentStep) : 1;
} catch (error) {}
} }
onMounted(async () => { try { taskConfig.value = (await getTrainingTaskByKey(TASK_KEY))?.data || null; } catch (error) { taskConfig.value = null; } try { restore((await getStudentTrainingAnswer(TASK_KEY))?.data); } catch (error) {} }); onBeforeUnmount(() => { if (funnelChart.value.url.startsWith("blob:")) URL.revokeObjectURL(funnelChart.value.url); if (abChart.value.url.startsWith("blob:")) URL.revokeObjectURL(abChart.value.url); wordCloudChart?.dispose(); }); onMounted(async () => { try { taskConfig.value = (await getTrainingTaskByKey(TASK_KEY))?.data || null; } catch (error) { taskConfig.value = null; } try { restore((await getStudentTrainingAnswer(TASK_KEY))?.data); } catch (error) {} }); onBeforeUnmount(() => { if (funnelChart.value.url.startsWith("blob:")) URL.revokeObjectURL(funnelChart.value.url); if (abChart.value.url.startsWith("blob:")) URL.revokeObjectURL(abChart.value.url); wordCloudChart?.dispose(); });
</script> </script>

@ -46,3 +46,13 @@ test("三个步骤的后端校验错误均交由页面显示单条中文提示",
assert.match(page, /function diagnosisErrorMessage[\s\S]*?Request failed with status code/); assert.match(page, /function diagnosisErrorMessage[\s\S]*?Request failed with status code/);
assert.match(page, /validateStepTwo[\s\S]*?diagnosisErrorMessage\(error/); assert.match(page, /validateStepTwo[\s\S]*?diagnosisErrorMessage\(error/);
}); });
test("第二步草稿保存完整分析状态并在重新进入页面后恢复", () => {
assert.match(page, /function buildStepTwo\(\)[\s\S]*?totalComments: commentPreview\.value\.totalComments[\s\S]*?analysisResult: analysisResult\.value/);
assert.match(page, /function buildStepTwoValidationPayload/);
assert.match(page, /validateProductDiagnosisIterationStepTwo\(buildStepTwoValidationPayload\(data\)\)/);
assert.match(page, /function restoreStepTwo\(stepTwo\)[\s\S]*?rootCauseAnalysis\.value = stepTwo\.rootCauseAnalysis/);
assert.match(page, /analysisResult\.value = \{ sourceFileName, totalComments, positiveCount, neutralCount, negativeCount/);
assert.match(page, /restoreStepTwo\(parseSavedStep\(answer\?\.step2Answer\)\)/);
assert.doesNotMatch(page, /if \(stepTwo\.sourceFileName\)/);
});

Loading…
Cancel
Save