diff --git a/src/api/studentTrainingAnswer.js b/src/api/studentTrainingAnswer.js index 218647b..7b3bc46 100644 --- a/src/api/studentTrainingAnswer.js +++ b/src/api/studentTrainingAnswer.js @@ -401,12 +401,19 @@ export function validateReleaseChannelEvaluationStepFour(payload) { export function validateProductDiagnosisIterationStepOne(payload) { const value = payload || {}; + const submittedChannels = Array.isArray(value.channels) ? value.channels : null; const conversionRates = Array.isArray(value.conversionRates) ? value.conversionRates : []; const anomalyRows = Array.isArray(value.anomalyRows) ? value.anomalyRows : []; if (isStudentDemo()) { try { const nodes = ["曝光-点击", "点击-加购", "加购-下单", "下单-好评", "好评-复购"]; - const channels = ["天猫", "京东", "抖音", "拼多多"]; + const legacyChannels = [{ id: "tmall", name: "天猫" }, { id: "jd", name: "京东" }, { id: "douyin", name: "抖音" }, { id: "pinduoduo", name: "拼多多" }]; + const channels = (submittedChannels || legacyChannels).map((channel) => ({ id: String(channel?.id || "").trim(), name: String(channel?.name || "").trim() })); + if (!channels.length || channels.some((channel) => !channel.id || !channel.name) + || new Set(channels.map((channel) => channel.id)).size !== channels.length + || new Set(channels.map((channel) => channel.name)).size !== channels.length) { + throw new Error("请至少保留一个渠道,并确保渠道名称不为空且不重复"); + } if (conversionRates.length !== nodes.length || new Set(conversionRates.map((row) => String(row?.linkNode || "").trim())).size !== nodes.length) { throw new Error("请完整填写 5 个链路节点的转化率"); } @@ -414,19 +421,22 @@ export function validateProductDiagnosisIterationStepOne(payload) { conversionRates.forEach((row) => { const node = String(row?.linkNode || "").trim(); if (!nodes.includes(node)) throw new Error("链路节点必须与题目中的 5 个节点一致"); - const values = [row?.tmall, row?.jd, row?.douyin, row?.pinduoduo, row?.industryAverage].map(Number); + const channelRates = row?.channelRates || row || {}; + const rawValues = [...channels.map((channel) => channelRates[channel.id]), row?.industryAverage]; + if (rawValues.some((item) => item === null || item === "" || typeof item === "undefined")) throw new Error("请填写 0 至 100 之间的转化率"); + const values = rawValues.map(Number); if (values.some((item) => !Number.isFinite(item) || item < 0 || item > 100)) throw new Error("请填写 0 至 100 之间的转化率"); - rates.set(node, { ...row, values }); + rates.set(node, { ...row, channelRates }); }); if (!String(value.funnelChartUrl || "").trim()) throw new Error("请上传节点转化漏斗对比图"); if (anomalyRows.length < 1 || anomalyRows.length > 5) throw new Error("异常节点分析最少填写 1 行,最多填写 5 行"); anomalyRows.forEach((row, index) => { - const channel = String(row?.channel || "").trim(); + const channel = channels.find((item) => item.id === String(row?.channelId || "").trim()) || channels.find((item) => item.name === String(row?.channel || "").trim()); const node = String(row?.linkNode || "").trim(); const source = rates.get(node); - if (!channels.includes(channel)) throw new Error(`第 ${index + 1} 行渠道必须来源于第一个表格`); + if (!channel) throw new Error(`第 ${index + 1} 行渠道必须来源于第一个表格`); if (!source) throw new Error(`第 ${index + 1} 行链路节点必须来源于第一个表格`); - const sourceRate = source[{ 天猫: "tmall", 京东: "jd", 抖音: "douyin", 拼多多: "pinduoduo" }[channel]]; + const sourceRate = source.channelRates[channel.id]; if (Number(row?.storeConversionRate) !== Number(sourceRate) || Number(row?.industryAverageConversionRate) !== Number(source.industryAverage)) { throw new Error(`第 ${index + 1} 行的店铺和行业转化率必须与第一个表格对应数据一致`); } @@ -438,7 +448,7 @@ export function validateProductDiagnosisIterationStepOne(payload) { return request({ url: "/api/student-training-answers/product-diagnosis-iteration/step-1/validate", method: "post", - data: { conversionRates, funnelChartUrl: value.funnelChartUrl, anomalyRows }, + data: { channels: submittedChannels, conversionRates, funnelChartUrl: value.funnelChartUrl, anomalyRows }, headers: { repeatSubmit: false }, }); } diff --git a/src/views/assessment/diagnosis.vue b/src/views/assessment/diagnosis.vue index 40149e4..f569136 100644 --- a/src/views/assessment/diagnosis.vue +++ b/src/views/assessment/diagnosis.vue @@ -14,9 +14,43 @@
2.下载Excel模版,填写关键信息,自动计算各环节转化率。提供自动化模版。

3.导出数据表上传到通用AI大模型,通过AI大模型辅助完成计算,参考步骤及提示词如下:请根据表格数据,计算从曝光到复购链路节点转化率。

-
一

计算各节点转化率

根据案例数据计算各个渠道链路节点转化率,参考方法及工具如下:

链路节点天猫京东抖音拼多多行业均值
{{ row.linkNode }}%
+
+
一

计算各节点转化率

根据案例数据计算各个渠道链路节点转化率,参考方法及工具如下:

+
+ 链路节点和行业均值固定,中间渠道可按案例数据增加或删除。 + +
+
+ + + + + + + + + + + + + + + +
链路节点 +
+ + +
+
行业均值
{{ row.linkNode }} +
+ + % +
+
%
+
+
二

全链路转化漏斗可视化

根据节点转化率计算结果,绘制各渠道转化率与行业均值的转化漏斗对比图,可视化呈现多环节情况,参考方法及工具如下:

1.使用Excel绘图功能,自主完成转化漏斗对比图,可参考《实训操作说明》。
2.下载Excel模版,填写关键信息,自动计算各环节转化率并生成转化漏斗对比图。

3.导出数据表上传到通用AI大模型,通过AI大模型辅助完成,参考步骤及提示词如下:请根据表格数据绘制漏斗图,并与行业均值漏斗图进行对比。

-
三

定位异常节点

对比案例行业均值数据,定位异常节点。

对比案例行业均值数据,定位异常节点。
渠道链路节点店铺转化率行业均值转化率异常分析与判定
%%

说明:这个表格默认展示一行可填写,最少一行,可以增加行,最多5行,这里填写的渠道需要来源于第一个表的渠道。

+
三

定位异常节点

对比案例行业均值数据,定位异常节点。

对比案例行业均值数据,定位异常节点。
渠道链路节点店铺转化率行业均值转化率异常分析与判定
%
%

说明:这个表格默认展示一行可填写,最少一行,可以增加行,最多5行,这里填写的渠道需要来源于第一个表的渠道。