|
|
|
@ -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>
|
|
|
|
|