import request from "@/utils/request"; import { getDemoAnswerKey, getStudentDemoSession, isStudentDemo } from "@/utils/studentDemo"; const newProductSurveyStepOneAnswers = { "selling-point-1": "核心层", "selling-point-2": "有形层", "selling-point-3": "核心层", "selling-point-4": "核心层", "selling-point-5": "有形层", "selling-point-6": "有形层", "selling-point-7": "有形层", "selling-point-8": "延伸层", "selling-point-9": "延伸层", "selling-point-10": "延伸层", "selling-point-11": "有形层", }; function demoResult(taskKey) { try { return JSON.parse(sessionStorage.getItem(getDemoAnswerKey(taskKey)) || "null"); } catch (error) { return null; } } export function getStudentTrainingAnswer(taskKey, params) { if (isStudentDemo()) return Promise.resolve({ code: 200, data: demoResult(taskKey) }); return request({ url: `/api/student-training-answers/${taskKey}`, method: "get", params, }); } export function saveStudentTrainingAnswer(taskKey, data) { if (isStudentDemo()) { const answer = { ...(demoResult(taskKey) || {}), ...data, teachingClassId: getStudentDemoSession()?.userInfo?.demoTeachingClassId }; sessionStorage.setItem(getDemoAnswerKey(taskKey), JSON.stringify(answer)); return Promise.resolve({ code: 200, data: answer }); } return request({ url: `/api/student-training-answers/${taskKey}`, method: "post", data, headers: { repeatSubmit: false }, }); } export function deleteStudentTrainingAnswer(taskKey, params) { if (isStudentDemo()) { sessionStorage.removeItem(getDemoAnswerKey(taskKey)); return Promise.resolve({ code: 200 }); } return request({ url: `/api/student-training-answers/${taskKey}`, method: "delete", params, }); } export function uploadStudentTrainingFile(data) { if (isStudentDemo()) return Promise.reject(new Error("演示模式不上传文件")); return request({ url: "/common/upload", method: "post", data, headers: { "Content-Type": "multipart/form-data", repeatSubmit: false, }, }); } export function validateProductPricingStepOne(payload) { const value = payload || {}; const rows = Array.isArray(value.pricingRows) ? value.pricingRows : []; if (isStudentDemo()) { const ranges = { dealer: [15, 25], "direct-retail": [40, 60], "premium-brand": [70, 80] }; const unitCost = Number(value.unitCost); if (!Number.isFinite(unitCost) || unitCost <= 0) return Promise.reject(new Error("单台综合成本必须大于 0")); if (rows.length !== 3) return Promise.reject(new Error("请填写三个渠道的加成率")); try { const pricingRows = Object.keys(ranges).map((id) => { const markupRate = Number(rows.find((item) => item?.id === id)?.markupRate); const [min, max] = ranges[id]; if (!Number.isFinite(markupRate) || markupRate < min || markupRate > max) { throw new Error(`渠道加成率需在 ${min}%~${max}% 范围内`); } return { id, markupRate: Number(markupRate.toFixed(2)), salePrice: Number((unitCost * (1 + markupRate / 100)).toFixed(2)) }; }); return Promise.resolve({ code: 200, data: { valid: true, message: "渠道加成定价已校验", unitCost: Number(unitCost.toFixed(2)), pricingRows } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/product-pricing/step-1/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function validateProductPricingStepTwo(payload) { const value = payload || {}; const rows = Array.isArray(value.competitorRows) ? value.competitorRows : []; const decision = value.decision || {}; if (isStudentDemo()) { const unitCost = Number(value.unitCost); if (!Number.isFinite(unitCost) || unitCost <= 0) return Promise.reject(new Error("请先完成第 1 步并填写单台综合成本")); if (rows.length !== 4) return Promise.reject(new Error("请完整填写 4 个竞品信息")); try { const productTypes = rows.map((row, index) => { const productType = String(row?.productType || "").trim(); const brand = String(row?.brand || "").trim(); const differentiator = String(row?.differentiator || "").trim(); const ecommercePrice = Number(row?.ecommercePrice); if (!productType || !brand || !differentiator || !Number.isFinite(ecommercePrice) || ecommercePrice <= 0) throw new Error(`请完整填写竞品 ${index + 1} 的信息`); return productType; }); if (new Set(productTypes).size !== productTypes.length) { throw new Error("竞品产品类型不能重复,请使用可区分的竞品名称"); } const targetPrice = Number(decision.targetPrice); if (!productTypes.includes(String(decision.benchmarkCompetitor || "").trim())) throw new Error("对标竞品必须从上方已填写的竞品中选择"); if (!Number.isFinite(targetPrice) || targetPrice <= 0 || !String(decision.differentiator || "").trim() || !String(decision.pricingReason || "").trim()) throw new Error("请完整填写本产品定价、差异化优势和定价原因"); return Promise.resolve({ code: 200, data: { valid: true, message: "竞品价格带与定价已校验", competitorRows: rows, decision: { ...decision, targetPrice: Number(targetPrice.toFixed(2)), grossMarginRate: Number((((targetPrice - unitCost) / targetPrice) * 100).toFixed(2)) } } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/product-pricing/step-2/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function validateProductPricingStepThree(payload) { const value = payload || {}; const valueItems = Array.isArray(value.valueItems) ? value.valueItems : []; if (isStudentDemo()) { try { if (!valueItems.length) throw new Error("请至少填写一项可替代产品或功能的价值感知"); const normalizedItems = valueItems.map((item, index) => { const name = String(item?.name || "").trim(); const perceivedValue = Number(item?.perceivedValue); if (!String(item?.id || "").trim() || !name || !Number.isFinite(perceivedValue) || perceivedValue <= 0) { throw new Error(`请完整填写第 ${index + 1} 项价值感知`); } return { ...item, id: String(item.id).trim(), name, perceivedValue: Number(perceivedValue.toFixed(2)) }; }); if (new Set(normalizedItems.map((item) => item.id)).size !== normalizedItems.length) throw new Error("价值感知条目不能重复"); const discountRate = Number(value.discountRate); if (!Number.isFinite(discountRate) || discountRate <= 0 || discountRate > 100) throw new Error("利润让渡折扣率应大于 0 且不超过 100%"); const perceivedValueTotal = Number(normalizedItems.reduce((total, item) => total + item.perceivedValue, 0).toFixed(2)); return Promise.resolve({ code: 200, data: { valid: true, message: "用户感知价值定价已校验", valueItems: normalizedItems, perceivedValueTotal, discountRate: Number(discountRate.toFixed(2)), valueBasedPrice: Number((perceivedValueTotal * discountRate / 100).toFixed(2)), }, }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/product-pricing/step-3/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function validateProductPricingStepFour(payload) { const value = payload || {}; const psmRows = Array.isArray(value.psmRows) ? value.psmRows : []; const pricePoints = value.pricePoints || {}; if (isStudentDemo()) { try { const requiredRanges = ["≤100元", "100~150元", "150~200元", "200~250元", "250~300元", "≥300元"]; if (psmRows.length < requiredRanges.length) throw new Error("请完整填写 6 个固定价格区间的 PSM 累计占比"); const seenIds = new Set(); const seenRanges = new Set(); const normalizedRows = psmRows.map((row, index) => { const id = String(row?.id || "").trim(); const priceRange = String(row?.priceRange || "").trim(); if (!id || !priceRange || seenIds.has(id) || seenRanges.has(priceRange)) throw new Error("PSM 价格区间不能重复且不能为空"); seenIds.add(id); seenRanges.add(priceRange); const normalized = { ...row, id, priceRange }; [["tooExpensive", "太贵不买"], ["tooCheap", "太便宜不买"], ["expensiveAcceptable", "偏高可接受"], ["cheapValue", "便宜划算"]].forEach(([field, label]) => { const percentage = Number(row?.[field]); if (!Number.isFinite(percentage) || percentage < 0 || percentage > 100) throw new Error(`第 ${index + 1} 行“${label}”累计占比应在 0%–100% 之间`); normalized[field] = Number(percentage.toFixed(2)); }); return normalized; }); if (!requiredRanges.every((range) => seenRanges.has(range))) throw new Error("请保留并填写 6 个固定价格区间"); const optimalPrice = Number(pricePoints.optimalPrice); const acceptablePrice = Number(pricePoints.acceptablePrice); const acceptableRange = String(pricePoints.acceptableRange || "").trim(); if (!Number.isFinite(optimalPrice) || optimalPrice <= 0 || !Number.isFinite(acceptablePrice) || acceptablePrice <= 0 || !acceptableRange) { throw new Error("请完整填写最优价格点、可接受价格点和可接受价格范围"); } return Promise.resolve({ code: 200, data: { valid: true, message: "PSM 价格敏感度测试已校验", psmRows: normalizedRows, pricePoints: { optimalPrice: Number(optimalPrice.toFixed(2)), acceptablePrice: Number(acceptablePrice.toFixed(2)), acceptableRange }, } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/product-pricing/step-4/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function validateProductPricingStepFive(payload) { const value = payload || {}; const methodResults = Array.isArray(value.methodResults) ? value.methodResults : []; if (isStudentDemo()) { try { const requiredKeys = ["cost-markup", "competitive", "value-based", "psm"]; const selectedCostMarkupChannel = String(value.selectedCostMarkupChannel || "").trim(); if (!selectedCostMarkupChannel) throw new Error("请选择用于汇总的成本加成定价渠道"); if (methodResults.length !== requiredKeys.length) throw new Error("请完整汇总四种定价方法的测算结果"); const seenKeys = new Set(); const normalizedResults = methodResults.map((item) => { const key = String(item?.key || "").trim(); const methodName = String(item?.methodName || "").trim(); const measuredPrice = Number(item?.measuredPrice); const grossMarginRate = Number(item?.grossMarginRate); if (!requiredKeys.includes(key) || seenKeys.has(key) || !methodName || !Number.isFinite(measuredPrice) || measuredPrice <= 0 || !Number.isFinite(grossMarginRate)) { throw new Error("请完整填写四种定价方法的测算结果和毛利润率"); } if (grossMarginRate < -100 || grossMarginRate > 100) throw new Error("毛利润率应在 -100% 至 100% 之间"); seenKeys.add(key); return { key, methodName, measuredPrice: Number(measuredPrice.toFixed(2)), grossMarginRate: Number(grossMarginRate.toFixed(2)) }; }); if (!requiredKeys.every((key) => seenKeys.has(key))) throw new Error("定价方法汇总项不正确或重复"); const finalAnalysis = String(value.finalAnalysis || "").trim(); if (!finalAnalysis) throw new Error("请填写最终量产定价分析"); return Promise.resolve({ code: 200, data: { valid: true, message: "最终量产定价汇总已校验", selectedCostMarkupChannel, methodResults: normalizedResults, finalAnalysis } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/product-pricing/step-5/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function checkNewProductSurveyStepOne(items) { if (isStudentDemo()) { const results = Array.isArray(items) ? items.map((item) => { const correct = newProductSurveyStepOneAnswers[item?.id] === item?.category; return { id: item?.id, correct, message: correct ? "分类正确" : "分类有误,请重新选择", }; }) : []; const correctCount = results.filter((item) => item.correct).length; return Promise.resolve({ code: 200, data: { items: results, correctCount, totalCount: results.length, allCorrect: results.length === Object.keys(newProductSurveyStepOneAnswers).length && correctCount === results.length, }, }); } return request({ url: "/api/student-training-answers/new-product-survey/step-1/check", method: "post", data: { items }, headers: { repeatSubmit: false }, }); } export function validateReleaseChannelEvaluationStepOne(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { const normalizedRows = rows.map((row) => ({ dimension: String(row?.dimension || "").trim(), description: String(row?.description || "").trim(), weight: Number(row?.weight), })); const totalWeight = Number(normalizedRows.reduce((sum, row) => sum + row.weight, 0).toFixed(6)); if (!normalizedRows.length || normalizedRows.some((row) => !row.dimension || !row.description || !Number.isFinite(row.weight) || row.weight < 0) || totalWeight !== 100) { return Promise.reject(new Error("请填写每行评估维度、维度说明和非负权重,且权重之和必须等于100%")); } return Promise.resolve({ code: 200, data: { valid: true, message: "权重校验通过", totalWeight } }); } return request({ url: "/api/student-training-answers/release-channel-evaluation/step-1/validate", method: "post", data: { rows }, headers: { repeatSubmit: false }, }); } export function validateReleaseChannelEvaluationStepTwo(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { const normalizedRows = rows.map((row) => ({ channel: String(row?.channel || "").trim(), dailySearchVolume: Number(row?.dailySearchVolume), supplyDemandRatio: Number(row?.supplyDemandRatio), averageClickRate: Number(row?.averageClickRate), averageCpc: Number(row?.averageCpc), averageConversionRate: Number(row?.averageConversionRate), existingVisitorCount: Number(row?.existingVisitorCount), matchingScore: Number(row?.matchingScore), })); const valid = normalizedRows.length > 0 && normalizedRows.every((row) => row.channel && [row.dailySearchVolume, row.supplyDemandRatio, row.averageCpc, row.existingVisitorCount] .every((value) => Number.isFinite(value) && value >= 0) && [row.averageClickRate, row.averageConversionRate].every((value) => Number.isFinite(value) && value >= 0 && value <= 100) && Number.isInteger(row.matchingScore) && row.matchingScore >= 1 && row.matchingScore <= 5); if (!valid) return Promise.reject(new Error("请完整填写渠道数据;点击率和转化率为0-100%,匹配度为1-5分")); return Promise.resolve({ code: 200, data: { valid: true, message: "渠道数据校验通过", rowCount: normalizedRows.length } }); } return request({ url: "/api/student-training-answers/release-channel-evaluation/step-2/validate", method: "post", data: { rows }, headers: { repeatSubmit: false }, }); } export function validateReleaseChannelEvaluationStepThree(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { try { const saved = demoResult("release-channel-evaluation") || {}; const stepOne = typeof saved.step1Answer === "string" ? JSON.parse(saved.step1Answer || "{}") : saved.step1Answer || {}; const stepTwo = typeof saved.step2Answer === "string" ? JSON.parse(saved.step2Answer || "{}") : saved.step2Answer || {}; const sourceRows = Array.isArray(stepTwo.rows) ? stepTwo.rows : []; const weights = Object.fromEntries((Array.isArray(stepOne.rows) ? stepOne.rows : []).map((row) => [row?.dimension, Number(row?.weight)])); const metrics = [ ["市场规模潜力", "dailySearchVolume", false], ["竞争激烈程度", "supplyDemandRatio", true], ["流量点击效率", "averageClickRate", false], ["流量获取成本", "averageCpc", true], ["流量转化效率", "averageConversionRate", false], ["现有用户基础", "existingVisitorCount", false], ["匹配度", "matchingScore", false], ]; if (!sourceRows.length || rows.length !== sourceRows.length) throw new Error("请先完成第二步全部渠道数据"); const submitted = new Map(rows.map((row) => [String(row?.channel || "").trim(), row])); for (const source of sourceRows) { const channel = String(source?.channel || "").trim(); const row = submitted.get(channel); if (!channel || !row) throw new Error("第三步渠道必须与第二步保持一致"); const dimensions = new Map((Array.isArray(row.dimensions) ? row.dimensions : []).map((item) => [item?.dimension, item])); let expectedTotal = 0; metrics.forEach(([dimension, field, reverse]) => { const weight = Number(weights[dimension]); const values = sourceRows.map((item) => Number(item?.[field])); const value = Number(source?.[field]); const min = Math.min(...values); const max = Math.max(...values); const expectedScore = max === min ? 10 : Number((((reverse ? max - value : value - min) * 10) / (max - min)).toFixed(1)); const supplied = dimensions.get(dimension); if (!supplied || Number(supplied.weight) !== weight || Number(supplied.score) !== expectedScore) throw new Error(`“${channel}”的“${dimension}”得分或权重不正确`); expectedTotal += expectedScore * weight / 100; }); if (Number(row.weightedScore) !== Number(expectedTotal.toFixed(2))) throw new Error(`“${channel}”的加权得分不正确`); } return Promise.resolve({ code: 200, data: { valid: true, message: "归一化得分与加权得分校验通过", rowCount: rows.length } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/release-channel-evaluation/step-3/validate", method: "post", data: { rows }, headers: { repeatSubmit: false }, }); } export function validateReleaseChannelEvaluationStepFour(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { try { const analysis = String(payload?.coreChannelAnalysis || "").trim(); const saved = demoResult("release-channel-evaluation") || {}; const stepThree = typeof saved.step3Answer === "string" ? JSON.parse(saved.step3Answer || "{}") : saved.step3Answer || {}; const expectedRows = Array.isArray(stepThree.rows) ? stepThree.rows : []; if (!analysis) throw new Error("请分析核心渠道优势并填写运营建议"); if (!expectedRows.length || rows.length !== expectedRows.length) throw new Error("渠道数量必须与第三步一致"); const expected = new Map(expectedRows.map((row) => [String(row?.channel || "").trim(), Number(row?.weightedScore)])); const channels = new Set(); let previousScore = null; rows.forEach((row, index) => { const channel = String(row?.channel || "").trim(); const score = Number(row?.weightedScore); if (!channel || channels.has(channel) || !expected.has(channel)) throw new Error(`第${index + 1}行渠道必须与第三步一致且不能重复`); if (!Number.isFinite(score) || score !== expected.get(channel)) throw new Error(`“${channel}”的加权综合得分必须与第三步一致`); if (previousScore !== null && previousScore < score) throw new Error("加权综合得分必须按从高到低顺序填写"); channels.add(channel); previousScore = score; }); return Promise.resolve({ code: 200, data: { valid: true, message: "渠道排名与核心渠道校验通过", coreChannels: rows.slice(0, 2).map((row) => row.channel) } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/release-channel-evaluation/step-4/validate", method: "post", data: { rows, coreChannelAnalysis: payload?.coreChannelAnalysis }, headers: { repeatSubmit: false }, }); } export function validateProductDiagnosisIterationStepOne(payload) { const value = payload || {}; const conversionRates = Array.isArray(value.conversionRates) ? value.conversionRates : []; const anomalyRows = Array.isArray(value.anomalyRows) ? value.anomalyRows : []; if (isStudentDemo()) { try { const nodes = ["曝光-点击", "点击-加购", "加购-下单", "下单-好评", "好评-复购"]; const channels = ["天猫", "京东", "抖音", "拼多多"]; if (conversionRates.length !== nodes.length || new Set(conversionRates.map((row) => String(row?.linkNode || "").trim())).size !== nodes.length) { throw new Error("请完整填写 5 个链路节点的转化率"); } const rates = new Map(); 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); if (values.some((item) => !Number.isFinite(item) || item < 0 || item > 100)) throw new Error("请填写 0 至 100 之间的转化率"); rates.set(node, { ...row, values }); }); 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 node = String(row?.linkNode || "").trim(); const source = rates.get(node); if (!channels.includes(channel)) throw new Error(`第 ${index + 1} 行渠道必须来源于第一个表格`); if (!source) throw new Error(`第 ${index + 1} 行链路节点必须来源于第一个表格`); const sourceRate = source[{ 天猫: "tmall", 京东: "jd", 抖音: "douyin", 拼多多: "pinduoduo" }[channel]]; if (Number(row?.storeConversionRate) !== Number(sourceRate) || Number(row?.industryAverageConversionRate) !== Number(source.industryAverage)) { throw new Error(`第 ${index + 1} 行的店铺和行业转化率必须与第一个表格对应数据一致`); } if (!String(row?.anomalyAnalysis || "").trim()) throw new Error(`请填写第 ${index + 1} 行的异常分析与判定`); }); return Promise.resolve({ code: 200, data: { valid: true, message: "转化率、漏斗图和异常节点分析校验通过" } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/product-diagnosis-iteration/step-1/validate", method: "post", data: { conversionRates, funnelChartUrl: value.funnelChartUrl, anomalyRows }, headers: { repeatSubmit: false }, }); } export async function analyzeProductDiagnosisIterationStepTwo(file) { if (isStudentDemo()) { const XLSX = await import("xlsx"); const buffer = await file.arrayBuffer(); const name = String(file?.name || "评论数据"); let comments = []; if (/\.xlsx?$/i.test(name)) { const workbook = XLSX.read(buffer, { type: "array" }); const rows = XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]], { header: 1, defval: "" }); comments = rows.slice(1).map((row) => row.map((item) => String(item || "").trim()).filter((item) => /[\u4e00-\u9fa5A-Za-z]/.test(item)).sort((a, b) => b.length - a.length)[0] || "").filter(Boolean); } else comments = new TextDecoder("utf-8").decode(buffer).split(/\r?\n/).slice(1).map((line) => line.split(/[,,\t]/).map((item) => item.trim()).sort((a, b) => b.length - a.length)[0] || "").filter(Boolean); const positiveWords = ["很好", "喜欢", "满意", "推荐", "好用", "值得", "清晰", "漂亮", "方便", "优秀", "赞"]; const negativeWords = ["太慢", "差", "坏", "问题", "失望", "退货", "卡顿", "慢", "漏水", "故障", "不行", "不值", "异味"]; const issueWords = ["质量", "物流", "故障", "外观", "价格", "包装", "发货", "售后", "退货", "卡顿", "漏水", "安装", "尺寸", "噪音", "异味", "续航", "慢"]; const count = (text, words) => words.filter((word) => text.includes(word)).length; const frequencies = new Map(); let positiveCount = 0; let neutralCount = 0; let negativeCount = 0; comments.forEach((comment) => { const positive = count(comment, positiveWords); const negative = count(comment, negativeWords); if (positive > negative) positiveCount += 1; else if (negative > positive) negativeCount += 1; else neutralCount += 1; issueWords.forEach((word) => { if (comment.includes(word)) frequencies.set(word, (frequencies.get(word) || 0) + 1); }); }); const wordCloud = [...frequencies.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0], "zh-CN")).slice(0, 30).map(([name, value]) => ({ name, value })); if (!comments.length) return Promise.reject(new Error("未从文件中读取到有效评论内容")); return { code: 200, data: { sourceFileName: name, totalComments: comments.length, positiveCount, neutralCount, negativeCount, topIssues: wordCloud.slice(0, 3).map((item) => item.name), wordCloud, previewComments: comments.slice(0, 5) } }; } const data = new FormData(); data.append("file", file); return request({ url: "/api/student-training-answers/product-diagnosis-iteration/step-2/analyze", method: "post", data, headers: { "Content-Type": "multipart/form-data", repeatSubmit: false } }); } export function validateProductDiagnosisIterationStepTwo(payload) { const value = payload || {}; const sentiments = Array.isArray(value.sentiments) ? value.sentiments : []; if (isStudentDemo()) { try { const topIssues = Array.isArray(value.topIssues) ? value.topIssues.map((item) => String(item || "").trim()) : []; if (!String(value.sourceFileName || "").trim()) throw new Error("请先导入评论数据并生成词云"); if (topIssues.length !== 3 || topIssues.some((item) => !item) || new Set(topIssues).size !== 3) throw new Error("请填写词云中出现频次最高的 3 个不重复问题"); if (!String(value.rootCauseAnalysis || "").trim()) throw new Error("请结合词云和情感分析填写核心问题根本原因"); 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 || "情感"}评论占比计算不正确`); }); return Promise.resolve({ code: 200, data: { valid: true, message: "情感评论占比计算校验通过" } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/product-diagnosis-iteration/step-2/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function validateProductDiagnosisIterationStepThree(payload) { const value = payload || {}; const rows = Array.isArray(value.rows) ? value.rows : []; if (isStudentDemo()) { try { const metrics = ["点击-加购转化率", "加购-下单转化率", "下单-好评转化率", "详情页跳出率", "退货率"]; if (!String(value.testPlan || "").trim()) throw new Error("请填写 AB 测试方案"); if (!String(value.comparisonChartUrl || "").trim()) throw new Error("请上传 AB 测试结果对比图"); if (!String(value.iterationRecommendation || "").trim()) throw new Error("请填写诊断结论与迭代优化建议"); if (rows.length !== metrics.length || new Set(rows.map((row) => String(row?.metric || "").trim())).size !== metrics.length) throw new Error("请完整填写 5 项 AB 测试指标"); rows.forEach((row) => { const control = Number(row?.controlValue); const experiment = Number(row?.experimentValue); if (!metrics.includes(String(row?.metric || "").trim()) || !Number.isFinite(control) || !Number.isFinite(experiment) || control < 0 || control > 100 || experiment < 0 || experiment > 100 || !String(row?.analysis || "").trim()) throw new Error("请完整填写五项指标的对照组、实验组数据和判定分析"); }); return Promise.resolve({ code: 200, data: { valid: true, message: "AB 测试方案、数据与对比图校验通过" } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/product-diagnosis-iteration/step-3/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function validatePromotionEffectOptimizationStepOne(payload) { const value = payload || {}; const rows = Array.isArray(value.rows) ? value.rows : []; if (isStudentDemo()) { try { const totalBudget = Number(value.totalBudget); if (!Number.isFinite(totalBudget) || totalBudget <= 0) throw new Error("请填写大于 0 的新产品投放试错预算"); if (rows.length < 2 || rows.length > 5) throw new Error("请填写 2 至 5 个投放渠道"); const saved = demoResult("release-channel-evaluation") || {}; const stepTwo = typeof saved.step2Answer === "string" ? JSON.parse(saved.step2Answer || "{}") : saved.step2Answer || {}; const availableChannels = new Set((Array.isArray(stepTwo.rows) ? stepTwo.rows : []) .map((row) => String(row?.channel || "").trim()).filter(Boolean)); if (!availableChannels.size) throw new Error("请先完成产品发布渠道评估中的渠道数据填写"); const selected = new Set(); let totalPercentage = 0; rows.forEach((row, index) => { const channel = String(row?.channel || "").trim(); const positioning = String(row?.channelPositioning || "").trim(); const percentage = Number(row?.budgetPercentage); const amount = Number(row?.budgetAmount); const phases = [row?.coldStartBudget, row?.growthBudget, row?.stableBudget].map(Number); if (!availableChannels.has(channel) || selected.has(channel)) throw new Error(`第 ${index + 1} 行渠道必须来自产品发布渠道评估,且不能重复`); if (!positioning) throw new Error(`请选择第 ${index + 1} 行的渠道定位`); if (!Number.isFinite(percentage) || percentage < 0) throw new Error(`请填写第 ${index + 1} 行的非负预算占比`); const expectedAmount = Number((totalBudget * percentage / 100).toFixed(2)); if (!Number.isFinite(amount) || Number(amount.toFixed(2)) !== expectedAmount) throw new Error(`第 ${index + 1} 行预算金额应由试错预算和预算占比自动计算`); if (phases.some((item) => !Number.isFinite(item) || item < 0) || Number(phases.reduce((sum, item) => sum + item, 0).toFixed(2)) !== expectedAmount) { throw new Error(`第 ${index + 1} 行三个阶段投放预算之和必须等于预算金额`); } selected.add(channel); totalPercentage += percentage; }); if (Number(totalPercentage.toFixed(6)) !== 100) throw new Error("各渠道预算占比之和必须等于 100%"); return Promise.resolve({ code: 200, data: { valid: true, message: "投放预算分配校验通过", totalBudget: Number(totalBudget.toFixed(2)), availableChannels: [...availableChannels] } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/promotion-effect-optimization/step-1/validate", method: "post", data: { totalBudget: value.totalBudget, rows }, headers: { repeatSubmit: false }, }); } export function validatePromotionEffectOptimizationStepTwo(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { try { const saved = demoResult("promotion-effect-optimization") || {}; const stepOne = typeof saved.step1Answer === "string" ? JSON.parse(saved.step1Answer || "{}") : saved.step1Answer || {}; const channels = [...new Set((Array.isArray(stepOne.rows) ? stepOne.rows : []) .map((row) => String(row?.channel || "").trim()).filter(Boolean))]; const phases = ["冷启动期", "增长期", "稳定期"]; if (channels.length < 2 || channels.length > 5) throw new Error("请先完成并保存第一步投放预算分配"); if (rows.length !== channels.length * phases.length) throw new Error("请为每个渠道的冷启动期、增长期和稳定期分别填写两项投放策略"); const cells = new Set(); rows.forEach((row) => { const phase = String(row?.phase || "").trim(); const channel = String(row?.channel || "").trim(); const strategies = Array.isArray(row?.strategies) ? row.strategies.map((item) => String(item || "").trim()) : []; if (!phases.includes(phase) || !channels.includes(channel)) throw new Error("投放策略的阶段和渠道必须与第一步保持一致"); const cell = `${phase}\u0000${channel}`; if (cells.has(cell)) throw new Error("每个阶段和渠道只能填写一组投放策略"); if (strategies.length !== 2 || strategies.some((item) => !item) || new Set(strategies).size !== 2) { throw new Error(`“${phase} - ${channel}”必须填写两项不重复的投放策略`); } cells.add(cell); }); return Promise.resolve({ code: 200, data: { valid: true, message: "各阶段投放策略校验通过", channelCount: channels.length, cellCount: cells.size } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/promotion-effect-optimization/step-2/validate", method: "post", data: { rows }, headers: { repeatSubmit: false }, }); } export function validatePromotionEffectOptimizationStepThree(payload) { const value = payload || {}; const rows = Array.isArray(value.rows) ? value.rows : []; if (isStudentDemo()) { try { const grossMargin = Number(value.grossMargin); const breakEvenRoi = Number(value.breakEvenRoi); if (!Number.isFinite(grossMargin) || grossMargin <= 0 || grossMargin > 100) throw new Error("请填写大于 0 且不超过 100% 的产品毛利率"); if (breakEvenRoi !== Number((100 / grossMargin).toFixed(2))) throw new Error("保本 ROI 应等于 1 ÷ 毛利率,结果保留两位小数"); const saved = demoResult("promotion-effect-optimization") || {}; const stepOne = typeof saved.step1Answer === "string" ? JSON.parse(saved.step1Answer || "{}") : saved.step1Answer || {}; const channels = [...new Set((Array.isArray(stepOne.rows) ? stepOne.rows : []).map((row) => String(row?.channel || "").trim()).filter(Boolean))]; if (rows.length !== channels.length + 1) throw new Error("请填写所有渠道数据,并在最后一行填写合计"); const channelRows = rows.slice(0, -1); const sum = { impressions: 0, clicks: 0, turnover: 0, promotionCost: 0 }; const validateRow = (row, label) => { const values = ["impressions", "clicks", "turnover", "promotionCost", "clickRate", "actualRoi"].reduce((all, key) => ({ ...all, [key]: Number(row?.[key]) }), {}); if (!Number.isFinite(values.impressions) || values.impressions <= 0 || !Number.isFinite(values.clicks) || values.clicks < 0 || !Number.isFinite(values.turnover) || values.turnover < 0 || !Number.isFinite(values.promotionCost) || values.promotionCost <= 0 || !Number.isFinite(values.clickRate) || !Number.isFinite(values.actualRoi)) throw new Error(`请完整填写${label}的投放数据`); if (values.clickRate !== Number((values.clicks / values.impressions * 100).toFixed(2))) throw new Error(`${label}的点击率应等于点击量 ÷ 曝光量 × 100%`); if (values.actualRoi !== Number((values.turnover / values.promotionCost).toFixed(2))) throw new Error(`${label}的实际 ROI 应等于成交额 ÷ 推广花费`); return values; }; const seen = new Set(); channelRows.forEach((row) => { const channel = String(row?.channel || "").trim(); if (!channels.includes(channel) || seen.has(channel)) throw new Error("投放数据渠道必须与第一步完全一致"); seen.add(channel); const values = validateRow(row, `“${channel}”`); ["impressions", "clicks", "turnover", "promotionCost"].forEach((key) => { sum[key] += values[key]; }); }); if (seen.size !== channels.length) throw new Error("投放数据渠道必须与第一步完全一致"); const total = rows[rows.length - 1]; if (String(total?.channel || "").trim() !== "合计") throw new Error("合计必须固定填写在最后一行"); const totalValues = validateRow(total, "合计"); if (["impressions", "clicks", "turnover", "promotionCost"].some((key) => totalValues[key] !== Number(sum[key].toFixed(2)))) throw new Error("合计行的曝光量、点击量、成交额和推广花费必须等于所有渠道数据之和"); return Promise.resolve({ code: 200, data: { valid: true, message: "保本 ROI 与投放数据校验通过", breakEvenRoi } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/promotion-effect-optimization/step-3/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function validatePromotionEffectOptimizationStepFour(payload) { const value = payload || {}; const rows = Array.isArray(value.rows) ? value.rows : []; if (isStudentDemo()) { try { const decision = String(value.optimizationDecision || "").trim(); if (!decision) throw new Error("请填写整体优化决策"); const saved = demoResult("promotion-effect-optimization") || {}; const stepThree = typeof saved.step3Answer === "string" ? JSON.parse(saved.step3Answer || "{}") : saved.step3Answer || {}; const breakEvenRoi = Number(stepThree.breakEvenRoi); const sourceRows = Array.isArray(stepThree.rows) ? stepThree.rows : []; const channelRois = new Map(sourceRows.filter((row) => row?.channel !== "合计").map((row) => [String(row?.channel || "").trim(), Number(row?.actualRoi)])); if (!Number.isFinite(breakEvenRoi) || !channelRois.size || rows.length !== channelRois.size) throw new Error("请先完成并保存第三步保本 ROI 与投放数据核算"); const seen = new Set(); rows.forEach((row) => { const channel = String(row?.channel || "").trim(); const expectedRoi = channelRois.get(channel); const actualRoi = Number(row?.roi); const shouldAchieve = expectedRoi >= breakEvenRoi; if (!Number.isFinite(expectedRoi) || seen.has(channel) || actualRoi !== expectedRoi) throw new Error("评估渠道与 ROI 必须和第三步保持一致"); if (row?.achievement !== (shouldAchieve ? "达标" : "不达标")) throw new Error(`“${channel}”的是否达标判断不正确`); if (!shouldAchieve && (!String(row?.problemDiagnosis || "").trim() || !String(row?.optimizationAction || "").trim())) throw new Error(`“${channel}”未达标,必须填写问题诊断和优化动作`); seen.add(channel); }); const total = sourceRows.find((row) => row?.channel === "合计"); const overallRoi = Number(total?.actualRoi); return Promise.resolve({ code: 200, data: { valid: true, message: "投放效果评估与优化决策校验通过", overallRoi, breakEvenRoi, overallAchievement: overallRoi >= breakEvenRoi ? "达标" : "不达标" } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/promotion-effect-optimization/step-4/validate", method: "post", data: { rows, optimizationDecision: value.optimizationDecision }, headers: { repeatSubmit: false } }); } export function validatePurchaseDemandForecastStepOne(payload) { const items = Array.isArray(payload?.items) ? payload.items : []; if (isStudentDemo()) { try { const expectedTimes = [13, 14, 15]; if (items.length !== expectedTimes.length) throw new Error("请完整填写未来3个月的市场需求量"); const normalizedItems = expectedTimes.map((time, index) => { const item = items.find((entry) => Number(entry?.time) === time); const demand = Number(item?.demand); if (!item || !Number.isInteger(demand) || demand < 0) { throw new Error(`请填写预测第${index + 1}个月的非负整数需求量`); } return { time, demand }; }); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过", items: normalizedItems } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/purchase-demand-forecast/step-1/validate", method: "post", data: { items }, headers: { repeatSubmit: false }, }); } export function validateCategoryOptimizationStepOne(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { const tolerance = 0.01; if (!rows.length) return Promise.reject(new Error("请至少填写一行品类数据")); if (rows.length > 30) return Promise.reject(new Error("品类数据最多填写30行")); const numbers = rows.map((row, index) => ({ index, categoryName: String(row?.categoryName || "").trim(), revenue: Number(row?.revenue), revenueShare: Number(row?.revenueShare), cumulativeShare: Number(row?.cumulativeShare), })); const incomplete = numbers.find((row) => !row.categoryName || [row.revenue, row.revenueShare, row.cumulativeShare].some((value) => !Number.isFinite(value) || value < 0)); if (incomplete) return Promise.reject(new Error(`第${incomplete.index + 1}行:请完整填写品类名称、近12个月营收、营收占比和累计占比`)); const totalRevenue = numbers.reduce((sum, row) => sum + row.revenue, 0); if (totalRevenue <= 0) return Promise.reject(new Error("近12个月营收总和必须大于0")); let accumulated = 0; for (const row of numbers) { const previous = numbers[row.index - 1]; if (previous && previous.revenue < row.revenue) return Promise.reject(new Error(`第${row.index + 1}行:近12个月营收需按从高到低排序`)); if (Math.abs(row.revenueShare - (row.revenue / totalRevenue) * 100) > tolerance) return Promise.reject(new Error(`第${row.index + 1}行:营收占比与近12个月营收不一致`)); accumulated += row.revenueShare; if (Math.abs(row.cumulativeShare - accumulated) > tolerance) return Promise.reject(new Error(`第${row.index + 1}行:累计占比应等于从第一行到当前行的营收占比之和`)); } return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/category-optimization/step-1/validate", method: "post", data: { rows }, headers: { repeatSubmit: false } }); } export function validateCategoryOptimizationStepTwo(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { if (!rows.length) return Promise.reject(new Error("请先完成第一步品类营收数据填写")); for (const [index, row] of rows.entries()) { const cumulativeShare = Number(row?.cumulativeShare); const expected = cumulativeShare <= 70 ? "A" : cumulativeShare <= 90 ? "B" : "C"; if (!["A", "B", "C"].includes(row?.abcClassification)) return Promise.reject(new Error(`第${index + 1}行:请选择ABC分层判定`)); if (row.abcClassification !== expected) return Promise.reject(new Error(`第${index + 1}行:累计营收占比为${row.cumulativeShare}% ,应判定为${expected}类`)); } return Promise.resolve({ code: 200, data: { valid: true, message: "ABC分层校验通过" } }); } return request({ url: "/api/student-training-answers/category-optimization/step-2/validate", method: "post", data: { rows }, headers: { repeatSubmit: false } }); } export function validateCategoryOptimizationStepThree(payload) { const imageUrl = String(payload?.imageUrl || "").trim(); if (isStudentDemo()) { if (!imageUrl) return Promise.reject(new Error("请上传帕累托图")); return Promise.resolve({ code: 200, data: { valid: true, message: "帕累托图上传校验通过" } }); } return request({ url: "/api/student-training-answers/category-optimization/step-3/validate", method: "post", data: { imageUrl }, headers: { repeatSubmit: false } }); } export function validateCategoryOptimizationStepFour(payload) { const value = payload || {}; const fields = ["productName", "matrixQuadrant", "matrixReason", "necessityAnalysis", "recommendedClassification", "trafficSupportMeasures"]; if (isStudentDemo()) { if (fields.some((field) => !String(value[field] || "").trim())) return Promise.reject(new Error("请完整填写新品矩阵定位、调整必要性与流量扶持措施")); const product = (Array.isArray(value.availableRows) ? value.availableRows : []).find((row) => row?.categoryName === value.productName); if (!product) return Promise.reject(new Error("新品名称必须为第一步品类列表中的一项")); if (value.recommendedClassification === product.abcClassification) return Promise.reject(new Error("建议调整分层不能与当前分层相同")); return Promise.resolve({ code: 200, data: { valid: true, message: "新品分层优化校验通过", currentClassification: product.abcClassification } }); } const data = Object.fromEntries(fields.map((field) => [field, value[field]])); return request({ url: "/api/student-training-answers/category-optimization/step-4/validate", method: "post", data, headers: { repeatSubmit: false } }); } export function validateCategoryOptimizationStepFive(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { const layers = new Set(rows.map((row) => row?.layer)); const complete = rows.length === 4 && ["A", "B", "C", "NEW"].every((layer) => layers.has(layer)) && rows.every((row) => String(row?.operationStrategy || "").trim() && String(row?.inventoryStrategy || "").trim() && String(row?.resourcePriority || "").trim()); if (!complete) return Promise.reject(new Error("请完整填写A、B、C类及新品四行的运营策略、库存策略和资源配置优先级")); return Promise.resolve({ code: 200, data: { valid: true, message: "差异化运营策略校验通过" } }); } return request({ url: "/api/student-training-answers/category-optimization/step-5/validate", method: "post", data: { rows }, headers: { repeatSubmit: false } }); } const requirementsDocumentStepOneAnswers = { "document-info": "document-governance", "product-overview": "product-boundary", "user-needs-scenarios": "user-context", "core-functional-requirements": "functional-scope", "non-functional-requirements": "quality-attributes", "hardware-spec-design": "hardware-constraints", "testing-requirements": "acceptance-criteria", "project-planning": "resource-plan", "change-management": "change-control", }; export function checkRequirementsDocumentStepOne(payload) { const assignments = payload?.assignments && typeof payload.assignments === "object" ? payload.assignments : {}; if (isStudentDemo()) { const expectedModules = Object.keys(requirementsDocumentStepOneAnswers); const wrongModuleIds = expectedModules.filter((moduleId) => assignments[moduleId] !== requirementsDocumentStepOneAnswers[moduleId]); const allCorrect = wrongModuleIds.length === 0 && Object.keys(assignments).length === expectedModules.length; return Promise.resolve({ code: 200, data: { allCorrect, correctCount: expectedModules.length - wrongModuleIds.length, totalCount: expectedModules.length, message: allCorrect ? "匹配正确,已解锁第 2 步。" : "当前匹配尚未全部正确,请继续调整。", }, }); } return request({ url: "/api/student-training-answers/requirements-doc/step-1/check", method: "post", data: { assignments }, headers: { repeatSubmit: false }, }); } export function checkMarketOpportunitySelectionStepOne(items) { const expected = { "search-count": "市场规模", "demand-supply-ratio": "竞争强度", "transaction-amount": "市场规模", "transaction-growth": "市场潜力", "category-transaction-growth": "市场潜力", "online-products": "竞争强度", "online-merchants": "竞争强度", "organic-traffic-ratio": "运营难度", "return-rate": "运营难度", }; if (isStudentDemo()) { const list = Array.isArray(items) ? items : []; const incorrect = list.find((item) => expected[item?.id] !== item?.category); if (list.length !== Object.keys(expected).length || incorrect) return Promise.reject(new Error("指标分类有误,请根据提示重新选择")); return Promise.resolve({ code: 200, data: { correctCount: Object.keys(expected).length, totalCount: Object.keys(expected).length, allCorrect: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/market-opportunity-selection/step-1/check", method: "post", data: { items }, headers: { repeatSubmit: false } }); } export function checkMarketOpportunitySelectionStepTwo(payload) { const primaryWeights = Array.isArray(payload?.primaryWeights) ? payload.primaryWeights : []; const dimensionWeights = Array.isArray(payload?.dimensionWeights) ? payload.dimensionWeights : []; const groups = ["市场规模", "市场潜力", "竞争强度", "运营难度"]; const expectedIds = ["search-count", "demand-supply-ratio", "transaction-amount", "transaction-growth", "category-transaction-growth", "online-products", "online-merchants", "organic-traffic-ratio", "return-rate"]; if (isStudentDemo()) { const primaryMap = Object.fromEntries(primaryWeights.map((item) => [item?.group, Number(item?.weight)])); const dimensionMap = Object.fromEntries(dimensionWeights.map((item) => [item?.id, Number(item?.weight)])); const groupsByIndicator = { "search-count": "市场规模", "transaction-amount": "市场规模", "transaction-growth": "市场潜力", "category-transaction-growth": "市场潜力", "demand-supply-ratio": "竞争强度", "online-products": "竞争强度", "online-merchants": "竞争强度", "organic-traffic-ratio": "运营难度", "return-rate": "运营难度" }; const isWeight = (value) => Number.isInteger(value) && value >= 1 && value <= 100; const validPrimary = primaryWeights.length === groups.length && groups.every((group) => isWeight(primaryMap[group])) && groups.reduce((sum, group) => sum + primaryMap[group], 0) === 100; const validDimensions = dimensionWeights.length === expectedIds.length && expectedIds.every((id) => isWeight(dimensionMap[id])) && groups.every((group) => expectedIds.filter((id) => groupsByIndicator[id] === group).reduce((sum, id) => sum + dimensionMap[id], 0) === 100); if (!validPrimary || !validDimensions) { return Promise.reject(new Error("一级指标权重合计和各类维度内权重合计均须为 100%")); } return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/market-opportunity-selection/step-2/validate", method: "post", data: { primaryWeights, dimensionWeights }, headers: { repeatSubmit: false } }); } export function checkMarketOpportunitySelectionStepThree(payload) { const scores = Array.isArray(payload?.scores) ? payload.scores : []; const expectedIds = ["smart-fitness-mirror", "smart-bathroom-mirror", "smart-beauty-mirror"]; if (isStudentDemo()) { const ids = new Set(scores.map((item) => item?.id)); const isScore = (value) => Number.isFinite(Number(value)) && Number(value) >= 0 && Number(value) <= 100 && Math.round(Number(value) * 100) === Number(value) * 100; if (scores.length !== expectedIds.length || expectedIds.some((id) => !ids.has(id)) || scores.some((item) => !isScore(item?.score))) { return Promise.reject(new Error("请为三类产品填写 0 至 100 分、最多保留两位小数的综合得分")); } return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/market-opportunity-selection/step-3/validate", method: "post", data: { scores }, headers: { repeatSubmit: false } }); } export function checkMarketOpportunitySelectionStepFour(payload) { const visualizationUrl = String(payload?.visualizationUrl || "").trim(); if (isStudentDemo()) { if (!visualizationUrl) return Promise.reject(new Error("请上传数据可视化结果图")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/market-opportunity-selection/step-4/validate", method: "post", data: { visualizationUrl }, headers: { repeatSubmit: false } }); } export function checkMarketOpportunitySelectionStepFive(payload) { const insights = Array.isArray(payload?.insights) ? payload.insights : []; const expectedIds = ["smart-fitness-mirror", "smart-bathroom-mirror", "smart-beauty-mirror"]; const isRate = (value) => Number.isFinite(Number(value)) && Number(value) >= 0 && Number(value) <= 100 && Math.round(Number(value) * 100) === Number(value) * 100; const isFilled = (value) => String(value || "").trim().length > 0; if (isStudentDemo()) { const ids = new Set(insights.map((item) => item?.productId)); if (insights.length !== expectedIds.length || expectedIds.some((id) => !ids.has(id)) || insights.some((item) => !isRate(item?.favorableRate) || !isFilled(item?.painPoint) || !isFilled(item?.unmetNeed))) { return Promise.reject(new Error("请完整填写三类产品的好评率、用户痛点和未被满足的需求;好评率为 0 至 100,最多保留两位小数")); } return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/market-opportunity-selection/step-5/validate", method: "post", data: { insights }, headers: { repeatSubmit: false } }); } export function checkMarketOpportunitySelectionStepSix(payload) { const developmentConclusion = String(payload?.developmentConclusion || "").trim(); if (isStudentDemo()) { if (!developmentConclusion) return Promise.reject(new Error("请填写建议开发的产品及具体原因")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/market-opportunity-selection/step-6/validate", method: "post", data: { developmentConclusion }, headers: { repeatSubmit: false } }); } export function checkProductDevelopmentProcessStepOne(items) { if (isStudentDemo()) { const results = Array.isArray(items) ? items.map((item) => { const correct = productDevelopmentProcessStepOneAnswers[item?.taskNo] === item?.classification; return { taskNo: item?.taskNo, correct, message: correct ? "分类正确" : "分类有误,请重新选择" }; }) : []; const correctCount = results.filter((item) => item.correct).length; return Promise.resolve({ code: 200, data: { items: results, correctCount, totalCount: results.length, allCorrect: results.length === Object.keys(productDevelopmentProcessStepOneAnswers).length && correctCount === results.length, }, }); } return request({ url: "/api/student-training-answers/product-development-process/step-1/check", method: "post", data: { items }, headers: { repeatSubmit: false }, }); } export function checkProductArchitectureDesignStepOne(layerIds) { const expected = ["hardware-physical", "driver-sensing", "algorithm-processing", "application-function"]; const items = Array.isArray(layerIds) ? layerIds : []; if (isStudentDemo()) { const correctPositionCount = expected.reduce((total, id, index) => total + (items[index] === id ? 1 : 0), 0); return Promise.resolve({ code: 200, data: { correctPositionCount, totalCount: expected.length, allCorrect: items.length === expected.length && correctPositionCount === expected.length, message: correctPositionCount === expected.length ? "排序正确,已解锁下一步。" : "排序尚未正确,请继续调整。", }, }); } return request({ url: "/api/student-training-answers/product-structure/step-1/check", method: "post", data: { layerIds: items }, headers: { repeatSubmit: false }, skipErrorMessage: true, }); } export function checkAfterSalesServiceStepOne(items) { const expected = { "voice-wake-no-response": "local-hardware-simple", "voice-control-cannot-switch": "local-hardware-simple", "app-cannot-find-device": "operation-network", "network-setup-timeout": "operation-network", "remote-control-intermittent": "cloud-connection", "frequent-disconnection": "cloud-connection", "indicator-no-response": "severe-fault", "water-damage-no-response": "severe-fault", }; const rows = Array.isArray(items) ? items : []; if (isStudentDemo()) { const correctCount = rows.filter((item) => expected[item?.faultId] === item?.categoryId).length; return Promise.resolve({ code: 200, data: { correctCount, totalCount: Object.keys(expected).length, allCorrect: rows.length === Object.keys(expected).length && correctCount === Object.keys(expected).length, message: correctCount === Object.keys(expected).length ? "分类正确,已解锁下一步。" : "分类尚未完全正确,请继续调整。", }, }); } return request({ url: "/api/student-training-answers/after-sales-service/step-1/check", method: "post", data: { items: rows }, headers: { repeatSubmit: false }, }); } export function checkAfterSalesServiceStepTwo(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { const expectedIds = ["operation-network", "cloud-connection", "local-hardware-simple", "severe-fault", "out-of-warranty"]; const valid = rows.length === expectedIds.length && rows.every((row, index) => row?.id === expectedIds[index] && Boolean(String(row?.process || "").trim()) && Boolean(String(row?.rule || "").trim())); return Promise.resolve({ code: 200, data: { valid, message: valid ? "服务分级与处理方案填写完整。" : "请完成五类故障的处理流程和核心规则。" }, }); } return request({ url: "/api/student-training-answers/after-sales-service/step-2/validate", method: "post", data: { rows }, headers: { repeatSubmit: false }, }); } export function checkAfterSalesServiceStepThree(payload) { const policies = Array.isArray(payload?.policies) ? payload.policies : []; const summaries = Array.isArray(payload?.summaries) ? payload.summaries : []; const policyIds = ["return-policy", "warranty-policy", "repair-service", "response-sla", "compensation-plan"]; const summaryIds = ["warranty-period", "response-commitment", "featured-service"]; if (isStudentDemo()) { const valid = policies.length === policyIds.length && policies.every((row, index) => row?.id === policyIds[index] && Boolean(String(row?.content || "").trim()) && Boolean(String(row?.condition || "").trim())) && summaries.length === summaryIds.length && summaries.every((row, index) => row?.id === summaryIds[index] && Boolean(String(row?.content || "").trim())); return Promise.resolve({ code: 200, data: { valid, message: valid ? "售后政策框架与详情页摘要填写完整。" : "请完成五类售后政策及三项详情页售后信息。" }, }); } return request({ url: "/api/student-training-answers/after-sales-service/step-3/validate", method: "post", data: { policies, summaries }, headers: { repeatSubmit: false }, }); } export function checkProductArchitectureDesignStepTwo(actionIds) { const expected = ["user-action", "hardware-senses", "driver-transmits", "algorithm-processes", "application-triggers", "driver-outputs", "hardware-effects"]; const items = Array.isArray(actionIds) ? actionIds : []; if (isStudentDemo()) { const correctPositionCount = expected.reduce((total, id, index) => total + (items[index] === id ? 1 : 0), 0); return Promise.resolve({ code: 200, data: { correctPositionCount, totalCount: expected.length, allCorrect: items.length === expected.length && correctPositionCount === expected.length, message: correctPositionCount === expected.length ? "排序正确,已解锁下一步。" : "排序尚未正确,请继续调整。", }, }); } return request({ url: "/api/student-training-answers/product-structure/step-2/check", method: "post", data: { actionIds: items }, headers: { repeatSubmit: false }, skipErrorMessage: true, }); } export function checkProductArchitectureDesignStepThree(rows) { const items = Array.isArray(rows) ? rows : []; const requiredFields = ["userScenario", "hardwareSense", "driverTransmit", "algorithmProcess", "applicationTrigger", "driverOutput", "hardwareEffect"]; if (isStudentDemo()) { const valid = items.length >= 3 && items.length <= 10 && items.every((row) => requiredFields.every((field) => Boolean(String(row?.[field] || "").trim()))); return Promise.resolve({ code: 200, data: { valid, message: valid ? "场景架构分析填写完整。" : "请至少完整填写 3 个典型工作场景。" }, }); } return request({ url: "/api/student-training-answers/product-structure/step-3/validate", method: "post", data: { rows: items }, headers: { repeatSubmit: false }, skipErrorMessage: true, }); } export function checkProductArchitectureDesignStepFour(images) { const files = Array.isArray(images) ? images : []; if (isStudentDemo()) { const valid = files.length === 3 && files.every((item) => typeof item === "string" && item.trim()); return Promise.resolve({ code: 200, data: { valid, message: valid ? "三张核心功能架构图已提交。" : "请上传三张核心功能架构图。" }, }); } return request({ url: "/api/student-training-answers/product-structure/step-4/validate", method: "post", data: { images: files }, headers: { repeatSubmit: false }, skipErrorMessage: true, }); } export function checkProductDevelopmentProcessStepTwo(items) { if (isStudentDemo()) { const rows = Array.isArray(items) ? items : []; const incomplete = rows.length !== Object.keys(productDevelopmentProcessStepOneAnswers).length || rows.find((row) => { const startDate = String(row?.startDate || "").trim(); const endDate = String(row?.endDate || "").trim(); return !startDate || !endDate || !String(row?.dependency || "").trim() || startDate > endDate || productDevelopmentProcessStepOneAnswers[row?.taskNo] !== row?.classification; }); if (incomplete) return Promise.reject(new Error("请完整填写任务工期和项目依赖关系,并先完成第一步关键任务判断")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/product-development-process/step-2/validate", method: "post", data: { items }, headers: { repeatSubmit: false }, skipErrorMessage: true, }); } export function checkProductDevelopmentProcessStepThree(payload) { if (isStudentDemo()) { const value = payload || {}; if (!String(value.ganttImageUrl || "").trim() || !/\.(png|jpe?g|webp|gif)$/i.test(String(value.ganttImageName || ""))) { return Promise.reject(new Error("请上传甘特图图片(PNG、JPG、JPEG、WEBP 或 GIF)")); } return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/product-development-process/step-3/validate", method: "post", data: payload, headers: { repeatSubmit: false }, }); } export function checkProductDevelopmentProcessStepFour(payload) { if (isStudentDemo()) { const value = payload || {}; if (!String(value.parallelWorkAnalysis || "").trim()) return Promise.reject(new Error("请填写可提前介入并同步完成的电商运营工作")); if (!String(value.aiScenarioOne || "").trim() || !String(value.aiScenarioTwo || "").trim()) { return Promise.reject(new Error("请至少填写 2 个 AI 工具提效应用场景")); } return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/product-development-process/step-4/validate", method: "post", data: payload, headers: { repeatSubmit: false }, }); } function validateNewProductSurveyStepTwoDemo(form) { const value = form || {}; const images = Array.isArray(value.images) ? value.images : []; if (!['京东', '天猫', '抖音电商'].includes(String(value.platform || '').trim())) { throw new Error('请选择调查平台'); } if (!String(value.scene || '').trim()) { throw new Error('请选择调查场景'); } if (value.scene === '其他场景' && !String(value.customScene || '').trim()) { throw new Error('请填写自定义调查场景'); } if (!String(value.keyword || '').trim()) { throw new Error('请填写搜索关键词'); } if (!String(value.productName || '').trim()) { throw new Error('请填写产品名称'); } if (images.length < 1 || images.length > 5 || images.some((image) => !String(image?.url || '').trim())) { throw new Error('请上传 1 至 5 张产品图片'); } return { valid: true, message: '校验通过' }; } export function checkNewProductSurveyStepTwo(form) { if (isStudentDemo()) { try { return Promise.resolve({ code: 200, data: validateNewProductSurveyStepTwoDemo(form) }); } catch (error) { return Promise.reject(error); } } return request({ url: '/api/student-training-answers/new-product-survey/step-2/validate', method: 'post', data: { ...form, imageUrls: (form?.images || []).map((image) => image?.url) }, headers: { repeatSubmit: false }, }); } function validateNewProductSurveyStepThreeDemo(payload) { const expectedDimensions = ["核心层", "有形层", "延伸层"]; const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (rows.length !== expectedDimensions.length) { throw new Error("请完成核心层、有形层、延伸层分析"); } const dimensions = rows.map((row) => String(row?.dimension || "").trim()); if (new Set(dimensions).size !== expectedDimensions.length || expectedDimensions.some((dimension) => !dimensions.includes(dimension))) { throw new Error("分析维度不完整或重复"); } const invalidRow = rows.find((row) => { const points = Array.isArray(row?.points) ? row.points : []; return points.map((point) => String(point || "").trim()).filter(Boolean).length < 2; }); if (invalidRow) { throw new Error(`${invalidRow.dimension || "该层"}至少填写 2 条分析要点`); } return { valid: true, message: "校验通过" }; } export function checkNewProductSurveyStepThree(payload) { if (isStudentDemo()) { try { return Promise.resolve({ code: 200, data: validateNewProductSurveyStepThreeDemo(payload) }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/new-product-survey/step-3/validate", method: "post", data: payload, headers: { repeatSubmit: false }, }); } function validateNewProductSurveyStepFourDemo(form) { const value = form || {}; const fields = [ ["coreValue", "核心层"], ["tangibleValue", "有形层"], ["extendedValue", "延伸层"], ]; for (const [field, label] of fields) { if (!String(value[field] || "").trim()) { throw new Error(`请填写${label}AI应用价值分析`); } } return { valid: true, message: "校验通过" }; } export function checkNewProductSurveyStepFour(form) { if (isStudentDemo()) { try { return Promise.resolve({ code: 200, data: validateNewProductSurveyStepFourDemo(form) }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/new-product-survey/step-4/validate", method: "post", data: form, headers: { repeatSubmit: false }, }); } const productDevelopmentFactorsStepOneDimensions = ["用户需求真实性", "产品体验闭环", "生态与兼容"]; function validateProductDevelopmentFactorsStepOneDemo(payload) { const factors = Array.isArray(payload?.factors) ? payload.factors : []; if (factors.length !== productDevelopmentFactorsStepOneDimensions.length) { throw new Error("请完成用户需求真实性、产品体验闭环和生态与兼容分析"); } const dimensions = factors.map((factor) => String(factor?.dimension || "").trim()); if (new Set(dimensions).size !== productDevelopmentFactorsStepOneDimensions.length || productDevelopmentFactorsStepOneDimensions.some((dimension) => !dimensions.includes(dimension))) { throw new Error("提交的分析维度不完整或重复"); } const incompleteFactor = factors.find((factor) => !String(factor?.successAnalysis || "").trim() || !String(factor?.failedAnalysis || "").trim()); if (incompleteFactor) { throw new Error(`请完成${incompleteFactor.dimension || "该项"}的成功产品和失败产品分析`); } return { valid: true, message: "校验通过" }; } export function checkProductDevelopmentFactorsStepOne(payload) { if (isStudentDemo()) { try { return Promise.resolve({ code: 200, data: validateProductDevelopmentFactorsStepOneDemo(payload) }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/product-development-factors/step-1/validate", method: "post", data: payload, headers: { repeatSubmit: false }, }); } const productDevelopmentFactorsStepTwoDimensions = ["技术应用", "合规与认证"]; function validateProductDevelopmentFactorsStepTwoDemo(payload) { const factors = Array.isArray(payload?.factors) ? payload.factors : []; const dimensions = factors.map((factor) => String(factor?.dimension || "").trim()); if (factors.length !== 2 || new Set(dimensions).size !== 2 || productDevelopmentFactorsStepTwoDimensions.some((dimension) => !dimensions.includes(dimension))) throw new Error("请完成技术应用和合规与认证分析"); const incomplete = factors.find((factor) => !String(factor?.successAnalysis || "").trim() || !String(factor?.failedAnalysis || "").trim()); if (incomplete) throw new Error(`请完成${incomplete.dimension || "该项"}的成功产品和失败产品分析`); return { valid: true, message: "校验通过" }; } export function checkProductDevelopmentFactorsStepTwo(payload) { if (isStudentDemo()) { try { return Promise.resolve({ code: 200, data: validateProductDevelopmentFactorsStepTwoDemo(payload) }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/product-development-factors/step-2/validate", method: "post", data: payload, headers: { repeatSubmit: false } }); } const productDevelopmentFactorsStepThreeDimensions = ["供应链与成本控制", "商业模式", "市场反馈"]; function validateProductDevelopmentFactorsStepThreeDemo(payload) { const factors = Array.isArray(payload?.factors) ? payload.factors : []; const dimensions = factors.map((factor) => String(factor?.dimension || "").trim()); if (factors.length !== productDevelopmentFactorsStepThreeDimensions.length || new Set(dimensions).size !== productDevelopmentFactorsStepThreeDimensions.length || productDevelopmentFactorsStepThreeDimensions.some((dimension) => !dimensions.includes(dimension))) { throw new Error("请完成供应链与成本控制、商业模式和市场反馈分析"); } const incomplete = factors.find((factor) => !String(factor?.successAnalysis || "").trim() || !String(factor?.failedAnalysis || "").trim()); if (incomplete) throw new Error(`请完成${incomplete.dimension || "该项"}的成功产品和失败产品分析`); return { valid: true, message: "校验通过" }; } export function checkProductDevelopmentFactorsStepThree(payload) { if (isStudentDemo()) { try { return Promise.resolve({ code: 200, data: validateProductDevelopmentFactorsStepThreeDemo(payload) }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/product-development-factors/step-3/validate", method: "post", data: payload, headers: { repeatSubmit: false } }); } export function checkProductDevelopmentFactorsStepFour(payload) { const value = payload || {}; if (isStudentDemo()) { if (!String(value.successFactors || "").trim()) return Promise.reject(new Error("请填写产品成功的关键因素")); if (!String(value.failedFactors || "").trim()) return Promise.reject(new Error("请填写产品失败的关键因素")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/product-development-factors/step-4/validate", method: "post", data: payload, headers: { repeatSubmit: false } }); } export function checkConsumerBehaviorStepOne(payload) { const hypotheses = Array.isArray(payload?.hypotheses) ? payload.hypotheses : []; if (isStudentDemo()) { const incomplete = hypotheses.find((item) => ["targetGroup", "keyFinding", "coreHypothesis"].some((field) => !String(item?.[field] || "").trim())); if (!hypotheses.length || incomplete) { return Promise.reject(new Error("请完整填写目标人群、实训8关键发现和问卷核心假设")); } return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/consumer-behavior/step-1/validate", method: "post", data: { hypotheses }, headers: { repeatSubmit: false }, }); } export function checkConsumerBehaviorStepTwo(payload) { const needs = Array.isArray(payload?.needs) ? payload.needs : []; const allowedTypes = ["基本型", "期望型", "兴奋型", "无差异型", "反向型"]; if (isStudentDemo()) { const incomplete = needs.find((item) => !String(item?.feature || "").trim() || !allowedTypes.includes(item?.kanoType) || !String(item?.featureDirection || "").trim()); if (!needs.length || needs.length > 20 || incomplete) { return Promise.reject(new Error("请完整填写功能、KANO类型和对应功能方向(最多20行)")); } return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/consumer-behavior/step-2/validate", method: "post", data: { needs }, headers: { repeatSubmit: false }, }); } export function checkConsumerBehaviorStepThree(payload) { if (isStudentDemo()) { const importedDocumentUrl = String(payload?.importedDocumentUrl || "").trim(); const questions = Array.isArray(payload?.questions) ? payload.questions : []; const requiredSections = ["基本信息", "购买行为", "需求偏好", "开放建议"]; const validQuestions = questions.filter((item) => String(item?.title || "").trim()); const complete = requiredSections.every((section) => validQuestions.some((item) => item.section === section)) && validQuestions.some((item) => item.section === "需求偏好" && item.type === "LIKERT"); if (!importedDocumentUrl && !complete) { return Promise.reject(new Error("请完整设计包含四个部分且含Likert五级量表的问卷,或导入Word问卷")); } return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/consumer-behavior/step-3/validate", method: "post", data: payload, headers: { repeatSubmit: false }, }); } export function previewConsumerBehaviorQuestionnaireWord(data) { if (isStudentDemo()) { return Promise.reject(new Error("演示模式不支持 Word 文档预览")); } return request({ url: "/api/student-training-answers/consumer-behavior/step-3/word-preview", method: "post", data, headers: { "Content-Type": "multipart/form-data", repeatSubmit: false, }, }); } export function previewRequirementsDocumentFile(data) { if (isStudentDemo()) { return Promise.resolve({ code: 200, data: { fileType: "DEMO", content: "演示模式下仅展示已选择文件的信息;登录学生账号后可解析并在线预览 Word 或 PDF 的正文内容。", }, }); } return request({ url: "/api/student-training-answers/requirements-doc/step-3/document-preview", method: "post", data, headers: { "Content-Type": "multipart/form-data", repeatSubmit: false, }, }); } export function checkConsumerBehaviorStepFour(payload) { if (isStudentDemo()) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (rows.length < 5 || rows.some((item) => !String(item?.title || "").trim() || !String(item?.content || "").trim())) { return Promise.reject(new Error("请完整填写 5 个问卷调研报告模块的标题和内容")); } return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/consumer-behavior/step-4/validate", method: "post", data: payload, headers: { repeatSubmit: false }, }); } export function checkProductValuePositioningStepOne(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; const types = ["核心痛点1", "核心痛点2", "核心痛点3", "弱需求", "伪需求"]; if (isStudentDemo()) { const valid = rows.length === types.length && rows.every((row, index) => row?.id === index + 1 && row?.type === types[index] && Boolean(String(row?.description || "").trim()) && Boolean(String(row?.scene || "").trim())); return Promise.resolve({ code: 200, data: { valid, message: valid ? "核心痛点分类填写完整。" : "请完成 3 个核心痛点、弱需求和伪需求的描述与场景。", }, }); } return request({ url: "/api/student-training-answers/product-value-positioning/step-1/validate", method: "post", data: { rows }, headers: { repeatSubmit: false }, }); } export function checkProductValuePositioningStepTwo(payload) { const customerProfile = payload?.customerProfile || {}; const valueMap = payload?.valueMap || {}; if (isStudentDemo()) { const requiredValues = [ customerProfile.jobs, customerProfile.pains, customerProfile.gains, valueMap.products, valueMap.painRelievers, valueMap.gainCreators, payload?.canvasUrl, ]; const valid = requiredValues.every((item) => Boolean(String(item || "").trim())); return Promise.resolve({ code: 200, data: { valid, message: valid ? "价值主张画布填写完整。" : "请完整填写用户档案、产品价值地图并上传产品价值主张画布。", }, }); } return request({ url: "/api/student-training-answers/product-value-positioning/step-2/validate", method: "post", data: payload, headers: { repeatSubmit: false }, }); } export function checkProductValuePositioningStepThree(payload) { const statements = Array.isArray(payload?.statements) ? payload.statements : []; const requiredFields = ["targetUser", "productName", "productCategory", "coreValue", "differentiationReason"]; if (isStudentDemo()) { const valid = statements.length === 2 && statements.every((statement, index) => statement?.id === index + 1 && requiredFields.every((field) => Boolean(String(statement?.[field] || "").trim()))); return Promise.resolve({ code: 200, data: { valid, message: valid ? "两版定位陈述填写完整。" : "请完整填写两个产品价值定位陈述版本。" }, }); } return request({ url: "/api/student-training-answers/product-value-positioning/step-3/validate", method: "post", data: { statements }, headers: { repeatSubmit: false }, }); } export function checkProductValuePositioningStepFour(payload) { const sectionKeys = ["marketInsights", "marketOverview", "userAnalysis", "competitorAnalysis", "productRequirements"]; if (isStudentDemo()) { const valid = sectionKeys.every((key) => Array.isArray(payload?.[key]) && payload[key].length > 0 && payload[key].every((row) => Boolean(String(row?.content || "").trim()))) && Boolean(String(payload?.productPositioning || "").trim()); return Promise.resolve({ code: 200, data: { valid, message: valid ? "MRD 文档填写完整。" : "请完整填写 MRD 的五组分析内容和产品定位。" }, }); } return request({ url: "/api/student-training-answers/product-value-positioning/step-4/validate", method: "post", data: payload, headers: { repeatSubmit: false }, }); } export function validateHardwareCostStepOne(payload) { const items = Array.isArray(payload?.items) ? payload.items : []; if (isStudentDemo()) { const normalizedItems = items.map((item) => { const unitPrice = Number(item?.unitPrice); const quantity = Number(item?.quantity); return { materialName: String(item?.materialName || "").trim(), specification: String(item?.specification || "").trim(), unitPrice, quantity, amount: Number.isFinite(unitPrice) && Number.isFinite(quantity) ? Number((unitPrice * quantity).toFixed(2)) : 0, }; }); const valid = normalizedItems.length >= 10 && normalizedItems.length <= 40 && normalizedItems.every((item) => item.materialName && item.specification && Number.isFinite(item.unitPrice) && item.unitPrice >= 0 && Number.isInteger(item.quantity) && item.quantity > 0); if (!valid) { return Promise.reject(new Error("请完整填写 10 至 40 行 BOM 物料明细,单价不能为负且数量必须大于 0")); } return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过", items: normalizedItems, totalAmount: Number(normalizedItems.reduce((total, item) => total + item.amount, 0).toFixed(2)), }, }); } return request({ url: "/api/student-training-answers/hardware-cost/step-1/validate", method: "post", data: { items }, headers: { repeatSubmit: false }, }); } export function validateHardwareCostStepTwo(payload) { const value = payload || {}; const items = Array.isArray(value.items) ? value.items : []; if (isStudentDemo()) { const smallBatchConstraint = Number(value.smallBatchConstraint); const largeBatchConstraint = Number(value.largeBatchConstraint); const normalizedItems = items.map((item) => ({ materialName: String(item?.materialName || "").trim(), smallBatchUnitCost: Number(item?.smallBatchUnitCost), largeBatchUnitCost: Number(item?.largeBatchUnitCost), })); const validRows = normalizedItems.length > 0 && normalizedItems.every((item) => item.materialName && Number.isFinite(item.smallBatchUnitCost) && item.smallBatchUnitCost >= 0 && Number.isFinite(item.largeBatchUnitCost) && item.largeBatchUnitCost >= 0); if (!validRows || !Number.isFinite(smallBatchConstraint) || smallBatchConstraint < 0 || !Number.isFinite(largeBatchConstraint) || largeBatchConstraint < 0) { return Promise.reject(new Error("请完整填写两种批量的物料成本和成本约束")); } const smallBatchTotal = Number(normalizedItems.reduce((sum, item) => sum + item.smallBatchUnitCost, 0).toFixed(2)); const largeBatchTotal = Number(normalizedItems.reduce((sum, item) => sum + item.largeBatchUnitCost, 0).toFixed(2)); const smallBatchWithinConstraint = smallBatchTotal <= smallBatchConstraint; const largeBatchWithinConstraint = largeBatchTotal <= largeBatchConstraint; return Promise.resolve({ code: 200, data: { valid: smallBatchWithinConstraint && largeBatchWithinConstraint, message: smallBatchWithinConstraint && largeBatchWithinConstraint ? "两种批量的 BOM 成本均满足约束" : "合计成本超出填写的成本约束", items: normalizedItems, smallBatchConstraint: Number(smallBatchConstraint.toFixed(2)), largeBatchConstraint: Number(largeBatchConstraint.toFixed(2)), smallBatchTotal, largeBatchTotal, smallBatchWithinConstraint, largeBatchWithinConstraint, }, }); } return request({ url: "/api/student-training-answers/hardware-cost/step-2/validate", method: "post", data: value, headers: { repeatSubmit: false }, }); } export function validateHardwareCostStepThree(payload) { const value = payload || {}; const items = Array.isArray(value.items) ? value.items : []; if (isStudentDemo()) { if (items.length < 6 || items.length > 15) { return Promise.reject(new Error("请填写 6 至 15 项制造费用。")); } const normalizedItems = items.map((item, index) => { const expenseName = String(item?.expenseName || "").trim(); const smallBatchUnitCost = Number(item?.smallBatchUnitCost); const largeBatchUnitCost = Number(item?.largeBatchUnitCost); if (!expenseName || !Number.isFinite(smallBatchUnitCost) || !Number.isFinite(largeBatchUnitCost) || smallBatchUnitCost < 0 || largeBatchUnitCost < 0) { throw new Error("请完整填写第 " + (index + 1) + " 项制造费用及两种批量成本。"); } return { expenseName, smallBatchUnitCost: Number(smallBatchUnitCost.toFixed(2)), largeBatchUnitCost: Number(largeBatchUnitCost.toFixed(2)), }; }); const saved = demoResult("hardware-cost"); const stepTwo = typeof saved?.step2Answer === "string" ? JSON.parse(saved.step2Answer) : saved?.step2Answer; const smallBatchBomCost = Number(stepTwo?.smallBatchTotal); const largeBatchBomCost = Number(stepTwo?.largeBatchTotal); if (!Number.isFinite(smallBatchBomCost) || !Number.isFinite(largeBatchBomCost)) { return Promise.reject(new Error("请先完成并保存第 2 步物料 BOM 成本核算。")); } const smallBatchManufacturingCost = Number(normalizedItems.reduce((sum, item) => sum + item.smallBatchUnitCost, 0).toFixed(2)); const largeBatchManufacturingCost = Number(normalizedItems.reduce((sum, item) => sum + item.largeBatchUnitCost, 0).toFixed(2)); return Promise.resolve({ code: 200, data: { valid: true, message: "制造成本与单件总成本已核算完成。", items: normalizedItems, smallBatchManufacturingCost, largeBatchManufacturingCost, smallBatchBomCost: Number(smallBatchBomCost.toFixed(2)), largeBatchBomCost: Number(largeBatchBomCost.toFixed(2)), smallBatchUnitTotalCost: Number((smallBatchManufacturingCost + smallBatchBomCost).toFixed(2)), largeBatchUnitTotalCost: Number((largeBatchManufacturingCost + largeBatchBomCost).toFixed(2)), }, }); } return request({ url: "/api/student-training-answers/hardware-cost/step-3/validate", method: "post", data: value, headers: { repeatSubmit: false }, skipErrorMessage: true, }); } export function validateHardwareCostStepFour(payload) { const value = payload || {}; if (isStudentDemo()) { const smallBatchChartUrl = String(value.smallBatchChartUrl || "").trim(); const largeBatchChartUrl = String(value.largeBatchChartUrl || "").trim(); const optimizationPlan = String(value.optimizationPlan || "").trim(); if (!smallBatchChartUrl || !largeBatchChartUrl) { return Promise.reject(new Error("请分别上传小批量试产和大批量量产成本结构图")); } if (!optimizationPlan) return Promise.reject(new Error("请填写成本优化决策")); if (optimizationPlan.length > 3000) return Promise.reject(new Error("成本优化决策不能超过 3000 字")); return Promise.resolve({ code: 200, data: { valid: true, message: "成本结构图与优化决策校验通过" } }); } return request({ url: "/api/student-training-answers/hardware-cost/step-4/validate", method: "post", data: value, headers: { repeatSubmit: false }, }); } export function validateSupplierEvaluationStepOne(payload) { const items = Array.isArray(payload?.items) ? payload.items : []; if (isStudentDemo()) { const expectedDimensions = ["采购成本", "交货周期", "品质可靠性", "采购灵活性", "服务支持"]; const normalizedItems = items.map((item) => ({ dimension: String(item?.dimension || "").trim(), smallBatchWeight: Number(item?.smallBatchWeight), largeBatchWeight: Number(item?.largeBatchWeight), })); const hasAllDimensions = normalizedItems.length === expectedDimensions.length && normalizedItems.every((item, index) => item.dimension === expectedDimensions[index]); const validNumbers = normalizedItems.every((item) => Number.isFinite(item.smallBatchWeight) && Number.isFinite(item.largeBatchWeight) && item.smallBatchWeight >= 0 && item.smallBatchWeight <= 100 && item.largeBatchWeight >= 0 && item.largeBatchWeight <= 100); const smallBatchTotal = Number(normalizedItems.reduce((sum, item) => sum + item.smallBatchWeight, 0).toFixed(2)); const largeBatchTotal = Number(normalizedItems.reduce((sum, item) => sum + item.largeBatchWeight, 0).toFixed(2)); if (!hasAllDimensions || !validNumbers || smallBatchTotal !== 100 || largeBatchTotal !== 100) { return Promise.reject(new Error("请完整填写 5 个评估维度,且小批量试产与大批量量产的权重合计均为 100%")); } return Promise.resolve({ code: 200, data: { valid: true, message: "双场景权重校验通过", items: normalizedItems, smallBatchTotal, largeBatchTotal }, }); } return request({ url: "/api/student-training-answers/supplier-evaluation/step-1/validate", method: "post", data: { items }, headers: { repeatSubmit: false }, }); } export function validateSupplierEvaluationStepTwo(payload) { const value = payload || {}; const expectedDimensions = ["采购成本", "交货周期", "品质可靠性", "采购灵活性", "服务支持"]; if (isStudentDemo()) { try { const saved = demoResult("supplier-evaluation"); const stepOne = typeof saved?.step1Answer === "string" ? JSON.parse(saved.step1Answer) : saved?.step1Answer; const weights = Array.isArray(stepOne?.items) ? stepOne.items : []; if (weights.length !== expectedDimensions.length || weights.some((item, index) => item?.dimension !== expectedDimensions[index])) { throw new Error("请先完成并保存第 1 步的供应渠道权重设置"); } const normalize = (rows, isSmallBatch, expectedChannels) => { if (!Array.isArray(rows) || rows.length !== 5) throw new Error("试产和量产场景均需填写 5 个供应渠道"); return rows.map((row, rowIndex) => { const channel = String(row?.channel || "").trim(); if (!channel) throw new Error(`请填写第 ${rowIndex + 1} 行供应渠道名称`); if (expectedChannels && channel !== expectedChannels[rowIndex]) { throw new Error("量产场景的供应渠道必须与试产场景保持一致"); } const scores = Array.isArray(row?.scores) ? row.scores : []; if (scores.length !== expectedDimensions.length) throw new Error(`请完整填写“${channel}”的 5 个维度评分`); const normalizedScores = scores.map((score, index) => { const numeric = Number(score?.score); if (score?.dimension !== expectedDimensions[index] || !Number.isInteger(numeric) || numeric < 0 || numeric > 10) { throw new Error(`请为“${channel}”填写 0 到 10 的整数评分:${expectedDimensions[index]}`); } return { dimension: expectedDimensions[index], score: numeric }; }); const weightedTotal = Number(normalizedScores.reduce((sum, score, index) => { const weight = Number(isSmallBatch ? weights[index]?.smallBatchWeight : weights[index]?.largeBatchWeight); return sum + score.score * weight / 100; }, 0).toFixed(2)); return { channel, scores: normalizedScores, weightedTotal }; }); }; const smallBatchChannels = normalize(value.smallBatchChannels, true); const largeBatchChannels = normalize(value.largeBatchChannels, false, smallBatchChannels.map((item) => item.channel)); return Promise.resolve({ code: 200, data: { valid: true, message: "两种采购场景的供应渠道加权得分已重新核算。", weights, smallBatchChannels, largeBatchChannels } }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/supplier-evaluation/step-2/validate", method: "post", data: value, headers: { repeatSubmit: false }, }); } export function validateSupplierEvaluationStepThree(payload) { if (isStudentDemo()) { try { const value = payload || {}; const saved = demoResult("supplier-evaluation"); const stepTwo = typeof saved?.step2Answer === "string" ? JSON.parse(saved.step2Answer || "{}") : (saved?.step2Answer || {}); const scenarioFields = { smallBatch: "smallBatchChannels", largeBatch: "largeBatchChannels", }; const suppliedAnalyses = Array.isArray(value.scenarioAnalyses) ? value.scenarioAnalyses : []; const scenarioAnalyses = Object.keys(scenarioFields).map((scenario) => { const analysis = suppliedAnalyses.find((item) => item?.scenario === scenario); if (!analysis || !String(analysis.differenceAnalysis || "").trim()) { throw new Error("请填写两种采购场景的分差与分析"); } const ranked = (Array.isArray(stepTwo[scenarioFields[scenario]]) ? stepTwo[scenarioFields[scenario]] : []) .map((item) => ({ channel: String(item?.channel || "").trim(), score: Number(item?.weightedTotal), })) .filter((item) => item.channel && Number.isFinite(item.score)) .sort((left, right) => right.score - left.score || left.channel.localeCompare(right.channel, "zh-CN")); if (ranked.length < 2) { throw new Error("请先完成第二步供应渠道评分"); } if (analysis.bestChannel !== ranked[0].channel || analysis.secondChannel !== ranked[1].channel) { throw new Error("渠道排名已更新,请确认第二步评分结果后再保存"); } return { scenario, bestChannel: ranked[0].channel, secondChannel: ranked[1].channel, bestScore: ranked[0].score.toFixed(2), secondScore: ranked[1].score.toFixed(2), scoreGap: (ranked[0].score - ranked[1].score).toFixed(2), differenceAnalysis: String(analysis.differenceAnalysis).trim(), }; }); const procurementRecommendation = String(value.procurementRecommendation || "").trim(); if (!procurementRecommendation) { throw new Error("请填写综合采购建议"); } const risks = Array.isArray(value.risks) ? value.risks : []; if (risks.length < 4 || risks.length > 10) { throw new Error("供应风险需保留至少 4 条,最多 10 条"); } const normalizedRisks = risks.map((risk) => { const normalized = { dimension: String(risk?.dimension || "").trim(), description: String(risk?.description || "").trim(), level: String(risk?.level || "").trim(), measure: String(risk?.measure || "").trim(), }; if (!normalized.dimension || !normalized.description || !normalized.measure || !["高", "中", "低"].includes(normalized.level)) { throw new Error("请完整填写每条供应风险及应对措施"); } return normalized; }); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过", scenarioAnalyses, procurementRecommendation, risks: normalizedRisks, }, }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/supplier-evaluation/step-3/validate", method: "post", data: payload, headers: { repeatSubmit: false }, }); } function validateBusinessFeasibilityStepOneDemo(payload) { const value = payload || {}; const overview = value.projectOverview || {}; const requiredOverview = ["projectBackground", "productPositioning", "projectGoals", "macroEnvironmentConclusion", "competitiveLandscapeConclusion"]; if (requiredOverview.some((key) => !String(overview[key] || "").trim())) throw new Error("请完整填写项目概述"); const market = value.marketAnalysis || {}; const numericFields = ["annualMarketSize", "monthlyGrowthRate", "topBrandMarketShare", "marketConcentrationCr3"]; if (numericFields.some((key) => !Number.isFinite(Number(market[key])) || Number(market[key]) < 0) || !String(market.competitorsPriceRange || "").trim()) throw new Error("请完整填写市场规模与竞争格局分析"); const users = Array.isArray(value.targetUsers) ? value.targetUsers : []; if (users.length !== 3 || users.some((user) => !String(user?.userGroup || "").trim() || !String(user?.scenario || "").trim() || !String(user?.coreNeed || "").trim() || !String(user?.spendingPower || "").trim())) throw new Error("请完整填写 3 类目标用户群体分析"); const models = Array.isArray(value.businessModels) ? value.businessModels : []; if (models.length !== 2 || models.some((model) => !String(model?.module || "").trim() || !String(model?.strategy || "").trim() || !String(model?.explanation || "").trim())) throw new Error("请完整填写商业模式梳理"); const costIds = ["hardware-bom", "manufacturing", "rd-amortization", "channel-marketing", "logistics-warehouse"]; const costs = Array.isArray(value.costs) ? value.costs : []; if (costs.length !== costIds.length || costIds.some((id) => !costs.some((cost) => cost?.id === id && Number.isFinite(Number(cost?.amount)) && Number(cost.amount) >= 0))) throw new Error("请填写全部成本项目"); const totalCost = costs.reduce((total, cost) => total + Number(cost.amount), 0); if (totalCost <= 0 || !Number.isFinite(Number(value.targetPrice)) || Number(value.targetPrice) < totalCost) throw new Error("目标售价需不低于单位总成本,且单位总成本必须大于 0"); const riskTypes = ["技术风险", "市场风险", "供应链风险", "资金风险"]; const risks = Array.isArray(value.risks) ? value.risks : []; if (risks.length !== riskTypes.length || riskTypes.some((type) => !risks.some((risk) => risk?.type === type && String(risk?.description || "").trim() && ["高", "中", "低"].includes(risk?.impact) && String(risk?.response || "").trim()))) throw new Error("请完整填写风险描述、影响程度和应对措施"); const unitGrossProfit = Number(value.targetPrice) - totalCost; return { valid: true, message: "校验通过", unitTotalCost: totalCost.toFixed(2), unitGrossProfit: unitGrossProfit.toFixed(2), grossMargin: (unitGrossProfit / totalCost * 100).toFixed(2) }; } export function checkBusinessFeasibilityStepOne(payload) { if (isStudentDemo()) { try { return Promise.resolve({ code: 200, data: validateBusinessFeasibilityStepOneDemo(payload) }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/business-feasibility/step-1/validate", method: "post", data: payload, headers: { repeatSubmit: false } }); } export function checkBusinessFeasibilityStepTwo(payload) { const value = payload || {}; if (isStudentDemo()) { const decisions = ["highly-feasible", "feasible-after-optimization", "observe", "not-feasible"]; const priorities = ["hardware-cost-control", "content-ecosystem", "channel-expansion", "brand-differentiation", "supply-chain-management", "capital-planning"]; const selected = Array.isArray(value.priorities) ? value.priorities : []; if (!decisions.includes(value.decision)) return Promise.reject(new Error("请选择项目商业可行性总体评估")); if (selected.some((item) => !priorities.includes(item)) || new Set(selected).size !== selected.length) return Promise.reject(new Error("优先问题包含无效或重复项")); if (value.decision === "not-feasible" && selected.length) return Promise.reject(new Error("选择不可行时无需勾选优先问题")); if (value.decision !== "not-feasible" && selected.length !== 3) return Promise.reject(new Error("请且仅选择 3 个需要优先解决的关键问题")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/business-feasibility/step-2/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function checkBusinessFeasibilityStepThree(payload) { const reportUrl = String(payload?.reportUrl || "").trim(); const reportName = String(payload?.reportName || "").trim(); const supported = /\.(pdf|docx?|png|jpe?g)(?:\?|$)/i; if (isStudentDemo()) { if (!reportUrl || !supported.test(reportUrl) && !supported.test(reportName)) return Promise.reject(new Error("请上传 PDF、Word、PNG 或 JPG 格式的可行性论证报告")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/business-feasibility/step-3/validate", method: "post", data: { reportUrl, reportName }, headers: { repeatSubmit: false }, skipErrorMessage: true }); } export function checkCompetitiveEnvironmentAnalysisStepOne(payload) { const value = payload || {}; if (isStudentDemo()) { if (!["strengths", "weaknesses", "opportunities", "threats"].every((key) => String(value[key] || "").trim())) return Promise.reject(new Error("请完整填写优势、劣势、机会和威胁四类信息")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/competitive-environment-analysis/step-1/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function checkCompetitiveEnvironmentAnalysisStepTwo(payload) { const value = payload || {}; if (isStudentDemo()) { if (!["strengths", "weaknesses", "opportunities", "threats"].every((key) => String(value[key] || "").trim())) return Promise.reject(new Error("请为优势、劣势、机会和威胁各补充至少 1 条信息")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/competitive-environment-analysis/step-2/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function checkCompetitiveEnvironmentAnalysisStepThree(payload) { const strategies = Array.isArray(payload?.strategies) ? payload.strategies : []; if (isStudentDemo()) { const types = ["so", "wo", "st", "wt"]; if (strategies.length !== types.length || types.some((type) => !strategies.some((item) => item?.strategyType === type)) || strategies.some((item) => !String(item?.recommendation || "").trim() || !String(item?.expectedEffect || "").trim())) return Promise.reject(new Error("请完成 SO、WO、ST 和 WT 四类策略及预期效果")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/competitive-environment-analysis/step-3/validate", method: "post", data: { strategies }, headers: { repeatSubmit: false } }); } export function checkCompetitiveEnvironmentAnalysisStepFour(payload) { const value = payload || {}; const priorities = Array.isArray(value.priorities) ? value.priorities : []; if (isStudentDemo()) { const types = ["so", "wo", "st", "wt"]; const ranks = priorities.map((item) => Number(item?.priority)); if (priorities.length !== types.length || types.some((type) => !priorities.some((item) => item?.strategyType === type)) || new Set(ranks).size !== 4 || ranks.some((rank) => ![1, 2, 3, 4].includes(rank)) || priorities.some((item) => !String(item?.reason || "").trim()) || !String(value.coreRiskOne || "").trim() || !String(value.coreRiskTwo || "").trim() || !String(value.conclusion || "").trim()) return Promise.reject(new Error("请完整填写策略优先级、至少 2 个核心风险点和总结 SWOT 竞争环境评估结论")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/competitive-environment-analysis/step-4/validate", method: "post", data: value, headers: { repeatSubmit: false }, skipErrorMessage: true }); } const consumerScenarioJourneyOrder = ["problem-awareness", "information-search", "evaluation-decision", "purchase-conversion", "post-purchase"]; export function checkConsumerScenarioStepOne(payload) { const stageOrder = Array.isArray(payload?.stageOrder) ? payload.stageOrder : []; if (isStudentDemo()) return Promise.resolve({ code: 200, data: { valid: stageOrder.length === consumerScenarioJourneyOrder.length && stageOrder.every((item, index) => item === consumerScenarioJourneyOrder[index]), message: "排序校验完成" } }); return request({ url: "/api/student-training-answers/consumer-scenario/step-1/validate", method: "post", data: { stageOrder }, headers: { repeatSubmit: false } }); } export function checkConsumerScenarioStepTwo(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { const valid = rows.length === consumerScenarioJourneyOrder.length && rows.every((row) => consumerScenarioJourneyOrder.includes(row?.stageId) && String(row?.behaviorDescription || "").trim() && String(row?.keyTouchpoints || "").trim() && ["负面", "中性", "正面"].includes(row?.emotionExperience) && String(row?.decisionFactors || "").trim()) && new Set(rows.map((row) => row.stageId)).size === consumerScenarioJourneyOrder.length; return Promise.resolve({ code: 200, data: { valid, message: valid ? "校验通过" : "请完整填写五个阶段的行为、触点、情绪和决策因素" } }); } return request({ url: "/api/student-training-answers/consumer-scenario/step-2/validate", method: "post", data: { rows }, headers: { repeatSubmit: false } }); } export function checkConsumerScenarioStepThree(payload) { const journeyMapUrl = String(payload?.journeyMapUrl || "").trim(); if (isStudentDemo()) return Promise.resolve({ code: 200, data: { valid: Boolean(journeyMapUrl), message: journeyMapUrl ? "用户购买旅程图已上传。" : "请上传用户购买旅程图。" } }); return request({ url: "/api/student-training-answers/consumer-scenario/step-3/validate", method: "post", data: { journeyMapUrl }, headers: { repeatSubmit: false } }); } export function checkConsumerScenarioStepFour(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { const valid = rows.length === consumerScenarioJourneyOrder.length && rows.every((row) => consumerScenarioJourneyOrder.includes(row?.stageId) && String(row?.painPoint || "").trim() && String(row?.optimizationSuggestion || "").trim()) && new Set(rows.map((row) => row.stageId)).size === consumerScenarioJourneyOrder.length; return Promise.resolve({ code: 200, data: { valid, message: valid ? "五个阶段的优化分析填写完整。" : "请完整填写五个阶段的痛点和优化建议。" } }); } return request({ url: "/api/student-training-answers/consumer-scenario/step-4/validate", method: "post", data: { rows }, headers: { repeatSubmit: false } }); } export function checkFeatureConversionStepOne(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { const valid = rows.length >= 8 && rows.length <= 12 && rows.every((row) => ["voice", "insight", "definition", "description", "acceptance"].every((key) => String(row?.[key] || "").trim())); return Promise.resolve({ code: 200, data: { valid, message: valid ? "需求转化表填写完整。" : "请完整填写 8 至 12 行需求转化信息。" } }); } return request({ url: "/api/student-training-answers/feature-conversion/step-1/validate", method: "post", data: { rows }, headers: { repeatSubmit: false } }); } export function checkFeatureConversionStepTwo(payload) { const sourceDefinitions = Array.isArray(payload?.sourceDefinitions) ? payload.sourceDefinitions : []; const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { const valid = rows.length >= 8 && rows.length <= 12 && sourceDefinitions.length === rows.length && rows.every((row) => ["reach", "impact", "confidence", "effort"].every((key) => Number.isFinite(Number(row?.[key])))); return Promise.resolve({ code: 200, data: { valid, message: valid ? "RICE 评分填写完整。" : "请完整填写 RICE 评分。" } }); } return request({ url: "/api/student-training-answers/feature-conversion/step-2/validate", method: "post", data: { sourceDefinitions, rows }, headers: { repeatSubmit: false } }); } export function checkFeatureConversionStepThree(payload) { const scoreRows = Array.isArray(payload?.scoreRows) ? payload.scoreRows : []; const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { const valid = scoreRows.length >= 8 && scoreRows.length <= 12 && rows.length === scoreRows.length && rows.every((row, index) => row?.name === scoreRows[index]?.name && ["P0", "P1", "P2", "P3"].includes(row?.priority) && String(row?.reason || "").trim()); return Promise.resolve({ code: 200, data: { valid, message: valid ? "功能优先级填写完整。" : "请完整填写功能优先级与理由。" } }); } return request({ url: "/api/student-training-answers/feature-conversion/step-3/validate", method: "post", data: { scoreRows, rows }, headers: { repeatSubmit: false } }); } function checkIndustryDemandDemo(value, required) { if (required.some((key) => !String(value?.[key] || "").trim())) throw new Error("请完整填写 PEST 分析信息"); return { valid: true, message: "校验通过" }; } export function checkIndustryDemandAnalysisStepOne(payload) { const value = payload || {}; if (isStudentDemo()) { try { return Promise.resolve({ code: 200, data: checkIndustryDemandDemo(value, ["political", "economic", "social", "technology"]) }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/industry-demand-analysis/step-1/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function checkIndustryDemandAnalysisStepTwo(payload) { const value = payload || {}; if (isStudentDemo()) { try { return Promise.resolve({ code: 200, data: checkIndustryDemandDemo(value, ["political", "economic", "social", "technology"]) }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/industry-demand-analysis/step-2/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function checkIndustryDemandAnalysisStepThree(payload) { const rows = Array.isArray(payload?.rows) ? payload.rows : []; if (isStudentDemo()) { const valid = rows.length === 4 && ["political", "economic", "social", "technology"].every((dimension) => rows.some((row) => row?.dimension === dimension && String(row?.opportunities || "").trim() && String(row?.challenges || "").trim() && String(row?.strategies || "").trim())); return Promise.resolve({ code: 200, data: { valid, message: valid ? "校验通过" : "请完成四个维度的机会、挑战和策略" } }); } return request({ url: "/api/student-training-answers/industry-demand-analysis/step-3/validate", method: "post", data: { rows }, headers: { repeatSubmit: false } }); } export function checkIndustryDemandAnalysisStepFour(payload) { const value = payload || {}; if (isStudentDemo()) { const valid = ["高度友好", "较为友好", "中性", "存在风险", "风险较高"].includes(String(value.overallAssessment || "").trim()) && ["建议进入", "谨慎进入", "建议暂缓"].includes(String(value.marketDecision || "").trim()) && String(value.decisionReason || "").trim(); return Promise.resolve({ code: 200, data: { valid, message: valid ? "校验通过" : "请完整填写总体评估、市场进入结论和理由" } }); } return request({ url: "/api/student-training-answers/industry-demand-analysis/step-4/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function checkTargetUserProfileStepOne(payload) { const value = payload || {}; if (isStudentDemo()) { const fileName = String(value.excelName || "").trim(); const fileUrl = String(value.excelUrl || "").trim(); if (!fileUrl || !/\.xlsx?$/i.test(fileName)) return Promise.reject(new Error("请上传 RFM 用户分层分析结果 Excel 数据")); if (Number(value.previewRowCount || 0) < 1 || Number(value.previewColumnCount || 0) < 1) return Promise.reject(new Error("Excel 文件需要包含可预览的表头和数据")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/target-user-profile/step-1/validate", method: "post", data: value, headers: { repeatSubmit: false } }); } export function checkTargetUserProfileStepTwo(payload) { const charts = Array.isArray(payload?.charts) ? payload.charts : []; if (isStudentDemo()) { const expectedTypes = ["segment-share", "average-spend", "score-heatmap"]; const types = new Set(charts.map((chart) => chart?.type)); const invalid = charts.find((chart) => !String(chart?.url || "").trim() || !/\.(png|jpe?g|webp)$/i.test(String(chart?.name || ""))); if (charts.length !== expectedTypes.length || expectedTypes.some((type) => !types.has(type)) || invalid) return Promise.reject(new Error("请上传三类 RFM 分析图")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/target-user-profile/step-2/validate", method: "post", data: { charts }, headers: { repeatSubmit: false } }); } export function checkTargetUserProfileStepThree(payload) { const insights = Array.isArray(payload?.insights) ? payload.insights : []; if (isStudentDemo()) { const expected = ["高价值用户占比", "用户活跃度", "消费能力", "用户忠诚度", "重点客户群特征"]; const dimensions = new Set(insights.map((item) => item?.dimension)); if (insights.length !== expected.length || expected.some((item) => !dimensions.has(item)) || insights.some((item) => !String(item?.analysis || "").trim())) return Promise.reject(new Error("请完成五个 RFM 用户群体特点分析维度")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/target-user-profile/step-3/validate", method: "post", data: { insights }, headers: { repeatSubmit: false } }); } export function checkTargetUserProfileStepFour(payload) { const records = Array.isArray(payload?.records) ? payload.records : []; if (isStudentDemo()) { const expected = ["U01", "U02", "U03"]; const codes = new Set(records.map((item) => item?.code)); const fields = ["user", "basicAttributes", "tags", "behavior", "painPoints", "psychology"]; if (records.length !== expected.length || expected.some((code) => !codes.has(code)) || records.some((item) => fields.some((field) => !String(item?.[field] || "").trim()))) return Promise.reject(new Error("请完成三位重点用户的完整画像")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/target-user-profile/step-4/validate", method: "post", data: { records }, headers: { repeatSubmit: false } }); } export function checkTargetUserProfileStepFive(payload) { const value = payload || {}; const segments = Array.isArray(value.segments) ? value.segments : []; if (isStudentDemo()) { const fields = ["market", "attractiveness", "painPoints", "behaviorCharacteristics", "productOpportunity"]; if (!segments.length || segments.length > 6 || !String(value.marketSelection || "").trim() || segments.some((item) => fields.some((field) => !String(item?.[field] || "").trim()))) return Promise.reject(new Error("请完成细分市场分析表和市场选择结论")); return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); } return request({ url: "/api/student-training-answers/target-user-profile/step-5/validate", method: "post", data: { segments, marketSelection: String(value.marketSelection || "") }, headers: { repeatSubmit: false } }); }