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 submittedChannels = Array . isArray ( value . channels ) ? value . channels : null ;
const conversionRates = Array . isArray ( value . conversionRates ) ? value . conversionRates : [ ] ;
const anomalyRows = Array . isArray ( value . anomalyRows ) ? value . anomalyRows : [ ] ;
if ( isStudentDemo ( ) ) {
try {
const nodes = [ "曝光-点击" , "点击-加购" , "加购-下单" , "下单-好评" , "好评-复购" ] ;
const legacyChannels = [ { id : "tmall" , name : "天猫" } , { id : "jd" , name : "京东" } , { id : "douyin" , name : "抖音" } , { id : "pinduoduo" , name : "拼多多" } ] ;
const channels = ( submittedChannels || legacyChannels ) . map ( ( channel ) => ( { id : String ( channel ? . id || "" ) . trim ( ) , name : String ( channel ? . name || "" ) . trim ( ) } ) ) ;
if ( ! channels . length || channels . some ( ( channel ) => ! channel . id || ! channel . name )
|| new Set ( channels . map ( ( channel ) => channel . id ) ) . size !== channels . length
|| new Set ( channels . map ( ( channel ) => channel . name ) ) . size !== channels . length ) {
throw new Error ( "请至少保留一个渠道,并确保渠道名称不为空且不重复" ) ;
}
if ( conversionRates . length !== nodes . length || new Set ( conversionRates . map ( ( row ) => String ( row ? . linkNode || "" ) . trim ( ) ) ) . size !== nodes . length ) {
throw new Error ( "请完整填写 5 个链路节点的转化率" ) ;
}
const rates = new Map ( ) ;
conversionRates . forEach ( ( row ) => {
const node = String ( row ? . linkNode || "" ) . trim ( ) ;
if ( ! nodes . includes ( node ) ) throw new Error ( "链路节点必须与题目中的 5 个节点一致" ) ;
const channelRates = row ? . channelRates || row || { } ;
const rawValues = [ ... channels . map ( ( channel ) => channelRates [ channel . id ] ) , row ? . industryAverage ] ;
if ( rawValues . some ( ( item ) => item === null || item === "" || typeof item === "undefined" ) ) throw new Error ( "请填写 0 至 100 之间的转化率" ) ;
const values = rawValues . map ( Number ) ;
if ( values . some ( ( item ) => ! Number . isFinite ( item ) || item < 0 || item > 100 ) ) throw new Error ( "请填写 0 至 100 之间的转化率" ) ;
rates . set ( node , { ... row , channelRates } ) ;
} ) ;
if ( ! String ( value . funnelChartUrl || "" ) . trim ( ) ) throw new Error ( "请上传节点转化漏斗对比图" ) ;
if ( anomalyRows . length < 1 || anomalyRows . length > 5 ) throw new Error ( "异常节点分析最少填写 1 行,最多填写 5 行" ) ;
anomalyRows . forEach ( ( row , index ) => {
const channel = channels . find ( ( item ) => item . id === String ( row ? . channelId || "" ) . trim ( ) ) || channels . find ( ( item ) => item . name === String ( row ? . channel || "" ) . trim ( ) ) ;
const node = String ( row ? . linkNode || "" ) . trim ( ) ;
const source = rates . get ( node ) ;
if ( ! channel ) throw new Error ( ` 第 ${ index + 1 } 行渠道必须来源于第一个表格 ` ) ;
if ( ! source ) throw new Error ( ` 第 ${ index + 1 } 行链路节点必须来源于第一个表格 ` ) ;
const sourceRate = source . channelRates [ channel . id ] ;
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 : { channels : submittedChannels , 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 } \u 0000 ${ 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 } } ) ;
}