@ -1,10 +1,11 @@
< template >
< template >
< div class = "product-process student-training-shell" >
< main class = "process-core" >
< section class = "task-header" >
< div class = "task-head-main" >
< h1 class = "task-title" > { { taskTitle } } < / h1 >
< p class = "task-brief" >
< div class = "task-brief" >
< span class = "task-brief-text" > { { taskBackground } } < / span >
< button type = "button" class = "task-brief-toggle" @ click = "briefOpen = !briefOpen" >
{ { briefOpen ? "收起" : "展开目标与要求" } }
@ -17,54 +18,49 @@
< b > 实训要求 < / b >
< p > { { taskRequirement } } < / p >
< / span >
< / p >
< / div >
< / div >
< div class = "workbench-head workbench-head--actions-only" >
< div class = "workbench-actions" >
< TrainingMaterialButton
: material - name = "taskConfig?.materialName"
: material - url = "taskConfig?.materialUrl"
: training - data - name = "taskConfig?.trainingDataName"
: training - data - url = "taskConfig?.trainingDataUrl"
: guide - manual - name = "taskConfig?.guideManualName"
: guide - manual - url = "taskConfig?.guideManualUrl"
: ai - manual - name = "taskConfig?.aiManualName"
: ai - manual - url = "taskConfig?.aiManualUrl"
/ >
< button type = "button" class = "outline-action" @click ="exportOutcome" > < el -icon > < Download / > < / e l - i c o n > 导 出 实 训 成 果 < / b u t t o n >
< TrainingMaterialButton :material-name ="taskConfig?.materialName" :material-url ="taskConfig?.materialUrl" :training-data-name ="taskConfig?.trainingDataName" :training-data-url ="taskConfig?.trainingDataUrl" :guide-manual-name ="taskConfig?.guideManualName" :guide-manual-url ="taskConfig?.guideManualUrl" :ai-manual-name ="taskConfig?.aiManualName" :ai-manual-url ="taskConfig?.aiManualUrl" / >
< button type = "button" class = "outline-action" @click ="exportOutcome" >
< el -icon > < Download / > < / e l - i c o n >
导出实训成果
< / button >
< / div >
< / div >
< / section >
< section class = "process-workbench" >
< nav v-if ="trainingSteps.length" class="step-indicator" aria-label="产品开发主要流程步骤" >
< button
v - for = "(step, index) in trainingSteps"
: key = "`${step}-${index}`"
type = "button"
: class = "['step-tab', { active: currentStep === index + 1, complete: currentStep > index + 1 }]"
: aria - label = "`切换至第 ${index + 1} 步:${step}`"
: aria - current = "currentStep === index + 1 ? 'step' : undefined"
: title = "step"
@ click = "currentStep = index + 1"
>
< button v-for ="(step, index) in trainingSteps" :key="`${step}-${index}`" type="button" :class="['step-tab', { active: currentStep === index + 1, complete: currentStep > index + 1 }]" :aria-label="`切换至第 ${index + 1} 步:${step}`" :aria-current="currentStep === index + 1 ? 'step' : undefined" :title="step" @click="currentStep = index + 1" >
< span class = "step-bar" aria -hidden = " true " > < / span >
< span class = "step-no" aria -hidden = " true " > { { String ( index + 1 ) . padStart ( 2 , '0' ) } } < / span >
< span class = "step-no" aria -hidden = " true " > { { String ( index + 1 ) . padStart ( 2 , "0" ) } } < / span >
< span class = "step-name" > { { step } } < / span >
< / button >
< / nav >
< / section >
< section class = "process-step-card" >
< template v-if ="currentStep === 1" >
< div class = "step-title-row" >
< div > < p class = "section-eyebrow" > Step 01 < / p > < h3 > { { trainingSteps [ 0 ] } } < / h3 > < / div >
< div >
< p class = "section-eyebrow" > Step 01 < / p >
< h3 > { { trainingSteps [ 0 ] } } < / h3 >
< / div >
< span class = "step-chip" > 关键任务识别 < / span >
< / div >
< p class = "process-intro" > 互联网产品开发在拆解任务之前 , 团队必须先明确做什么样的产品 、 开发过程中有哪些卡点 。 这是后续排期的前提 。 请判断哪些开发环节需要特别关注 : 关键任务会影响全项目进度与最终落地效果 , 非关键任务可在核心任务约束下灵活协调 。 < / p >
< div class = "training-table-wrap" >
< table class = "process-table" >
< thead > < tr > < th > 阶段 < / th > < th > 任务编号 < / th > < th > 具体工作任务 < / th > < th > 是否关键任务 < / th > < / tr > < / thead >
< thead >
< tr >
< th > 阶段 < / th >
< th > 任务编号 < / th >
< th > 具体工作任务 < / th >
< th > 是否关键任务 < / th >
< / tr >
< / thead >
< tbody >
< tr v-for ="row in processRows" :key="row.taskNo" :class="feedbackClass(row.taskNo)" >
< td class = "stage-cell" > { { row . stage } } < / td >
@ -83,15 +79,24 @@
< / div >
< p v-if ="checkResult" class="classification-summary" :class="{ 'is-success': checkResult.allCorrect }" > 本 次 判 题 : {{ checkResult.correctCount }} / {{ checkResult.totalCount }} 项 分 类 正 确 < / p >
< div class = "task-actions" >
< button type = "button" class = "btn-nav btn-submit" :disabled ="checking" @click ="saveAndGoToStepTwo" > {{ checking ? ' 判 题 中 … ' : ' 保 存 并 进 入 第 2 步 ' }} < / button >
< button type = "button" class = "btn-nav" :disabled ="checking" @click ="resetTraining" > < el -icon > < RefreshLeft / > < / e l - i c o n > 清 空 填 写 < / b u t t o n >
< button type = "button" class = "btn-nav btn-submit" :disabled ="checking" @click ="saveAndGoToStepTwo" > {{ checking ? " 判 题 中 … " : " 保 存 并 进 入 第 2 步 " }} < / button >
< button type = "button" class = "btn-nav" :disabled ="checking" @click ="resetTraining" >
< el -icon > < RefreshLeft / > < / e l - i c o n >
清空填写
< / button >
< / div >
< / template >
< template v -else -if = " currentStep = = = 2 " >
< div class = "step-title-row" >
< div > < p class = "section-eyebrow" > Step 02 < / p > < h3 > { { trainingSteps [ 1 ] } } < / h3 > < / div >
< div >
< p class = "section-eyebrow" > Step 02 < / p >
< h3 > { { trainingSteps [ 1 ] } } < / h3 >
< / div >
< div class = "step-title-actions" >
< button type = "button" class = "outline-action schedule-export-action" @click ="exportScheduleList" > < el -icon > < Download / > < / e l - i c o n > 导 出 列 表 < / b u t t o n >
< button type = "button" class = "outline-action schedule-export-action" @click ="exportScheduleList" >
< el -icon > < Download / > < / e l - i c o n >
导出列表
< / button >
< span class = "step-chip" > 项目排期练习 < / span >
< / div >
< / div >
@ -99,13 +104,23 @@
< p v-if ="!hasCompletedStepOne" class="step-warning" > 请 先 完 成 第 1 步 并 判 题 通 过 ; 关 键 任 务 结 果 会 自 动 带 入 本 表 。 < / p >
< div class = "training-table-wrap" >
< table class = "process-table schedule-table" >
< thead > < tr > < th > 阶段 < / th > < th > 任务编号 < / th > < th > 具体工作任务 < / th > < th > 是否关键任务 < / th > < th > 开始时间 < / th > < th > 完成时间 < / th > < th > 项目依赖关系 < / th > < / tr > < / thead >
< thead >
< tr >
< th > 阶段 < / th >
< th > 任务编号 < / th >
< th > 具体工作任务 < / th >
< th > 是否关键任务 < / th >
< th > 开始时间 < / th >
< th > 完成时间 < / th >
< th > 项目依赖关系 < / th >
< / tr >
< / thead >
< tbody >
< tr v-for ="row in processRows" :key="row.taskNo" >
< td class = "stage-cell" > { { row . stage } } < / td >
< td class = "task-no-cell" > { { row . taskNo } } < / td >
< td class = "task-cell" > { { row . task } } < / td >
< td class = "classification-cell" > { { row . classification || '—' } } < / td >
< td class = "classification-cell" > { { row . classification || "—" } } < / td >
< td > < input v -model = " row.startDate " type = "date" : disabled = "!hasCompletedStepOne || checking" / > < / td >
< td > < input v -model = " row.endDate " type = "date" : disabled = "!hasCompletedStepOne || checking" / > < / td >
< td > < input v -model .trim = " row.dependency " class = "dependency-input" : disabled = "!hasCompletedStepOne || checking" placeholder = "示例:开始-开始( SS) " maxlength = "100" / > < / td >
@ -114,13 +129,19 @@
< / table >
< / div >
< div class = "task-actions" >
< button type = "button" class = "btn-nav btn-submit" : disabled = "checking || !hasCompletedStepOne" @click ="saveAndGoToStepThree" > {{ checking ? ' 校 验 中 … ' : ' 保 存 并 进 入 第 3 步 ' }} < / button >
< button type = "button" class = "btn-nav" :disabled ="checking" @click ="resetStepTwo" > < el -icon > < RefreshLeft / > < / e l - i c o n > 清 空 本 步 < / b u t t o n >
< button type = "button" class = "btn-nav btn-submit" : disabled = "checking || !hasCompletedStepOne" @click ="saveAndGoToStepThree" > {{ checking ? " 校 验 中 … " : " 保 存 并 进 入 第 3 步 " }} < / button >
< button type = "button" class = "btn-nav" :disabled ="checking" @click ="resetStepTwo" >
< el -icon > < RefreshLeft / > < / e l - i c o n >
清空本步
< / button >
< / div >
< / template >
< template v -else -if = " currentStep = = = 3 " >
< div class = "step-title-row" >
< div > < p class = "section-eyebrow" > Step 03 < / p > < h3 > { { trainingSteps [ 2 ] } } < / h3 > < / div >
< div >
< p class = "section-eyebrow" > Step 03 < / p >
< h3 > { { trainingSteps [ 2 ] } } < / h3 >
< / div >
< span class = "step-chip" > 甘特图绘制 < / span >
< / div >
< p class = "process-intro" > 根据上表数据 , 使用工具绘制甘特图 , 并上传 。 < / p >
@ -129,7 +150,7 @@
< label class = "gantt-upload-trigger" : class = "{ disabled: !stepTwoPassed || uploadingGantt }" >
< input type = "file" accept = "image/png,image/jpeg,image/webp,image/gif" : disabled = "!stepTwoPassed || uploadingGantt" @change ="uploadGanttImage" / >
< el -icon > < Upload / > < / e l - i c o n >
< strong > { { uploadingGantt ? '上传中…' : ganttImage . url ? '替换甘特图' : '上传甘特图' } } < / strong >
< strong > { { uploadingGantt ? "上传中…" : ganttImage . url ? "替换甘特图" : "上传甘特图" } } < / strong >
< span > 支持 PNG 、 JPG 、 JPEG 、 WEBP 、 GIF < / span >
< / label >
< figure v-if ="ganttImage.url" class="gantt-preview" >
@ -140,49 +161,73 @@
< section class = "gantt-guide" >
< h4 > 推荐工具如下 , 自行选择作图工具 : < / h4 >
< ol >
< li > Excel / WPS表格 , 利用堆积条形图制作 , 可参考 《 实训操作说明 》 完成 。 < button type = "button" @click ="downloadPracticeGuide" > 下 载 实 训 操 作 说 明 < / button > < / li >
< li > 下载Excel模板 , 填写关键信息 , 完成甘特图绘制 。 < button type = "button" @click ="downloadGanttTemplate" > 下 载 甘 特 图 绘 制 模 板 < / button > < / li >
< li >
Excel / WPS表格 , 利用堆积条形图制作 , 可参考 《 实训操作说明 》 完成 。
< button type = "button" @click ="downloadPracticeGuide" > 下 载 实 训 操 作 说 明 < / button >
< / li >
< li >
下载Excel模板 , 填写关键信息 , 完成甘特图绘制 。
< button type = "button" @click ="downloadGanttTemplate" > 下 载 甘 特 图 绘 制 模 板 < / button >
< / li >
< li > 导出步骤二信息表 , 上传到通用AI绘图模型 , 通过大模型绘制甘特图 。 参考提示词 : 请根据产品WBS任务拆解与排期表生成甘特图用于后续项目管理 。 < / li >
< / ol >
< / section >
< div class = "task-actions" >
< button type = "button" class = "btn-nav btn-submit" : disabled = "uploadingGantt || !stepTwoPassed" @click ="saveAndGoToStepFour" > 保 存 并 进 入 第 4 步 < el -icon > < ArrowRight / > < / e l - i c o n > < / b u t t o n >
< button type = "button" class = "btn-nav" :disabled ="uploadingGantt" @click ="resetStepThree" > < el -icon > < RefreshLeft / > < / e l - i c o n > 清 空 本 步 < / b u t t o n >
< button type = "button" class = "btn-nav btn-submit" : disabled = "uploadingGantt || !stepTwoPassed" @click ="saveAndGoToStepFour" >
保存并进入第 4 步
< el -icon > < ArrowRight / > < / e l - i c o n >
< / button >
< button type = "button" class = "btn-nav" :disabled ="uploadingGantt" @click ="resetStepThree" >
< el -icon > < RefreshLeft / > < / e l - i c o n >
清空本步
< / button >
< / div >
< / template >
< template v -else -if = " currentStep = = = 4 " >
< div class = "step-title-row" >
< div > < p class = "section-eyebrow" > Step 04 < / p > < h3 > { { trainingSteps [ 3 ] } } < / h3 > < / div >
< div >
< p class = "section-eyebrow" > Step 04 < / p >
< h3 > { { trainingSteps [ 3 ] } } < / h3 >
< / div >
< span class = "step-chip" > 并行协同复盘 < / span >
< / div >
< p v-if ="!stepThreePassed" class="step-warning" > 请 先 上 传 并 保 存 第 3 步 甘 特 图 , 再 完 成 本 步 复 盘 。 < / p >
< section class = "process-reflection" : class = "{ disabled: !stepThreePassed }" >
< label class = "reflection-field" >
< span > ( 1 ) 观察甘特图 , 思考电商运营在产品开发过程中 , 可以提前介入同步完成哪些工作 , 以缩短运营周期 、 提高效率 。 ( 如 “ 预热蓄水 ” “ 内容种草 ” 等 ) < / span >
< textarea v -model .trim = " stepFourForm.parallelWorkAnalysis " : disabled = "!stepThreePassed || checking" placeholder = "请结合甘特图说明可同步推进的电商运营工作、介入节点及预期效率提升" / >
< textarea v -model .trim = " stepFourForm.parallelWorkAnalysis " : disabled = "!stepThreePassed || checking" placeholder = "请结合甘特图说明可同步推进的电商运营工作、介入节点及预期效率提升" > < / textarea >
< / label >
< div class = "reflection-question" > ( 2 ) 在互联网产品开发管理中 , 你认为哪些环节可以借助AI工具提升效率 ? 请列举至少2个具体应用场景 。 < / div >
< div class = "scenario-grid" >
< label class = "reflection-field" > < span > 场景1 : < / span > < textarea v -model .trim = " stepFourForm.aiScenarioOne " : disabled = "!stepThreePassed || checking" placeholder = "例如:使用 AI 汇总竞品评价与用户需求, 辅助生成PRD初稿" / > < / label >
< label class = "reflection-field" > < span > 场景2 : < / span > < textarea v -model .trim = " stepFourForm.aiScenarioTwo " : disabled = "!stepThreePassed || checking" placeholder = "例如:使用 AI 生成电商详情页文案、主图脚本或运营素材" / > < / label >
< label class = "reflection-field" >
< span > 场景1 : < / span >
< textarea v -model .trim = " stepFourForm.aiScenarioOne " : disabled = "!stepThreePassed || checking" placeholder = "例如:使用 AI 汇总竞品评价与用户需求, 辅助生成PRD初稿" > < / textarea >
< / label >
< label class = "reflection-field" >
< span > 场景2 : < / span >
< textarea v -model .trim = " stepFourForm.aiScenarioTwo " : disabled = "!stepThreePassed || checking" placeholder = "例如:使用 AI 生成电商详情页文案、主图脚本或运营素材" > < / textarea >
< / label >
< / div >
< / section >
< div class = "task-actions" >
< button type = "button" class = "btn-nav btn-submit" : disabled = "checking || !stepThreePassed" @click ="submitTraining" > 提 交 任 务 < el -icon > < CircleCheck / > < / e l - i c o n > < / b u t t o n >
< button type = "button" class = "btn-nav" :disabled ="checking" @click ="resetStepFour" > < el -icon > < RefreshLeft / > < / e l - i c o n > 清 空 本 步 < / b u t t o n >
< button type = "button" class = "btn-nav btn-submit" : disabled = "checking || !stepThreePassed" @click ="submitTraining" >
提交任务
< el -icon > < CircleCheck / > < / e l - i c o n >
< / button >
< button type = "button" class = "btn-nav" :disabled ="checking" @click ="resetStepFour" >
< el -icon > < RefreshLeft / > < / e l - i c o n >
清空本步
< / button >
< / div >
< / template >
< div v -else class = "step-placeholder" > < p > 第 { { currentStep } } 步内容待配置 < / p > < span > 第 1 步答案已保存 , 可从上方步骤栏返回查看 。 < / span > < / div >
< / section >
< div v -else class = "step-placeholder" >
< p > 第 { { currentStep } } 步内容待配置 < / p >
< span > 第 1 步答案已保存 , 可从上方步骤栏返回查看 。 < / span >
< / div >
< / section >
< / main >
< TrainingAiSidebar
study - tip = "请根据产品开发流程中任务对总进度和最终落地效果的影响,判断其是否为关键任务。"
reference - title = "关键任务判断提示"
reference - text = "· 关键任务:直接影响产品定义、核心技术验证、量产交付或上市节奏<br />· 非关键任务:可在核心任务约束下进行资源协调,不直接阻塞整体排期"
: progress - items = "progressItems"
/ >
< TrainingAiSidebar study -tip = " 请 根 据 产 品 开 发 流 程 中 任 务 对 总 进 度 和 最 终 落 地 效 果 的 影 响 , 判 断 其 是 否 为 关 键 任 务 。 " reference -title = " 关 键 任 务 判 断 提 示 " reference -text = " · 关 键 任 务 : 直 接 影 响 产 品 定 义 、 核 心 技 术 验 证 、 量 产 交 付 或 上 市 节 奏 < br / > · 非关键任务 : 可在核心任务约束下进行资源协调 , 不直接阻塞整体排期 " :progress-items=" progressItems " > < / TrainingAiSidebar >
< / div >
< / template >
@ -207,11 +252,25 @@ const GANTT_TEMPLATE_NAME = "3.项目进度管理表-项目进度甘特图模版
const CLASSIFICATION _OPTIONS = [ "关键任务" , "非关键任务" ] ;
const DEFAULT _STEPS = [ "产品开发关键任务识别" , "任务依赖关系分析" , "开发排期制定" , "流程复盘优化" ] ;
const PROCESS _TASKS = [
[ "需求锚定" , "1.1" , "竞品调研与用户需求分析" ] , [ "" , "1.2" , "产品概念确认与核心卖点提炼" ] ,
[ "产品定义" , "2.1" , "产品需求文档( PRD) 撰写与评审" ] , [ "" , "2.2" , "外观ID设计" ] , [ "" , "2.3" , "功能规格确认" ] ,
[ "开发验证" , "3.1" , "硬件方案选型与原理图设计" ] , [ "" , "3.2" , "软件架构及软硬件协同设计" ] , [ "" , "3.3" , "结构设计评审" ] , [ "" , "3.4" , "功能手板制作与外观评审" ] , [ "" , "3.5" , "工程验证测试与设计验证测试" ] , [ "" , "3.6" , "PVT小批量试产" ] ,
[ "供应链对接" , "4.1" , "供应商评估与选定" ] , [ "" , "4.2" , "模具正式开模与试模修模" ] , [ "" , "4.3" , "产品认证及质量检测" ] , [ "" , "4.4" , "BOM成本核算与采购合同签订" ] ,
[ "上市推广" , "5.1" , "产品量产及入库" ] , [ "" , "5.2" , "电商详情页全渠道上线" ] , [ "" , "5.3" , "正式开售与首销爆发" ] , [ "" , "5.4" , "首批用户评价监控与差评预警" ] ,
[ "需求锚定" , "1.1" , "竞品调研与用户需求分析" ] ,
[ "" , "1.2" , "产品概念确认与核心卖点提炼" ] ,
[ "产品定义" , "2.1" , "产品需求文档( PRD) 撰写与评审" ] ,
[ "" , "2.2" , "外观ID设计" ] ,
[ "" , "2.3" , "功能规格确认" ] ,
[ "开发验证" , "3.1" , "硬件方案选型与原理图设计" ] ,
[ "" , "3.2" , "软件架构及软硬件协同设计" ] ,
[ "" , "3.3" , "结构设计评审" ] ,
[ "" , "3.4" , "功能手板制作与外观评审" ] ,
[ "" , "3.5" , "工程验证测试与设计验证测试" ] ,
[ "" , "3.6" , "PVT小批量试产" ] ,
[ "供应链对接" , "4.1" , "供应商评估与选定" ] ,
[ "" , "4.2" , "模具正式开模与试模修模" ] ,
[ "" , "4.3" , "产品认证及质量检测" ] ,
[ "" , "4.4" , "BOM成本核算与采购合同签订" ] ,
[ "上市推广" , "5.1" , "产品量产及入库" ] ,
[ "" , "5.2" , "电商详情页全渠道上线" ] ,
[ "" , "5.3" , "正式开售与首销爆发" ] ,
[ "" , "5.4" , "首批用户评价监控与差评预警" ] ,
] ;
const defaultTask = {
title : "产品开发主要流程" ,
@ -266,13 +325,20 @@ function parseArray(value, fallback) {
const parsed = JSON . parse ( value ) ;
return Array . isArray ( parsed ) && parsed . length ? parsed . map ( ( item ) => String ( item ? . name ? ? item ) . trim ( ) ) . filter ( Boolean ) : fallback ;
} catch ( error ) {
const list = String ( value ) . split ( /[; ;|]/ ) . map ( ( item ) => item . trim ( ) ) . filter ( Boolean ) ;
const list = String ( value )
. split ( /[; ;|]/ )
. map ( ( item ) => item . trim ( ) )
. filter ( Boolean ) ;
return list . length ? list : fallback ;
}
}
async function loadTaskConfig ( ) {
try { taskConfig . value = ( await getTrainingTaskByKey ( TASK _KEY ) ) ? . data || null ; } catch ( error ) { taskConfig . value = null ; }
try {
taskConfig . value = ( await getTrainingTaskByKey ( TASK _KEY ) ) ? . data || null ;
} catch ( error ) {
taskConfig . value = null ;
}
}
async function loadSavedAnswer ( ) {
@ -379,11 +445,17 @@ function buildStepFourOutcome() {
return { ... stepFourForm . value } ;
}
function buildDraft ( ) { return { step1 : buildOutcome ( ) , step2 : buildStepTwoOutcome ( ) , step3 : buildStepThreeOutcome ( ) , step4 : buildStepFourOutcome ( ) , currentStep : currentStep . value } ; }
function buildDraft ( ) {
return { step1 : buildOutcome ( ) , step2 : buildStepTwoOutcome ( ) , step3 : buildStepThreeOutcome ( ) , step4 : buildStepFourOutcome ( ) , currentStep : currentStep . value } ;
}
function persistDraft ( ) {
if ( ! draftReady . value ) return ;
try { localStorage . setItem ( getLocalDraftKey ( ) , JSON . stringify ( buildDraft ( ) ) ) ; } catch ( error ) { /* Browser draft is optional. */ }
try {
localStorage . setItem ( getLocalDraftKey ( ) , JSON . stringify ( buildDraft ( ) ) ) ;
} catch ( error ) {
/* Browser draft is optional. */
}
}
function loadDraft ( ) {
@ -397,11 +469,17 @@ function loadDraft() {
if ( draft ? . step4 ) restoreStepFourOutcome ( draft . step4 ) ;
if ( Number ( draft ? . currentStep ) > 0 ) currentStep . value = Number ( draft . currentStep ) ;
}
} catch ( error ) { clearDraft ( ) ; }
} catch ( error ) {
clearDraft ( ) ;
}
}
function clearDraft ( ) {
try { localStorage . removeItem ( getLocalDraftKey ( ) ) ; } catch ( error ) { /* Ignore storage failures. */ }
try {
localStorage . removeItem ( getLocalDraftKey ( ) ) ;
} catch ( error ) {
/* Ignore storage failures. */
}
}
function feedbackClass ( taskNo ) {
@ -602,9 +680,7 @@ function exportScheduleList() {
项目依赖关系 : row . dependency ,
} ) ) ;
const worksheet = XLSX . utils . json _to _sheet ( rows ) ;
worksheet [ "!cols" ] = [
{ wch : 12 } , { wch : 10 } , { wch : 28 } , { wch : 14 } , { wch : 14 } , { wch : 14 } , { wch : 22 } ,
] ;
worksheet [ "!cols" ] = [ { wch : 12 } , { wch : 10 } , { wch : 28 } , { wch : 14 } , { wch : 14 } , { wch : 14 } , { wch : 22 } ] ;
const workbook = XLSX . utils . book _new ( ) ;
XLSX . utils . book _append _sheet ( workbook , worksheet , "WBS排期表" ) ;
XLSX . writeFile ( workbook , "产品开发WBS任务拆解与排期表.xlsx" ) ;
@ -712,10 +788,6 @@ function exportOutcome() {
min - width : 0 ;
}
. task - title {
margin : 0 ;
color : var ( -- student - text ) ;
@ -820,7 +892,11 @@ function exportOutcome() {
font - weight : 600 ;
line - height : 1 ;
cursor : pointer ;
transition : border - color 0.16 s ease , background 0.16 s ease , color 0.16 s ease , box - shadow 0.16 s ease ;
transition :
border - color 0.16 s ease ,
background 0.16 s ease ,
color 0.16 s ease ,
box - shadow 0.16 s ease ;
& : hover {
border - color : # 2563 eb ;
@ -1046,7 +1122,12 @@ tr.is-incorrect .classification-feedback {
line - height : 1 ;
text - shadow : none ;
cursor : pointer ;
transition : transform 0.16 s ease , border - color 0.16 s ease , color 0.16 s ease , background 0.16 s ease , box - shadow 0.16 s ease ;
transition :
transform 0.16 s ease ,
border - color 0.16 s ease ,
color 0.16 s ease ,
background 0.16 s ease ,
box - shadow 0.16 s ease ;
}
. btn - nav : hover : not ( : disabled ) {
@ -1119,10 +1200,18 @@ tr.is-incorrect .classification-feedback {
}
/* 排期表列宽 */
. schedule - table { min - width : 1020 px ; }
. schedule - table . stage - cell { width : 7 % ; }
. schedule - table . task - no - cell { width : 7 % ; }
. schedule - table . task - cell { width : 18 % ; }
. schedule - table {
min - width : 1020 px ;
}
. schedule - table . stage - cell {
width : 7 % ;
}
. schedule - table . task - no - cell {
width : 7 % ;
}
. schedule - table . task - cell {
width : 18 % ;
}
. classification - cell {
width : 12 % ;
@ -1135,14 +1224,20 @@ tr.is-incorrect .classification-feedback {
. schedule - table th : nth - child ( 5 ) ,
. schedule - table td : nth - child ( 5 ) ,
. schedule - table th : nth - child ( 6 ) ,
. schedule - table td : nth - child ( 6 ) { width : 18 % ; }
. schedule - table . dependency - input { min - width : 0 ; }
. schedule - table td : nth - child ( 6 ) {
width : 18 % ;
}
. schedule - table . dependency - input {
min - width : 0 ;
}
. schedule - table . dependency - input : : placeholder {
color : # 9 ca3af ;
opacity : 1 ;
}
. schedule - table th : nth - child ( 7 ) ,
. schedule - table td : nth - child ( 7 ) { width : 20 % ; }
. schedule - table td : nth - child ( 7 ) {
width : 20 % ;
}
. schedule - table input : disabled {
cursor : not - allowed ;
opacity : 0.6 ;
@ -1268,7 +1363,9 @@ tr.is-incorrect .classification-feedback {
font - weight : 700 ;
line - height : 1.5 ;
cursor : pointer ;
transition : border - color 0.16 s ease , background 0.16 s ease ;
transition :
border - color 0.16 s ease ,
background 0.16 s ease ;
}
. gantt - guide button : hover {
@ -1443,7 +1540,9 @@ tr.is-incorrect .classification-feedback {
}
/* 不显示顶部引导线 */
. step - bar { display : none ! important ; }
. step - bar {
display : none ! important ;
}
/* 编号小徽章 */
. step - no {
@ -1464,10 +1563,14 @@ tr.is-incorrect .classification-feedback {
line - height : 1 ! important ;
transition : all 0.16 s ease ! important ;
& : : after { display : none ! important ; }
& : : after {
display : none ! important ;
}
}
. step - name { color : inherit ! important ; }
. step - name {
color : inherit ! important ;
}
/* 未到达项 hover: 教育蓝边浅底 */
& : hover : not ( . active ) : not ( . complete ) {
@ -1476,7 +1579,10 @@ tr.is-incorrect .classification-feedback {
color : # 2563 eb ! important ;
z - index : 1 ! important ;
. step - no { background : # 2563 eb ! important ; color : # ffffff ! important ; }
. step - no {
background : # 2563 eb ! important ;
color : # ffffff ! important ;
}
}
/* 已完成:绿色编号徽章 */
@ -1484,14 +1590,20 @@ tr.is-incorrect .classification-feedback {
border - color : # e5e7eb ! important ;
color : # 374151 ! important ;
. step - no { background : rgba ( 43 , 164 , 113 , 0.12 ) ! important ; color : # 16915 f ! important ; }
. step - no {
background : rgba ( 43 , 164 , 113 , 0.12 ) ! important ;
color : # 16915 f ! important ;
}
& : hover {
border - color : # 2 ba471 ! important ;
background : # f0fdf6 ! important ;
z - index : 1 ! important ;
. step - no { background : # 2 ba471 ! important ; color : # ffffff ! important ; }
. step - no {
background : # 2 ba471 ! important ;
color : # ffffff ! important ;
}
}
}
@ -1503,7 +1615,10 @@ tr.is-incorrect .classification-feedback {
font - weight : 700 ! important ;
z - index : 2 ! important ;
. step - no { background : # ffffff ! important ; color : # 2563 eb ! important ; }
. step - no {
background : # ffffff ! important ;
color : # 2563 eb ! important ;
}
& : hover {
background : # 1 d4ed8 ! important ;
@ -1537,7 +1652,11 @@ tr.is-incorrect .classification-feedback {
background : # ffffff ! important ;
font - size : 13 px ! important ;
font - weight : 600 ! important ;
transition : border - color 0.16 s ease , background 0.16 s ease , color 0.16 s ease , box - shadow 0.16 s ease ! important ;
transition :
border - color 0.16 s ease ,
background 0.16 s ease ,
color 0.16 s ease ,
box - shadow 0.16 s ease ! important ;
& : hover {
border - color : # 2563 eb ! important ;
@ -1590,7 +1709,11 @@ tr.is-incorrect .classification-feedback {
font - size : 13 px ! important ;
font - weight : 600 ! important ;
line - height : 1 ! important ;
transition : border - color 0.16 s ease , background 0.16 s ease , color 0.16 s ease , box - shadow 0.16 s ease ! important ;
transition :
border - color 0.16 s ease ,
background 0.16 s ease ,
color 0.16 s ease ,
box - shadow 0.16 s ease ! important ;
}
# app . studentLayout section . app - main : has ( . product - process ) . workbench - actions > button . outline - action : hover {
@ -1660,7 +1783,11 @@ tr.is-incorrect .classification-feedback {
font - size : 13 px ! important ;
font - weight : 700 ! important ;
line - height : 1 ! important ;
transition : transform 0.16 s ease , border - color 0.16 s ease , background 0.16 s ease , box - shadow 0.16 s ease ! important ;
transition :
transform 0.16 s ease ,
border - color 0.16 s ease ,
background 0.16 s ease ,
box - shadow 0.16 s ease ! important ;
}
# app . studentLayout section . app - main : has ( . product - process ) . task - actions > button . el - icon {