|
|
|
|
@ -104,6 +104,14 @@
|
|
|
|
|
<div class="task-actions"><button type="button" class="btn-nav btn-submit" :disabled="saving" @click="saveAndGoToStepThree">保存并进入第 3 步</button><button type="button" class="btn-nav" :disabled="saving" @click="resetTraining"><el-icon><RefreshLeft /></el-icon>清空填写</button></div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<template v-else-if="currentStep === 2">
|
|
|
|
|
<p class="factor-intro">商业化及市场反馈角度分析,填写下表:</p>
|
|
|
|
|
<div class="training-table-wrap"><table class="factor-table"><thead><tr><th class="dimension-col">维度</th><th class="criteria-col">评估项</th><th>成功产品</th><th>失败产品</th></tr></thead><tbody>
|
|
|
|
|
<tr v-for="row in stepThreeRows" :key="row.dimension"><td class="dimension-cell">{{ row.dimension }}</td><td class="criteria-cell">{{ row.criteria }}</td><td><textarea v-model="row.successAnalysis" class="table-textarea" placeholder="填写成功产品在该维度的分析" /></td><td><textarea v-model="row.failedAnalysis" class="table-textarea" placeholder="填写失败产品在该维度的分析" /></td></tr>
|
|
|
|
|
</tbody></table></div>
|
|
|
|
|
<div class="task-actions"><button type="button" class="btn-nav btn-submit" :disabled="saving" @click="saveAndGoToStepFour">保存并进入第 4 步</button><button type="button" class="btn-nav" :disabled="saving" @click="resetTraining"><el-icon><RefreshLeft /></el-icon>清空填写</button></div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<div v-else class="step-placeholder">
|
|
|
|
|
<p>第 {{ currentStep + 1 }} 步内容待配置</p>
|
|
|
|
|
<span>已保存的分析可从上方步骤栏返回查看。</span>
|
|
|
|
|
@ -125,7 +133,7 @@
|
|
|
|
|
<script setup>
|
|
|
|
|
import { Download, Operation, RefreshLeft } from "@element-plus/icons-vue";
|
|
|
|
|
import { getTrainingTaskByKey } from "@/api/trainingTask";
|
|
|
|
|
import { checkProductDevelopmentFactorsStepOne, checkProductDevelopmentFactorsStepTwo, getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
|
|
|
|
|
import { checkProductDevelopmentFactorsStepOne, checkProductDevelopmentFactorsStepTwo, checkProductDevelopmentFactorsStepThree, getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
|
|
|
|
|
import useUserStore from "@/store/modules/user";
|
|
|
|
|
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
|
|
|
|
|
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue";
|
|
|
|
|
@ -182,6 +190,11 @@ const STEP_TWO_FACTOR_ROWS = [
|
|
|
|
|
{ dimension: "技术应用", criteria: "核心技术是否已成熟、可量产?避免使用实验室级原型(如未验证的 AI 模型)" },
|
|
|
|
|
{ dimension: "合规与认证", criteria: "是否通过目标市场强制认证?目标市场要求的产品资质、检测报告等中国:CCC;美国:FCC;欧盟:CE;出口需符合 RoHS、REACH。是否规避专利与知识产权风险?" },
|
|
|
|
|
];
|
|
|
|
|
const STEP_THREE_FACTOR_ROWS = [
|
|
|
|
|
{ dimension: "供应链与成本控制", criteria: "产品成本是否可控?是否支持规模化?是否具备备选供应链?关键元器件(如芯片、传感器)有≥2家可替换供应商" },
|
|
|
|
|
{ dimension: "商业模式", criteria: "盈利模式是否清晰?硬件销售毛利率≥50%,或有订阅服务(如云存储、AI 功能包)。是否构建“数据 - 反馈 - 迭代”闭环?产品使用数据可回传,驱动功能优化与新版本开发" },
|
|
|
|
|
{ dimension: "市场反馈", criteria: "是否处于需求上升期?功能、设计、服务、品牌是否有差异化壁垒?用户是否高留存、高互动、低退货?" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const createStepOneRows = () => STEP_ONE_FACTOR_ROWS.map((row) => ({
|
|
|
|
|
...row,
|
|
|
|
|
@ -189,8 +202,10 @@ const createStepOneRows = () => STEP_ONE_FACTOR_ROWS.map((row) => ({
|
|
|
|
|
failedAnalysis: "",
|
|
|
|
|
}));
|
|
|
|
|
const createStepTwoRows = () => STEP_TWO_FACTOR_ROWS.map((row) => ({ ...row, successAnalysis: "", failedAnalysis: "" }));
|
|
|
|
|
const createStepThreeRows = () => STEP_THREE_FACTOR_ROWS.map((row) => ({ ...row, successAnalysis: "", failedAnalysis: "" }));
|
|
|
|
|
const stepOneRows = ref(createStepOneRows());
|
|
|
|
|
const stepTwoRows = ref(createStepTwoRows());
|
|
|
|
|
const stepThreeRows = ref(createStepThreeRows());
|
|
|
|
|
|
|
|
|
|
const progressItems = [
|
|
|
|
|
{ text: "进行中:成功/失败产品对比", status: "doing" },
|
|
|
|
|
@ -230,6 +245,7 @@ async function loadSavedAnswer() {
|
|
|
|
|
clearDraft();
|
|
|
|
|
}
|
|
|
|
|
if (answer?.step2Answer) stepTwoRows.value = restoreRows(answer.step2Answer, createStepTwoRows);
|
|
|
|
|
if (answer?.step3Answer) stepThreeRows.value = restoreRows(answer.step3Answer, createStepThreeRows);
|
|
|
|
|
restoreCurrentStep(answer?.currentStep);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Keep local draft when the backend answer cannot be loaded.
|
|
|
|
|
@ -310,6 +326,7 @@ function loadDraft() {
|
|
|
|
|
if (draft?.stepOne) stepOneRows.value = restoreRows(draft.stepOne, createStepOneRows);
|
|
|
|
|
else stepOneRows.value = restoreRows(raw, createStepOneRows);
|
|
|
|
|
if (draft?.stepTwo) stepTwoRows.value = restoreRows(draft.stepTwo, createStepTwoRows);
|
|
|
|
|
if (draft?.stepThree) stepThreeRows.value = restoreRows(draft.stepThree, createStepThreeRows);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
clearDraft();
|
|
|
|
|
@ -336,7 +353,8 @@ function buildStepOneOutcome() {
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
function buildStepTwoOutcome() { return { title: taskTitle.value, factors: stepTwoRows.value.map((row) => ({ dimension: row.dimension, criteria: row.criteria, successAnalysis: row.successAnalysis, failedAnalysis: row.failedAnalysis })) }; }
|
|
|
|
|
function buildDraft() { return { stepOne: buildStepOneOutcome(), stepTwo: buildStepTwoOutcome(), currentStep: currentStep.value + 1 }; }
|
|
|
|
|
function buildStepThreeOutcome() { return { title: taskTitle.value, factors: stepThreeRows.value.map((row) => ({ dimension: row.dimension, criteria: row.criteria, successAnalysis: row.successAnalysis, failedAnalysis: row.failedAnalysis })) }; }
|
|
|
|
|
function buildDraft() { return { stepOne: buildStepOneOutcome(), stepTwo: buildStepTwoOutcome(), stepThree: buildStepThreeOutcome(), currentStep: currentStep.value + 1 }; }
|
|
|
|
|
|
|
|
|
|
function buildStepOneValidationPayload() {
|
|
|
|
|
return {
|
|
|
|
|
@ -358,6 +376,8 @@ function buildStepOneAnswerPayload(saveAction = "SAVE", persistedStep = currentS
|
|
|
|
|
}
|
|
|
|
|
function buildStepTwoValidationPayload() { return { factors: stepTwoRows.value.map(({ dimension, successAnalysis, failedAnalysis }) => ({ dimension, successAnalysis, failedAnalysis })) }; }
|
|
|
|
|
function buildStepTwoAnswerPayload(saveAction = "SAVE", persistedStep = currentStep.value + 1) { return { step2Answer: JSON.stringify(buildStepTwoOutcome()), currentStep: persistedStep, submitted: false, saveAction }; }
|
|
|
|
|
function buildStepThreeValidationPayload() { return { factors: stepThreeRows.value.map(({ dimension, successAnalysis, failedAnalysis }) => ({ dimension, successAnalysis, failedAnalysis })) }; }
|
|
|
|
|
function buildStepThreeAnswerPayload(saveAction = "SAVE", persistedStep = currentStep.value + 1) { return { step3Answer: JSON.stringify(buildStepThreeOutcome()), currentStep: persistedStep, submitted: false, saveAction }; }
|
|
|
|
|
|
|
|
|
|
async function saveAndGoToStepTwo() {
|
|
|
|
|
if (saving.value) return;
|
|
|
|
|
@ -392,9 +412,23 @@ async function saveAndGoToStepThree() {
|
|
|
|
|
} catch (error) { proxy?.$modal?.msgError?.(error?.message || "请完成技术应用和合规与认证分析"); } finally { saving.value = false; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function saveAndGoToStepFour() {
|
|
|
|
|
if (saving.value) return;
|
|
|
|
|
saving.value = true;
|
|
|
|
|
try {
|
|
|
|
|
await checkProductDevelopmentFactorsStepThree(buildStepThreeValidationPayload());
|
|
|
|
|
await saveStudentTrainingAnswer(TASK_KEY, buildStepThreeAnswerPayload("SAVE", 4));
|
|
|
|
|
clearDraft();
|
|
|
|
|
if (trainingSteps.value.length < 4) { proxy?.$modal?.msgSuccess("已保存,当前任务尚未配置第 4 步"); return; }
|
|
|
|
|
currentStep.value = 3;
|
|
|
|
|
proxy?.$modal?.msgSuccess("已保存,已进入第 4 步");
|
|
|
|
|
} catch (error) { proxy?.$modal?.msgError?.(error?.message || "请完成供应链与成本控制、商业模式和市场反馈分析"); } finally { saving.value = false; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function resetTraining() {
|
|
|
|
|
stepOneRows.value = createStepOneRows();
|
|
|
|
|
stepTwoRows.value = createStepTwoRows();
|
|
|
|
|
stepThreeRows.value = createStepThreeRows();
|
|
|
|
|
currentStep.value = 0;
|
|
|
|
|
clearDraft();
|
|
|
|
|
try {
|
|
|
|
|
|