> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
<divclass="table-wrap"><table><thead><tr><th>模块</th><th>案例公司的策略</th><th>说明</th></tr></thead><tbody><trv-for="model in form.businessModels" :key="model.module"><td class="fixed-cell">{{model.module}}</td><td><textareav-model.trim="model.strategy"placeholder="填写渠道、产品组合或服务策略"/></td><td><textareav-model.trim="model.explanation"placeholder="说明策略与用户、成本的关系"/></td></tr></tbody></table></div>
<sectionclass="decision-section"><h3>(1)总体竞争态势评估</h3><p>请选择该产品进入目标市场的总体竞争判断:</p><divclass="radio-options"><labelv-for="option in evaluationOptions" :key="option"><inputv-model="finalForm.overallEvaluation":disabled="!stepThreePassed"type="radio"name="competitive-evaluation":value="option"/><span>{{option}}</span></label></div></section>
<sectionclass="decision-section"><h3>(2)优先策略排序</h3><pclass="section-tip">策略内容由第3步填写的“具体策略建议”自动带入;请完成优先级排序并说明理由。</p><divclass="priority-table-wrap"><tableclass="priority-table"><thead><tr><th>优先级排序</th><th>策略</th><th>理由</th></tr></thead><tbody><trv-for="row in strategyDefinitions" :key="row.key"><td><select v-model.number="finalForm.priorities[row.key].priority" :disabled="!stepThreePassed" :aria-label="`${row.label} 的优先级排序`"><option :value="null">请选择</option><option v-for="rank in 4" :key="rank" :value="rank">{{rank}}</option></select></td><tdclass="priority-strategy"><strong>{{row.label}}</strong><p>{{strategyForm[row.key].recommendation||'请先在第 3 步填写该策略建议'}}</p></td><td><textareav-model.trim="finalForm.priorities[row.key].reason":disabled="!stepThreePassed"placeholder="说明该策略的排序理由"/></td></tr></tbody></table></div></section>
asyncfunctionloadSavedAnswer(){try{constanswer=(awaitgetStudentTrainingAnswer(TASK_KEY))?.data;if(answer?.step1Answer){restoreOutcome(answer.step1Answer,form);stepOnePassed.value=true;clearDraft();}if(answer?.step2Answer){restoreOutcome(answer.step2Answer,supplementForm,"supplements");stepTwoPassed.value=true;}if(answer?.step3Answer){restoreStrategyOutcome(answer.step3Answer);stepThreePassed.value=true;}if(answer?.step4Answer)restoreFinalOutcome(answer.step4Answer);if(Number(answer?.currentStep)>0)currentStep.value=Number(answer.currentStep);}catch(error){/* Keep local draft when unavailable. */}}
@ -90,7 +92,7 @@ function switchStep(step) { if (step === 2 && !stepOnePassed.value) { proxy?.$mo
asyncfunctionresetTraining(){form.value=createForm();supplementForm.value=createForm();strategyForm.value=createStrategyForm();finalForm.value=createFinalForm();stepOnePassed.value=false;stepTwoPassed.value=false;stepThreePassed.value=false;currentStep.value=1;clearDraft();try{saving.value=true;awaitsaveStudentTrainingAnswer(TASK_KEY,{step1Answer:JSON.stringify(buildStepOneOutcome()),currentStep:1,submitted:false,saveAction:"RESET"});}catch(error){/* Local clearing still succeeds. */}finally{saving.value=false;}proxy?.$modal?.msgSuccess("已清空填写内容");}
assert(/\/help/.test(api)&&/\/assessment/.test(api),"evaluation API should expose help and assessment operations");
assert(/TrainingAiSidebar/.test(page)&&/:has-saved-answer="hasSavedAnswer"/.test(page),"generic training page should pass persisted answer state to the AI sidebar");
assert(/form\.value/.test(page)&&/workRows\.value/.test(page)&&/processRows\.value/.test(page),"hasAnyAnswer should inspect all supported answer shapes");
assert(/requestAiTrainingHelp\(taskKey, question, stepContext = ""\)/.test(api),"AI help API should accept the student's question and current step context");
assert(/data:\s*\{\s*question\s*,\s*stepContext\s*\}/.test(api),"AI help API should submit the student's question and current step context as JSON");
assert(/requestAiTrainingHelp\(taskKey, question\)/.test(api),"AI help API should accept the student's question");
assert(/data:\s*\{\s*question\s*\}/.test(api),"AI help API should submit the student's question as JSON");
assert(/const question = assistQuestion\.value\.trim\(\)/.test(sidebar),"AI sidebar should trim the student's question before sending it");
assert(/if \(!question\)/.test(sidebar),"AI sidebar should block an empty AI help question");
assert(/requestAiTrainingHelp\(currentTaskKey\.value, question, currentStepContext\.value\)/.test(sidebar),"AI sidebar should send the trimmed question and current step context to AI help");
assert(/requestAiTrainingHelp\(currentTaskKey\.value, question\)/.test(sidebar),"AI sidebar should send the trimmed question to AI help");
assert(/onMounted\(/.test(component)&&/watch\(\s*\(\)\s*=>\s*props\.taskKey/.test(component),"AI evaluation should load on mount and task changes");
assert(/AI助学/.test(component)&&/AI助评/.test(component),"AI evaluation should offer separate help and assessment controls");
assert.ok(stepper,"product pricing must use the shared step-indicator navigation pattern");
assert.match(stepper,/v-for="step in pricingSteps"/,"the step navigation must be rendered from actual step data");
assert.match(stepper,/@click="switchStep\(step\.no\)"/,"each pricing step must be navigable");
assert.match(page,/class="workbench-head workbench-head--actions-only"/,"pricing must use the standard top action row");
assert.match(page,/@click="exportPriceOutcome"/,"pricing must provide the standard export action");
assert.ok(page.indexOf("workbench-head--actions-only")<page.indexOf('class="step-indicator"'),"actions must appear before the step indicator");
assert.ok(page.indexOf('class="step-indicator"')<page.indexOf('<section class="step-card">'),"step content must be inside a separate standard step card");
assert.match(page,/const pricingSteps = computed\(\(\) => \[/,"the page must define its pricing steps");
assert.match(page,newRegExp(`title: labels\\.${label}`),`${label} must appear in the five-step navigation`);
}
assert.match(page,/\.step-indicator\s*\{/,"the page must style the shared step-indicator layout");
assert.match(page,/\.step-connector\s*\{[\s\S]*?flex:\s*0 1 40px;/,"connectors must use the compact shared spacing");
assert.match(page,/\.primary-button,\s*\.secondary-button\s*\{[\s\S]*?color:\s*#e9fbff;/,"pricing toolbar buttons must use the student dark theme");
assert.match(page,/\.primary-button\s*\{[\s\S]*?background:\s*linear-gradient/,"the primary action must use the blue gradient button style");
assert.match(page,/\.toolbar-actions\s*\{[\s\S]*?flex:\s*none;/,"toolbar actions must retain their width instead of being compressed");
constpsmTheme=page.match(/\/\* PSM dark theme \*\/([\s\S]*?)<\/style>/)?.[1]||"";
assert.ok(psmTheme,"the PSM step must define its dark theme locally");
assert.match(psmTheme,/\.psm-point-section,\s*\.psm-chart-section\s*\{[\s\S]*?background:\s*rgba\(5, 35, 48, 0\.72\)/,"PSM panels must not fall back to white cards");
assert.match(psmTheme,/\.psm-table\s*\{[\s\S]*?width:\s*100%/,"the PSM table must fill its available workspace");
assert.match(psmTheme,/\.psm-table input,\s*\.psm-point-grid input\s*\{[\s\S]*?background:\s*rgba\(2, 19, 28, 0\.7\)/,"PSM inputs must use the dark form style");
assert(/实训背景/.test(trainingTaskBrief)&&/实训目标/.test(trainingTaskBrief)&&/实训要求/.test(trainingTaskBrief),"common TrainingTaskBrief component should render the three student brief sections");