From 10b1d140a1b371ee57d8eabe871cb2df60030fbd Mon Sep 17 00:00:00 2001 From: chenyuan Date: Wed, 2 Sep 2026 20:47:10 +0800 Subject: [PATCH] fix: clarify diagnosis sentiment validation --- src/api/studentTrainingAnswer.js | 2 +- src/views/assessment/diagnosis.vue | 9 +++++++-- tests/product-diagnosis-dynamic-channels.static.test.cjs | 3 +++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/api/studentTrainingAnswer.js b/src/api/studentTrainingAnswer.js index 27c8564..a45c351 100644 --- a/src/api/studentTrainingAnswer.js +++ b/src/api/studentTrainingAnswer.js @@ -490,7 +490,7 @@ export function validateProductDiagnosisIterationStepTwo(payload) { if (sentiments.length !== 3) throw new Error("请填写正面、中性、负面三类情感数据"); const total = sentiments.reduce((sum, item) => sum + Number(item?.count), 0); if (!Number.isFinite(total) || total <= 0) throw new Error("情感评论数量合计必须大于 0"); - sentiments.forEach((item) => { const expected = Number((Number(item?.count) * 100 / total).toFixed(2)); if (!Number.isFinite(Number(item?.count)) || Number(item?.count) < 0 || Number(item?.percentage) !== expected) throw new Error(`${item?.type || "情感"}评论占比计算不正确`); }); + sentiments.forEach((item) => { const expected = Number((Number(item?.count) * 100 / total).toFixed(2)); if (!Number.isFinite(Number(item?.count)) || Number(item?.count) < 0 || Number(item?.percentage) !== expected) throw new Error("正面、中性、负面评论占比之和应为100%。"); }); return Promise.resolve({ code: 200, data: { valid: true, message: "情感评论占比计算校验通过" } }); } catch (error) { return Promise.reject(error); } } diff --git a/src/views/assessment/diagnosis.vue b/src/views/assessment/diagnosis.vue index 7111c24..a4f1f1e 100644 --- a/src/views/assessment/diagnosis.vue +++ b/src/views/assessment/diagnosis.vue @@ -120,6 +120,11 @@ function createAnomalyRow() { return { channelId: "", channel: "", linkNode: "", function createSentiments() { return ["正面", "中性", "负面"].map((type) => ({ type, count: null, percentage: null })); } function createAbRows() { return ["点击-加购转化率", "加购-下单转化率", "下单-好评转化率", "详情页跳出率", "退货率"].map((metric) => ({ metric, controlValue: null, experimentValue: null, analysis: "" })); } function isRate(value) { return value !== null && value !== "" && Number.isFinite(Number(value)); } +function diagnosisErrorMessage(error, fallback) { + const serverMessage = String(error?.response?.data?.msg || "").trim(); + const message = serverMessage || String(error?.message || "").trim(); + return /Request failed with status code|系统接口\d{3}异常/i.test(message) ? fallback : message || fallback; +} function buildStepOne() { const channelSnapshot = channels.value.map((channel) => ({ id: channel.id, name: channel.name.trim() })); return { @@ -168,7 +173,7 @@ function runSentimentAnalysis() { })); 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?.(error?.message || "请检查词云、情感占比和根因分析"); 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(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: "" }; } 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; } } @@ -199,7 +204,7 @@ async function submitTask() { proxy?.$modal?.msgSuccess?.("实训任务已提交"); return true; } catch (error) { - proxy?.$modal?.msgError?.(error?.message || "提交失败,请检查三个步骤的填写内容"); + proxy?.$modal?.msgError?.(diagnosisErrorMessage(error, "提交失败,请检查三个步骤的填写内容")); return false; } finally { saving.value = false; diff --git a/tests/product-diagnosis-dynamic-channels.static.test.cjs b/tests/product-diagnosis-dynamic-channels.static.test.cjs index 9e09889..f52ae92 100644 --- a/tests/product-diagnosis-dynamic-channels.static.test.cjs +++ b/tests/product-diagnosis-dynamic-channels.static.test.cjs @@ -42,4 +42,7 @@ test("三个步骤的后端校验错误均交由页面显示单条中文提示", api.indexOf("export function validatePromotionEffectOptimizationStepOne") ); assert.equal((validationBlock.match(/skipErrorMessage:\s*true/g) || []).length, 3); + assert.match(api, /throw new Error\("正面、中性、负面评论占比之和应为100%。"\)/); + assert.match(page, /function diagnosisErrorMessage[\s\S]*?Request failed with status code/); + assert.match(page, /validateStepTwo[\s\S]*?diagnosisErrorMessage\(error/); });