@ -63,7 +63,7 @@
< / tr >
< / tr >
< / thead >
< / thead >
< tbody >
< tbody >
< tr v-for ="row in factor Rows" :key="row.dimension">
< tr v-for ="row in stepOne Rows" :key="row.dimension">
< td class = "dimension-cell" > { { row . dimension } } < / td >
< td class = "dimension-cell" > { { row . dimension } } < / td >
< td class = "criteria-cell" > { { row . criteria } } < / td >
< td class = "criteria-cell" > { { row . criteria } } < / td >
< td >
< td >
@ -96,9 +96,17 @@
< / div >
< / div >
< / template >
< / template >
< template v -else -if = " currentStep = = = 1 " >
< 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 stepTwoRows" :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 ="saveAndGoToStepThree" > 保 存 并 进 入 第 3 步 < / button > < button type = "button" class = "btn-nav" :disabled ="saving" @click ="resetTraining" > < el -icon > < RefreshLeft / > < / e l - i c o n > 清 空 填 写 < / b u t t o n > < / d i v >
< / template >
< div v -else class = "step-placeholder" >
< div v -else class = "step-placeholder" >
< p > 第 { { currentStep + 1 } } 步内容待配置 < / p >
< p > 第 { { currentStep + 1 } } 步内容待配置 < / p >
< span > 第 1 步分析已保存 , 可从上方步骤栏返回查看 。 < / span >
< span > 已保存的分析 可从上方步骤栏返回查看。 < / span >
< / div >
< / div >
< / section >
< / section >
< / section >
< / section >
@ -117,7 +125,7 @@
< script setup >
< script setup >
import { Download , Operation , RefreshLeft } from "@element-plus/icons-vue" ;
import { Download , Operation , RefreshLeft } from "@element-plus/icons-vue" ;
import { getTrainingTaskByKey } from "@/api/trainingTask" ;
import { getTrainingTaskByKey } from "@/api/trainingTask" ;
import { checkProductDevelopmentFactorsStepOne , getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer" ;
import { checkProductDevelopmentFactorsStepOne , checkProductDevelopmentFactorsStepTwo, getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer" ;
import useUserStore from "@/store/modules/user" ;
import useUserStore from "@/store/modules/user" ;
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue" ;
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue" ;
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue" ;
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue" ;
@ -170,14 +178,19 @@ const STEP_ONE_FACTOR_ROWS = [
"是否能接入主流平台? 是否支持iOS/Android、HomeKit、米家、Alexa等生态? 是否支持第三方开发者扩展, 提供API或SDK, 鼓励生态共建? " ,
"是否能接入主流平台? 是否支持iOS/Android、HomeKit、米家、Alexa等生态? 是否支持第三方开发者扩展, 提供API或SDK, 鼓励生态共建? " ,
} ,
} ,
] ;
] ;
const STEP _TWO _FACTOR _ROWS = [
{ dimension : "技术应用" , criteria : "核心技术是否已成熟、可量产?避免使用实验室级原型(如未验证的 AI 模型)" } ,
{ dimension : "合规与认证" , criteria : "是否通过目标市场强制认证? 目标市场要求的产品资质、检测报告等中国: CCC; 美国: FCC; 欧盟: CE; 出口需符合 RoHS、REACH。是否规避专利与知识产权风险? " } ,
] ;
const createFactorRows = ( ) => STEP _ONE _FACTOR _ROWS . map ( ( row ) => ( {
const create StepOne Rows = ( ) => STEP _ONE _FACTOR _ROWS . map ( ( row ) => ( {
... row ,
... row ,
successAnalysis : "" ,
successAnalysis : "" ,
failedAnalysis : "" ,
failedAnalysis : "" ,
} ) ) ;
} ) ) ;
const createStepTwoRows = ( ) => STEP _TWO _FACTOR _ROWS . map ( ( row ) => ( { ... row , successAnalysis : "" , failedAnalysis : "" } ) ) ;
const factorRows = ref ( createFactorRows ( ) ) ;
const stepOneRows = ref ( createStepOneRows ( ) ) ;
const stepTwoRows = ref ( createStepTwoRows ( ) ) ;
const progressItems = [
const progressItems = [
{ text : "进行中:成功/失败产品对比" , status : "doing" } ,
{ text : "进行中:成功/失败产品对比" , status : "doing" } ,
@ -193,7 +206,7 @@ onMounted(async () => {
} ) ;
} ) ;
watch (
watch (
( ) => JSON . stringify ( build Outcome ( ) ) ,
( ) => JSON . stringify ( build Draft ( ) ) ,
( ) => persistDraft ( )
( ) => persistDraft ( )
) ;
) ;
@ -213,9 +226,10 @@ async function loadSavedAnswer() {
const res = await getStudentTrainingAnswer ( TASK _KEY ) ;
const res = await getStudentTrainingAnswer ( TASK _KEY ) ;
const answer = res ? . data ;
const answer = res ? . data ;
if ( answer ? . step1Answer ) {
if ( answer ? . step1Answer ) {
applySavedAnswer ( answer . step1Answer ) ;
stepOneRows. value = restoreRows ( answer . step1Answer , createStepOneRows ) ;
clearDraft ( ) ;
clearDraft ( ) ;
}
}
if ( answer ? . step2Answer ) stepTwoRows . value = restoreRows ( answer . step2Answer , createStepTwoRows ) ;
restoreCurrentStep ( answer ? . currentStep ) ;
restoreCurrentStep ( answer ? . currentStep ) ;
} catch ( error ) {
} catch ( error ) {
/ / K e e p l o c a l d r a f t w h e n t h e b a c k e n d a n s w e r c a n n o t b e l o a d e d .
/ / K e e p l o c a l d r a f t w h e n t h e b a c k e n d a n s w e r c a n n o t b e l o a d e d .
@ -250,10 +264,10 @@ function parseSavedAnswer(value) {
}
}
}
}
function applySavedAnswer( value ) {
function restoreRows( value , createRows ) {
const parsed = parseSavedAnswer ( value ) ;
const parsed = parseSavedAnswer ( value ) ;
if ( ! parsed || typeof parsed !== "object" ) {
if ( ! parsed || typeof parsed !== "object" ) {
return ;
return createRows ( ) ;
}
}
if ( Array . isArray ( parsed . factors ) ) {
if ( Array . isArray ( parsed . factors ) ) {
const savedByDimension = parsed . factors . reduce ( ( map , row ) => {
const savedByDimension = parsed . factors . reduce ( ( map , row ) => {
@ -262,12 +276,13 @@ function applySavedAnswer(value) {
}
}
return map ;
return map ;
} , { } ) ;
} , { } ) ;
factorRows . value = createFactor Rows( ) . map ( ( row , index ) => ( {
return create Rows( ) . map ( ( row , index ) => ( {
... row ,
... row ,
successAnalysis : savedByDimension [ row . dimension ] ? . successAnalysis || parsed . factors [ index ] ? . successAnalysis || "" ,
successAnalysis : savedByDimension [ row . dimension ] ? . successAnalysis || parsed . factors [ index ] ? . successAnalysis || "" ,
failedAnalysis : savedByDimension [ row . dimension ] ? . failedAnalysis || parsed . factors [ index ] ? . failedAnalysis || "" ,
failedAnalysis : savedByDimension [ row . dimension ] ? . failedAnalysis || parsed . factors [ index ] ? . failedAnalysis || "" ,
} ) ) ;
} ) ) ;
}
}
return createRows ( ) ;
}
}
function restoreCurrentStep ( savedStep ) {
function restoreCurrentStep ( savedStep ) {
@ -281,7 +296,7 @@ function restoreCurrentStep(savedStep) {
function persistDraft ( ) {
function persistDraft ( ) {
if ( ! draftReady . value ) return ;
if ( ! draftReady . value ) return ;
try {
try {
localStorage . setItem ( getLocalDraftKey ( ) , JSON . stringify ( build Outcome ( ) ) ) ;
localStorage . setItem ( getLocalDraftKey ( ) , JSON . stringify ( build Draft ( ) ) ) ;
} catch ( error ) {
} catch ( error ) {
/ / L o c a l d r a f t i s o n l y a c o n v e n i e n c e ; b a c k e n d s a v e r e m a i n s t h e s o u r c e o f t r u t h .
/ / L o c a l d r a f t i s o n l y a c o n v e n i e n c e ; b a c k e n d s a v e r e m a i n s t h e s o u r c e o f t r u t h .
}
}
@ -291,7 +306,10 @@ function loadDraft() {
try {
try {
const raw = localStorage . getItem ( getLocalDraftKey ( ) ) ;
const raw = localStorage . getItem ( getLocalDraftKey ( ) ) ;
if ( raw ) {
if ( raw ) {
applySavedAnswer ( raw ) ;
const draft = parseSavedAnswer ( raw ) ;
if ( draft ? . stepOne ) stepOneRows . value = restoreRows ( draft . stepOne , createStepOneRows ) ;
else stepOneRows . value = restoreRows ( raw , createStepOneRows ) ;
if ( draft ? . stepTwo ) stepTwoRows . value = restoreRows ( draft . stepTwo , createStepTwoRows ) ;
}
}
} catch ( error ) {
} catch ( error ) {
clearDraft ( ) ;
clearDraft ( ) ;
@ -306,10 +324,10 @@ function clearDraft() {
}
}
}
}
function build Outcome( ) {
function build StepOne Outcome( ) {
return {
return {
title : taskTitle . value ,
title : taskTitle . value ,
factors : factor Rows. value . map ( ( row ) => ( {
factors : stepOne Rows. value . map ( ( row ) => ( {
dimension : row . dimension ,
dimension : row . dimension ,
criteria : row . criteria ,
criteria : row . criteria ,
successAnalysis : row . successAnalysis ,
successAnalysis : row . successAnalysis ,
@ -317,10 +335,12 @@ function buildOutcome() {
} ) ) ,
} ) ) ,
} ;
} ;
}
}
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 buildStepOneValidationPayload ( ) {
function buildStepOneValidationPayload ( ) {
return {
return {
factors : factor Rows. value . map ( ( { dimension , successAnalysis , failedAnalysis } ) => ( {
factors : stepOne Rows. value . map ( ( { dimension , successAnalysis , failedAnalysis } ) => ( {
dimension ,
dimension ,
successAnalysis ,
successAnalysis ,
failedAnalysis ,
failedAnalysis ,
@ -328,21 +348,23 @@ function buildStepOneValidationPayload() {
} ;
} ;
}
}
function build AnswerPayload( saveAction = "SAVE" , persistedStep = currentStep . value + 1 ) {
function build StepOne AnswerPayload( saveAction = "SAVE" , persistedStep = currentStep . value + 1 ) {
return {
return {
step1Answer : JSON . stringify ( build Outcome( ) ) ,
step1Answer : JSON . stringify ( build StepOne Outcome( ) ) ,
currentStep : persistedStep ,
currentStep : persistedStep ,
submitted : false ,
submitted : false ,
saveAction ,
saveAction ,
} ;
} ;
}
}
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 } ; }
async function saveAndGoToStepTwo ( ) {
async function saveAndGoToStepTwo ( ) {
if ( saving . value ) return ;
if ( saving . value ) return ;
saving . value = true ;
saving . value = true ;
try {
try {
await checkProductDevelopmentFactorsStepOne ( buildStepOneValidationPayload ( ) ) ;
await checkProductDevelopmentFactorsStepOne ( buildStepOneValidationPayload ( ) ) ;
await saveStudentTrainingAnswer ( TASK _KEY , build AnswerPayload( "SAVE" , 2 ) ) ;
await saveStudentTrainingAnswer ( TASK _KEY , build StepOne AnswerPayload( "SAVE" , 2 ) ) ;
clearDraft ( ) ;
clearDraft ( ) ;
if ( trainingSteps . value . length < 2 ) {
if ( trainingSteps . value . length < 2 ) {
proxy ? . $modal ? . msgSuccess ( "已保存,当前任务尚未配置第 2 步" ) ;
proxy ? . $modal ? . msgSuccess ( "已保存,当前任务尚未配置第 2 步" ) ;
@ -357,13 +379,27 @@ async function saveAndGoToStepTwo() {
}
}
}
}
async function saveAndGoToStepThree ( ) {
if ( saving . value ) return ;
saving . value = true ;
try {
await checkProductDevelopmentFactorsStepTwo ( buildStepTwoValidationPayload ( ) ) ;
await saveStudentTrainingAnswer ( TASK _KEY , buildStepTwoAnswerPayload ( "SAVE" , 3 ) ) ;
clearDraft ( ) ;
if ( trainingSteps . value . length < 3 ) { proxy ? . $modal ? . msgSuccess ( "已保存,当前任务尚未配置第 3 步" ) ; return ; }
currentStep . value = 2 ;
proxy ? . $modal ? . msgSuccess ( "已保存,已进入第 3 步" ) ;
} catch ( error ) { proxy ? . $modal ? . msgError ? . ( error ? . message || "请完成技术应用和合规与认证分析" ) ; } finally { saving . value = false ; }
}
async function resetTraining ( ) {
async function resetTraining ( ) {
factorRows . value = createFactorRows ( ) ;
stepOneRows . value = createStepOneRows ( ) ;
stepTwoRows . value = createStepTwoRows ( ) ;
currentStep . value = 0 ;
currentStep . value = 0 ;
clearDraft ( ) ;
clearDraft ( ) ;
try {
try {
saving . value = true ;
saving . value = true ;
await saveStudentTrainingAnswer ( TASK _KEY , buildAnswerPayload ( "RESET" , 1 ) ) ;
await saveStudentTrainingAnswer ( TASK _KEY , build StepOne AnswerPayload( "RESET" , 1 ) ) ;
} catch ( error ) {
} catch ( error ) {
/ / C l e a r i n g t h e p a g e s h o u l d s t i l l w o r k l o c a l l y e v e n w h e n b a c k e n d r e s e t f a i l s .
/ / C l e a r i n g t h e p a g e s h o u l d s t i l l w o r k l o c a l l y e v e n w h e n b a c k e n d r e s e t f a i l s .
} finally {
} finally {
@ -373,7 +409,7 @@ async function resetTraining() {
}
}
function exportOutcome ( ) {
function exportOutcome ( ) {
const outcome = build Outcome ( ) ;
const outcome = build Draft ( ) ;
const blob = new Blob ( [ JSON . stringify ( outcome , null , 2 ) ] , { type : "application/json;charset=utf-8" } ) ;
const blob = new Blob ( [ JSON . stringify ( outcome , null , 2 ) ] , { type : "application/json;charset=utf-8" } ) ;
const url = URL . createObjectURL ( blob ) ;
const url = URL . createObjectURL ( blob ) ;
const link = document . createElement ( "a" ) ;
const link = document . createElement ( "a" ) ;