diff --git a/.env.development b/.env.development index d7062c9..acde742 100644 --- a/.env.development +++ b/.env.development @@ -7,7 +7,7 @@ # @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE ### # 页面标题 -VITE_APP_TITLE = 电商互联网产品开发实训系统 +VITE_APP_TITLE = 互联网产品开发实训系统 # 开发环境配置 VITE_APP_ENV = 'development' diff --git a/.env.production b/.env.production index 67fa8fd..d74b828 100644 --- a/.env.production +++ b/.env.production @@ -7,14 +7,14 @@ # @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE ### # 页面标题 -VITE_APP_TITLE = 电商互联网产品开发实训系统 +VITE_APP_TITLE = 互联网产品开发实训系统 # 生产环境配置 VITE_APP_ENV = 'production' # 若依管理系统/生产环境 -VITE_APP_BASE_API = '/' -VITE_APP_AI_API = 'https://whmnjy.sztzjy.com:8764/' -VITE_APP_BI_URL = 'https://dshlw.sztzjy.com:196' -# 是否在打包时开启压缩,支持 gzip 和 brotli -VITE_BUILD_COMPRESS = gzip +VITE_APP_BASE_API = '/' +VITE_APP_AI_API = 'https://whmnjy.sztzjy.com:8764/' +VITE_APP_BI_URL = 'https://dshlw.sztzjy.com:196' +# 是否在打包时开启压缩,支持 gzip 和 brotli +VITE_BUILD_COMPRESS = gzip diff --git a/.env.staging b/.env.staging index a296a2a..5152829 100644 --- a/.env.staging +++ b/.env.staging @@ -1,5 +1,5 @@ # 页面标题 -VITE_APP_TITLE = 电商互联网产品开发实训系统 +VITE_APP_TITLE = 互联网产品开发实训系统 # 生产环境配置 VITE_APP_ENV = 'staging' @@ -8,4 +8,4 @@ VITE_APP_ENV = 'staging' VITE_APP_BASE_API = '/stage-api' # 是否在打包时开启压缩,支持 gzip 和 brotli -VITE_BUILD_COMPRESS = gzip \ No newline at end of file +VITE_BUILD_COMPRESS = gzip diff --git a/.gitignore b/.gitignore index 1da7204..2887a3c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ selenium-debug.log # Editor directories and files .idea +.workbuddy .cursor/ .vscode *.suo diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md index fe13175..1f1d9d5 100644 --- a/PROJECT_CONTEXT.md +++ b/PROJECT_CONTEXT.md @@ -1,4 +1,4 @@ -# 电商互联网产品开发实训系统 - 项目认知 +# 互联网产品开发实训系统 - 项目认知 > 本文件为 AI 助手提供项目上下文,每次新对话开始时会读取此文件。 @@ -6,7 +6,7 @@ ## 1. 项目基本信息 -- **项目名称**:电商互联网产品开发实训系统 +- **项目名称**:互联网产品开发实训系统 - **技术栈**:Vue 3 (Composition API) + Vite 3 + Element Plus 2.2.x + Pinia + Vue Router 4 + ECharts 5.4 + SCSS - **基础框架**:RuoYi-Vue 3.8.6(若依前后端分离系统增强版) - **项目路径**:`D:\TZ\dianshang-qianduan` 或 `d:\TZ\dianshang-qianduan` diff --git a/index.html b/index.html index 78ccecf..aad1267 100644 --- a/index.html +++ b/index.html @@ -1,352 +1,363 @@ - + + + + + + + + 互联网产品开发实训系统 + + - - - -
-
-
- -
-
Teaching Lab System
-

正在初始化教学环境

-

正在同步课程资源、实训数据与学习进度,请稍候。

- - - - - + + diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue index 8c7554f..ce2d91f 100644 --- a/src/layout/components/Navbar.vue +++ b/src/layout/components/Navbar.vue @@ -1,871 +1,908 @@ @@ -1005,160 +1044,170 @@ onMounted(() => { diff --git a/src/utils/trainingOutcomePdf.js b/src/utils/trainingOutcomePdf.js index afae289..4b510b3 100644 --- a/src/utils/trainingOutcomePdf.js +++ b/src/utils/trainingOutcomePdf.js @@ -27,11 +27,16 @@ function normalizeList(value) { } catch { // 兼容历史任务中使用换行保存的步骤或目标。 } - return text.split(/\r?\n/).map((item) => item.replace(/^\s*(?:\d+[、..]|[-•])\s*/, "").trim()).filter(Boolean); + return text + .split(/\r?\n/) + .map((item) => item.replace(/^\s*(?:\d+[、..]|[-•])\s*/, "").trim()) + .filter(Boolean); } function safeFileName(value) { - return normalizeText(value || "实训成果").replace(/[\\/:*?\"<>|]/g, "_").slice(0, 80); + return normalizeText(value || "实训成果") + .replace(/[\\/:*?\"<>|]/g, "_") + .slice(0, 80); } function sectionMarkup(title, body) { @@ -67,8 +72,7 @@ function findStepNodes(scope) { function printableCopy(node) { if (!node) return null; const copy = node.cloneNode(true); - copy.querySelectorAll("input, textarea, select, button, .training-task-toolbar, .training-ai-sidebar, .step-actions, .step-navigation, .el-input__suffix, script, style") - .forEach((element) => element.remove()); + copy.querySelectorAll("input, textarea, select, button, .training-task-toolbar, .training-ai-sidebar, .step-actions, .step-navigation, .el-input__suffix, script, style").forEach((element) => element.remove()); return copy; } @@ -116,7 +120,11 @@ function controlLabel(control, index) { function controlValue(control) { const type = String(control.type || "").toLowerCase(); if ((type === "checkbox" || type === "radio") && !control.checked) return ""; - if (control.tagName === "SELECT") return Array.from(control.selectedOptions || []).map((option) => compactText(option.textContent)).filter(Boolean).join("、"); + if (control.tagName === "SELECT") + return Array.from(control.selectedOptions || []) + .map((option) => compactText(option.textContent)) + .filter(Boolean) + .join("、"); return normalizeText(control.value); } @@ -138,7 +146,11 @@ function collectDomAnswers(stepNodes) { function parseAnswer(value) { if (typeof value !== "string") return value; - try { return JSON.parse(value); } catch { return value; } + try { + return JSON.parse(value); + } catch { + return value; + } } // 学生作答 JSON 的英文键统一映射为中文标签。 @@ -146,239 +158,624 @@ function parseAnswer(value) { // 因此 smallBatchWeight、small Batch Weight、"small-batch-weight" 都能命中同一条。 const ANSWER_LABELS = { // 通用字段 - title: "标题", name: "名称", label: "名称", type: "类型", level: "等级", - description: "描述", content: "内容", value: "数值", values: "各渠道转化率", - count: "数量", percentage: "占比", ratio: "占比", score: "得分", scores: "得分明细", - weight: "权重", weights: "权重", priority: "优先级", reason: "理由", analysis: "分析", - result: "结果", answer: "学生填写内容", answers: "答案", selected: "选择结果", - summary: "总结", conclusion: "结论", recommendation: "建议", strategy: "策略", - strategies: "策略", decision: "决策结论", month: "月份", time: "时间", phase: "投放阶段", - stage: "旅程阶段", stageid: "旅程阶段", index: "序号", no: "序号", section: "问卷分区", - options: "题目选项", task: "实训要求", taskno: "任务编号", classification: "分类", - dimension: "维度", dimensions: "维度得分项", channel: "渠道", channels: "渠道列表", - startdate: "开始时间", enddate: "完成时间", dependency: "依赖关系", metric: "测试指标", - scenario: "场景", scene: "场景", condition: "适用条件", conditions: "适用条件", - measure: "应对措施", response: "应对措施", process: "处理流程", rule: "核心规则", - total: "合计", amount: "金额", quantity: "数量", unitprice: "单价", unitcost: "单台综合成本", - cost: "成本", price: "定价", brand: "品牌", feature: "功能", file: "附件文件", - filetype: "文件类型", size: "文件大小", sheetname: "工作表名", originalfilename: "原始文件名", - totalrows: "总行数", totalcolumns: "总列数", imagename: "上传图片名称", + title: "标题", + name: "名称", + label: "名称", + type: "类型", + level: "等级", + description: "描述", + content: "内容", + value: "数值", + values: "各渠道转化率", + count: "数量", + percentage: "占比", + ratio: "占比", + score: "得分", + scores: "得分明细", + weight: "权重", + weights: "权重", + priority: "优先级", + reason: "理由", + analysis: "分析", + result: "结果", + answer: "学生填写内容", + answers: "答案", + selected: "选择结果", + summary: "总结", + conclusion: "结论", + recommendation: "建议", + strategy: "策略", + strategies: "策略", + decision: "决策结论", + month: "月份", + time: "时间", + phase: "投放阶段", + stage: "旅程阶段", + stageid: "旅程阶段", + index: "序号", + no: "序号", + section: "问卷分区", + options: "题目选项", + task: "实训要求", + taskno: "任务编号", + classification: "分类", + dimension: "维度", + dimensions: "维度得分项", + channel: "渠道", + channels: "渠道列表", + startdate: "开始时间", + enddate: "完成时间", + dependency: "依赖关系", + metric: "测试指标", + scenario: "场景", + scene: "场景", + condition: "适用条件", + conditions: "适用条件", + measure: "应对措施", + response: "应对措施", + process: "处理流程", + rule: "核心规则", + total: "合计", + amount: "金额", + quantity: "数量", + unitprice: "单价", + unitcost: "单台综合成本", + cost: "成本", + price: "定价", + brand: "品牌", + feature: "功能", + file: "附件文件", + filetype: "文件类型", + size: "文件大小", + sheetname: "工作表名", + originalfilename: "原始文件名", + totalrows: "总行数", + totalcolumns: "总列数", + imagename: "上传图片名称", // PEST / SWOT 分析 - political: "政治环境", economic: "经济环境", social: "社会环境", technology: "技术环境", - strengths: "优势(S)", weaknesses: "劣势(W)", opportunities: "机会(O)", threats: "威胁(T)", - so: "SO策略", wo: "WO策略", st: "ST策略", wt: "WT策略", swot: "SWOT分析", pest: "PEST分析", - opportunity: "机会", risk: "风险", factor: "关键因素", entrystrategy: "市场切入策略", - competitionconclusion: "竞争态势结论", overallassessment: "总体评估", marketdecision: "市场进入结论", - decisionreason: "结论理由", riskone: "核心风险点一", risktwo: "核心风险点二", - coreriskone: "核心风险点一", corerisktwo: "核心风险点二", otherrisk: "其他风险点", - otherrisks: "其他风险点", supplements: "补充要点", + political: "政治环境", + economic: "经济环境", + social: "社会环境", + technology: "技术环境", + strengths: "优势(S)", + weaknesses: "劣势(W)", + opportunities: "机会(O)", + threats: "威胁(T)", + so: "SO策略", + wo: "WO策略", + st: "ST策略", + wt: "WT策略", + swot: "SWOT分析", + pest: "PEST分析", + opportunity: "机会", + risk: "风险", + factor: "关键因素", + entrystrategy: "市场切入策略", + competitionconclusion: "竞争态势结论", + overallassessment: "总体评估", + marketdecision: "市场进入结论", + decisionreason: "结论理由", + riskone: "核心风险点一", + risktwo: "核心风险点二", + coreriskone: "核心风险点一", + corerisktwo: "核心风险点二", + otherrisk: "其他风险点", + otherrisks: "其他风险点", + supplements: "补充要点", // 供应渠道评估 - smallbatchweight: "小批量试产权重", largebatchweight: "大批量量产权重", - trialweight: "小批量试产权重", massweight: "大批量量产权重", - smallbatchchannels: "小批量试产渠道评分", largebatchchannels: "大批量量产渠道评分", - weightedtotal: "加权得分", weightedscore: "加权得分", bestchannel: "最优渠道", - secondchannel: "次优渠道", differenceanalysis: "差异分析", - procurementrecommendation: "综合采购建议", risks: "风险分析", - items: "数据项", rows: "填写数据", comparisons: "对比分析", + smallbatchweight: "小批量试产权重", + largebatchweight: "大批量量产权重", + trialweight: "小批量试产权重", + massweight: "大批量量产权重", + smallbatchchannels: "小批量试产渠道评分", + largebatchchannels: "大批量量产渠道评分", + weightedtotal: "加权得分", + weightedscore: "加权得分", + bestchannel: "最优渠道", + secondchannel: "次优渠道", + differenceanalysis: "差异分析", + procurementrecommendation: "综合采购建议", + risks: "风险分析", + items: "数据项", + rows: "填写数据", + comparisons: "对比分析", // BOM 与成本核算 - materialname: "物料名称", specification: "选型规格参数", expensename: "费用名称", - totalamount: "BOM成本合计", smallbatchconstraint: "小批量试产BOM成本约束", - largebatchconstraint: "大批量量产BOM成本约束", smallbatchlimit: "小批量试产BOM成本约束", - largebatchlimit: "大批量量产BOM成本约束", smallbatchtotal: "小批量BOM成本合计", - largebatchtotal: "大批量BOM成本合计", smallbatchunitcost: "小批量单台成本", - largebatchunitcost: "大批量单台成本", smallbatchmanufacturingcost: "小批量制造成本", - largebatchmanufacturingcost: "大批量制造成本", smallbatchunittotalcost: "小批量单台总成本", - largebatchunittotalcost: "大批量单台总成本", smallcost: "小批量成本", largecost: "大批量成本", - smallbatchchart: "小批量成本结构图", largebatchchart: "大批量成本结构图", - optimizationplan: "降本优化方案", costoptimization: "成本优化方案", + materialname: "物料名称", + specification: "选型规格参数", + expensename: "费用名称", + totalamount: "BOM成本合计", + smallbatchconstraint: "小批量试产BOM成本约束", + largebatchconstraint: "大批量量产BOM成本约束", + smallbatchlimit: "小批量试产BOM成本约束", + largebatchlimit: "大批量量产BOM成本约束", + smallbatchtotal: "小批量BOM成本合计", + largebatchtotal: "大批量BOM成本合计", + smallbatchunitcost: "小批量单台成本", + largebatchunitcost: "大批量单台成本", + smallbatchmanufacturingcost: "小批量制造成本", + largebatchmanufacturingcost: "大批量制造成本", + smallbatchunittotalcost: "小批量单台总成本", + largebatchunittotalcost: "大批量单台总成本", + smallcost: "小批量成本", + largecost: "大批量成本", + smallbatchchart: "小批量成本结构图", + largebatchchart: "大批量成本结构图", + optimizationplan: "降本优化方案", + costoptimization: "成本优化方案", // 采购需求预测 - linearforecastrows: "线性趋势预测", procurementplanrows: "采购计划", - demandforecastchart: "需求预测可视化图", tabledata: "历史数据与移动平均", - tabledata1: "季节指数", forecastdata: "需求预测", monthlydata: "移动平均数据", - seasonalindex: "季节指数", demandforecast: "需求预测", safetystock: "安全库存", - openingstock: "期初库存", initialstock: "期初库存", purchasequantity: "采购量", - demand: "市场需求量", forecast: "预测采购量", forecastsales: "预测销量", - ma12: "12个月移动平均", costpricingrows: "成本加成定价渠道", markup: "加成率", - markuprate: "加成率", selectedcostmarkupchannel: "成本加成定价渠道", + linearforecastrows: "线性趋势预测", + procurementplanrows: "采购计划", + demandforecastchart: "需求预测可视化图", + tabledata: "历史数据与移动平均", + tabledata1: "季节指数", + forecastdata: "需求预测", + monthlydata: "移动平均数据", + seasonalindex: "季节指数", + demandforecast: "需求预测", + safetystock: "安全库存", + openingstock: "期初库存", + initialstock: "期初库存", + purchasequantity: "采购量", + demand: "市场需求量", + forecast: "预测采购量", + forecastsales: "预测销量", + ma12: "12个月移动平均", + costpricingrows: "成本加成定价渠道", + markup: "加成率", + markuprate: "加成率", + selectedcostmarkupchannel: "成本加成定价渠道", // 消费者需求与行为 / 消费场景 - targethypotheses: "目标人群与核心假设", targetgroup: "目标人群", keyfinding: "关键发现", - corehypothesis: "核心假设", kanoneeds: "KANO需求分析", kanotype: "KANO类型", - featuredirection: "功能方向", kano: "KANO属性", questionnairequestions: "问卷题目", - questionnaireimport: "导入问卷", questionnairetext: "问卷文本", surveyreportrows: "调研报告模块", - questionnairefilename: "问卷文件名", dataexcelfilename: "数据Excel文件名", - chartfiles: "数据可视化截图", journeyorder: "旅程阶段排序", journeyanalysis: "用户旅程分析", - journeymap: "旅程图", journeyoptimizations: "旅程痛点优化", journeyrows: "用户旅程分析", - painrows: "痛点优化", behavior: "用户行为", behaviordescription: "用户行为描述", - keytouchpoints: "关键触点", touchpoint: "接触点", touchpoints: "关键触点", - emotion: "情绪", emotionexperience: "情感体验", sentiment: "情感体验", - decisionfactors: "关键决策因素", painpoint: "痛点", painpoints: "痛点", - optimizationsuggestion: "优化建议", goal: "用户目标", matrix: "旅程矩阵", - classifications: "阶段分类", phases: "旅程阶段", needtrigger: "需求触发阶段", - productsearch: "产品搜索阶段", comparedecision: "比较决策阶段", orderpayment: "下单支付阶段", - usagereview: "使用评价阶段", problem: "问题识别", search: "信息搜索", - evaluate: "方案评估", purchase: "购买决策", post: "购后评价", + targethypotheses: "目标人群与核心假设", + targetgroup: "目标人群", + keyfinding: "关键发现", + corehypothesis: "核心假设", + kanoneeds: "KANO需求分析", + kanotype: "KANO类型", + featuredirection: "功能方向", + kano: "KANO属性", + questionnairequestions: "问卷题目", + questionnaireimport: "导入问卷", + questionnairetext: "问卷文本", + surveyreportrows: "调研报告模块", + questionnairefilename: "问卷文件名", + dataexcelfilename: "数据Excel文件名", + chartfiles: "数据可视化截图", + journeyorder: "旅程阶段排序", + journeyanalysis: "用户旅程分析", + journeymap: "旅程图", + journeyoptimizations: "旅程痛点优化", + journeyrows: "用户旅程分析", + painrows: "痛点优化", + behavior: "用户行为", + behaviordescription: "用户行为描述", + keytouchpoints: "关键触点", + touchpoint: "接触点", + touchpoints: "关键触点", + emotion: "情绪", + emotionexperience: "情感体验", + sentiment: "情感体验", + decisionfactors: "关键决策因素", + painpoint: "痛点", + painpoints: "痛点", + optimizationsuggestion: "优化建议", + goal: "用户目标", + matrix: "旅程矩阵", + classifications: "阶段分类", + phases: "旅程阶段", + needtrigger: "需求触发阶段", + productsearch: "产品搜索阶段", + comparedecision: "比较决策阶段", + orderpayment: "下单支付阶段", + usagereview: "使用评价阶段", + problem: "问题识别", + search: "信息搜索", + evaluate: "方案评估", + purchase: "购买决策", + post: "购后评价", // 需求转化为功能 - voice: "用户原声", uservoice: "用户原声", insight: "场景洞察", scenarioinsight: "场景洞察", - definition: "功能定义", featuredefinition: "功能定义", featuredescription: "功能描述", - acceptance: "验收标准", acceptancecriteria: "验收标准", requirementid: "需求ID", - reach: "覆盖范围", impact: "影响程度", confidence: "置信度", effort: "开发成本", - featurename: "功能名称", prioritydecision: "功能优先级决策", - p0: "P0级功能", p1: "P1级功能", p2: "P2级功能", p3: "P3级功能", + voice: "用户原声", + uservoice: "用户原声", + insight: "场景洞察", + scenarioinsight: "场景洞察", + definition: "功能定义", + featuredefinition: "功能定义", + featuredescription: "功能描述", + acceptance: "验收标准", + acceptancecriteria: "验收标准", + requirementid: "需求ID", + reach: "覆盖范围", + impact: "影响程度", + confidence: "置信度", + effort: "开发成本", + featurename: "功能名称", + prioritydecision: "功能优先级决策", + p0: "P0级功能", + p1: "P1级功能", + p2: "P2级功能", + p3: "P3级功能", // 商业可行性 - projectbackground: "项目背景", productpositioning: "产品定位", projectgoals: "项目目标", - macroenvironmentconclusion: "宏观环境分析结论", competitivelandscapeconclusion: "竞争态势分析结论", - annualmarketsize: "年度市场规模", monthlygrowthrate: "月均增长率", - topbrandmarketshare: "头部品牌市场份额", competitorspricerange: "主要竞品售价区间", - marketconcentrationcr3: "市场集中度CR3", usergroup: "用户群体", coreneed: "核心需求", - spendingpower: "消费能力", businessmodels: "商业模式分析", module: "模块", - explanation: "说明", costs: "成本构成", targetprice: "目标售价", calculations: "成本测算", - unittotalcost: "单台总成本", unitgrossprofit: "单位毛利", grossmargin: "毛利率", - grossmarginrate: "毛利率", valuediscount: "利润让渡折扣率", steptwo: "总体评估结论", - report: "可行性论证报告", marketanalysis: "市场分析", + projectbackground: "项目背景", + productpositioning: "产品定位", + projectgoals: "项目目标", + macroenvironmentconclusion: "宏观环境分析结论", + competitivelandscapeconclusion: "竞争态势分析结论", + annualmarketsize: "年度市场规模", + monthlygrowthrate: "月均增长率", + topbrandmarketshare: "头部品牌市场份额", + competitorspricerange: "主要竞品售价区间", + marketconcentrationcr3: "市场集中度CR3", + usergroup: "用户群体", + coreneed: "核心需求", + spendingpower: "消费能力", + businessmodels: "商业模式分析", + module: "模块", + explanation: "说明", + costs: "成本构成", + targetprice: "目标售价", + calculations: "成本测算", + unittotalcost: "单台总成本", + unitgrossprofit: "单位毛利", + grossmargin: "毛利率", + grossmarginrate: "毛利率", + valuediscount: "利润让渡折扣率", + steptwo: "总体评估结论", + report: "可行性论证报告", + marketanalysis: "市场分析", // 目标用户画像 - profilefilename: "画像数据Excel文件名", rfmcharts: "RFM可视化图表", - segmentshare: "客户分层占比", averagespend: "客户平均消费对比", scoreheatmap: "RFM分布热力图", - rfmresult: "RFM分层结果", segmentationchart: "客户分层占比图", comparisonchart: "平均消费对比图", - rfmheatmap: "RFM分布热力图", rfminsights: "RFM数据分析", rfmanalyses: "RFM分析结论", - keypoint: "分析要点", keyuserrecords: "重点用户访谈画像", basicattributes: "基础属性", - tags: "标签", psychology: "消费心理", consumerpsychology: "消费心理", - behaviorcharacteristics: "行为特征", marketsegments: "细分市场分析", market: "细分市场", - attractiveness: "市场吸引力", productopportunity: "产品机会", marketselection: "市场选择结论", - userinfo: "用户信息", highvalueshare: "高价值用户占比", activity: "用户活跃度", - loyalty: "用户忠诚度", keycustomer: "重点客户群", + profilefilename: "画像数据Excel文件名", + rfmcharts: "RFM可视化图表", + segmentshare: "客户分层占比", + averagespend: "客户平均消费对比", + scoreheatmap: "RFM分布热力图", + rfmresult: "RFM分层结果", + segmentationchart: "客户分层占比图", + comparisonchart: "平均消费对比图", + rfmheatmap: "RFM分布热力图", + rfminsights: "RFM数据分析", + rfmanalyses: "RFM分析结论", + keypoint: "分析要点", + keyuserrecords: "重点用户访谈画像", + basicattributes: "基础属性", + tags: "标签", + psychology: "消费心理", + consumerpsychology: "消费心理", + behaviorcharacteristics: "行为特征", + marketsegments: "细分市场分析", + market: "细分市场", + attractiveness: "市场吸引力", + productopportunity: "产品机会", + marketselection: "市场选择结论", + userinfo: "用户信息", + highvalueshare: "高价值用户占比", + activity: "用户活跃度", + loyalty: "用户忠诚度", + keycustomer: "重点客户群", // 产品开发流程与关键因素 - parallelworkanalysis: "可并行工作分析", aiscenarioone: "AI应用场景一", - aiscenariotwo: "AI应用场景二", ganttimagename: "甘特图文件名", criteria: "判断标准", - successanalysis: "成功产品分析", failedanalysis: "失败产品分析", - successfactors: "关键成功因素总结", failedfactors: "失败原因总结", factors: "关键因素分析", + parallelworkanalysis: "可并行工作分析", + aiscenarioone: "AI应用场景一", + aiscenariotwo: "AI应用场景二", + ganttimagename: "甘特图文件名", + criteria: "判断标准", + successanalysis: "成功产品分析", + failedanalysis: "失败产品分析", + successfactors: "关键成功因素总结", + failedfactors: "失败原因总结", + factors: "关键因素分析", // 新产品调查 - platform: "电商平台", customscene: "自定义场景", keyword: "搜索关键词", - points: "分析要点", tangiblevalue: "有形层AI应用价值分析", - extendedvalue: "延伸层AI应用价值分析", corevalue: "核心价值", + platform: "电商平台", + customscene: "自定义场景", + keyword: "搜索关键词", + points: "分析要点", + tangiblevalue: "有形层AI应用价值分析", + extendedvalue: "延伸层AI应用价值分析", + corevalue: "核心价值", // 市场机会识别与选品 - competitor: "竞品对比", competitora: "竞品A", competitorb: "竞品B", gap: "差距分析", - dataselection: "数据选择", reflection: "数据思考", primaryweights: "一级指标权重", - dimensionweights: "维度内权重", correspondingweights: "对应总权重", totalweights: "对应总权重", - marketsize: "市场规模", marketpotential: "市场潜力", competition: "竞争强度", - operations: "运营壁垒", categoryscores: "品类综合得分", reviews: "品类评价洞察", - favorablerate: "好评率", unmetneed: "未被满足的需求", unmetneeds: "未被满足的需求", - developmentconclusion: "开发结论", reviewinsights: "品类评价洞察", + competitor: "竞品对比", + competitora: "竞品A", + competitorb: "竞品B", + gap: "差距分析", + dataselection: "数据选择", + reflection: "数据思考", + primaryweights: "一级指标权重", + dimensionweights: "维度内权重", + correspondingweights: "对应总权重", + totalweights: "对应总权重", + marketsize: "市场规模", + marketpotential: "市场潜力", + competition: "竞争强度", + operations: "运营壁垒", + categoryscores: "品类综合得分", + reviews: "品类评价洞察", + favorablerate: "好评率", + unmetneed: "未被满足的需求", + unmetneeds: "未被满足的需求", + developmentconclusion: "开发结论", + reviewinsights: "品类评价洞察", visualization: "数据可视化结果图", // 价值主张与定位 - corepainrows: "核心痛点识别", customerprofile: "用户档案", jobs: "用户目标", - pains: "痛点", gains: "收益期望", valuemap: "产品价值地图", products: "产品与服务", - painrelievers: "痛点缓释器", gaincreators: "增益创造器", valuecanvas: "价值画布", - productservice: "产品与服务", customergains: "收益期望", customerpains: "痛点", - customerjobs: "用户目标", positioningstatements: "定位陈述", targetuser: "目标用户", - targetusers: "目标用户", productcategory: "产品类别", differentiationreason: "差异化理由", - mrd: "需求文档(MRD)", marketinsights: "市场问题与机会", marketoverview: "市场概述", - useranalysis: "用户分析", competitoranalysis: "竞品分析", productrequirements: "产品需求说明", - customervalue: "用户价值", targetmarket: "目标市场", versions: "定位陈述版本", - versionone: "定位陈述版本一", versiontwo: "定位陈述版本二", persona: "用户画像", - positioncanvas: "定位画布", needsandpains: "需求与痛点", competitoropportunity: "竞品机会", + corepainrows: "核心痛点识别", + customerprofile: "用户档案", + jobs: "用户目标", + pains: "痛点", + gains: "收益期望", + valuemap: "产品价值地图", + products: "产品与服务", + painrelievers: "痛点缓释器", + gaincreators: "增益创造器", + valuecanvas: "价值画布", + productservice: "产品与服务", + customergains: "收益期望", + customerpains: "痛点", + customerjobs: "用户目标", + positioningstatements: "定位陈述", + targetuser: "目标用户", + targetusers: "目标用户", + productcategory: "产品类别", + differentiationreason: "差异化理由", + mrd: "需求文档(MRD)", + marketinsights: "市场问题与机会", + marketoverview: "市场概述", + useranalysis: "用户分析", + competitoranalysis: "竞品分析", + productrequirements: "产品需求说明", + customervalue: "用户价值", + targetmarket: "目标市场", + versions: "定位陈述版本", + versionone: "定位陈述版本一", + versiontwo: "定位陈述版本二", + persona: "用户画像", + positioncanvas: "定位画布", + needsandpains: "需求与痛点", + competitoropportunity: "竞品机会", // 产品需求文档(PRD) - contents: "填写内容", backgroundpain: "项目背景与痛点", positioningtarget: "定位与目标用户", - coregoal: "核心目标", productscope: "产品范围", usagescenario: "用户场景", - painandneed: "痛点与诉求", featureoverview: "功能概览", featuredetails: "详细功能", - performance: "性能", security: "安全", usability: "可用性", compatibility: "兼容性", - hardwarespecs: "硬件参数", interfaces: "接口与协议", powerheat: "功耗散热", - structureconstraints: "结构约束", testingfocus: "测试重点", testmatrix: "测试矩阵", - milestones: "里程碑", resourcedependencies: "资源依赖", riskassessment: "风险评估", - docpurpose: "文档说明", document: "需求文档", + contents: "填写内容", + backgroundpain: "项目背景与痛点", + positioningtarget: "定位与目标用户", + coregoal: "核心目标", + productscope: "产品范围", + usagescenario: "用户场景", + painandneed: "痛点与诉求", + featureoverview: "功能概览", + featuredetails: "详细功能", + performance: "性能", + security: "安全", + usability: "可用性", + compatibility: "兼容性", + hardwarespecs: "硬件参数", + interfaces: "接口与协议", + powerheat: "功耗散热", + structureconstraints: "结构约束", + testingfocus: "测试重点", + testmatrix: "测试矩阵", + milestones: "里程碑", + resourcedependencies: "资源依赖", + riskassessment: "风险评估", + docpurpose: "文档说明", + document: "需求文档", // 产品架构 / 售后服务 - layerids: "架构层级排序", actionids: "工作流程排序", userscenario: "用户场景", - hardwaresense: "硬件物理层感知", hardwareinput: "硬件物理层感知", - drivertransmit: "驱动感知层传递", driverinput: "驱动感知层传递", - algorithmprocess: "算法处理层", algorithm: "算法处理层", applicationtrigger: "应用功能层", - application: "应用功能层", driveroutput: "驱动感知层输出", hardwareeffect: "硬件物理层效果", - hardwareoutput: "硬件物理层效果", images: "图片", assignments: "分类映射", - orderedfaultids: "故障排序", policies: "售后政策框架", summaries: "详情页售后信息", + layerids: "架构层级排序", + actionids: "工作流程排序", + userscenario: "用户场景", + hardwaresense: "硬件物理层感知", + hardwareinput: "硬件物理层感知", + drivertransmit: "驱动感知层传递", + driverinput: "驱动感知层传递", + algorithmprocess: "算法处理层", + algorithm: "算法处理层", + applicationtrigger: "应用功能层", + application: "应用功能层", + driveroutput: "驱动感知层输出", + hardwareeffect: "硬件物理层效果", + hardwareoutput: "硬件物理层效果", + images: "图片", + assignments: "分类映射", + orderedfaultids: "故障排序", + policies: "售后政策框架", + summaries: "详情页售后信息", // 产品发布渠道评估 / 投放效果 - dailysearchvolume: "日均搜索量", supplydemandratio: "供需比", averageclickrate: "平均点击率", - averagecpc: "平均CPC", averageconversionrate: "平均转化率", existingvisitorcount: "现有访客数", - matchingscore: "匹配度", corechannelanalysis: "核心渠道优势与运营建议", - channelconclusion: "核心渠道结论", channelpositioning: "渠道定位", - budgetpercentage: "预算占比", budgetamount: "预算金额", coldstartbudget: "冷启动期预算", - growthbudget: "增长期预算", stablebudget: "稳定期预算", totalbudget: "新品投放试错预算", - impressions: "曝光量", clicks: "点击量", clickrate: "点击率", turnover: "成交额", - promotioncost: "推广花费", roi: "实际ROI", breakevenroi: "保本ROI", - achievement: "是否达标", qualified: "是否达标", problemdiagnosis: "问题诊断", - optimizationaction: "优化动作", optimizationdecision: "整体优化决策", - budgetrows: "投放预算分配", roirows: "投放效果评估", funnelchannels: "漏斗渠道", - funnelrows: "转化漏斗", funneldiagnoses: "漏斗异常诊断", storerate: "店铺转化率", - industryrate: "行业转化率", node: "链路节点", abrows: "AB测试指标", - control: "对照组", experiment: "实验组", commentattachment: "评论数据附件", - searchvolume: "搜索次数", onlinemerchantcount: "在线商家数", onlineproductcount: "在线商品数", + dailysearchvolume: "日均搜索量", + supplydemandratio: "供需比", + averageclickrate: "平均点击率", + averagecpc: "平均CPC", + averageconversionrate: "平均转化率", + existingvisitorcount: "现有访客数", + matchingscore: "匹配度", + corechannelanalysis: "核心渠道优势与运营建议", + channelconclusion: "核心渠道结论", + channelpositioning: "渠道定位", + budgetpercentage: "预算占比", + budgetamount: "预算金额", + coldstartbudget: "冷启动期预算", + growthbudget: "增长期预算", + stablebudget: "稳定期预算", + totalbudget: "新品投放试错预算", + impressions: "曝光量", + clicks: "点击量", + clickrate: "点击率", + turnover: "成交额", + promotioncost: "推广花费", + roi: "实际ROI", + breakevenroi: "保本ROI", + achievement: "是否达标", + qualified: "是否达标", + problemdiagnosis: "问题诊断", + optimizationaction: "优化动作", + optimizationdecision: "整体优化决策", + budgetrows: "投放预算分配", + roirows: "投放效果评估", + funnelchannels: "漏斗渠道", + funnelrows: "转化漏斗", + funneldiagnoses: "漏斗异常诊断", + storerate: "店铺转化率", + industryrate: "行业转化率", + node: "链路节点", + abrows: "AB测试指标", + control: "对照组", + experiment: "实验组", + commentattachment: "评论数据附件", + searchvolume: "搜索次数", + onlinemerchantcount: "在线商家数", + onlineproductcount: "在线商品数", categorygrowth: "类目成交增速", // 产品问题诊断与迭代 - conversionrates: "链路节点转化率", linknode: "链路节点", industryaverage: "行业平均转化率", - funnelchartname: "漏斗对比图文件名", anomalyrows: "异常节点分析", - storeconversionrate: "店铺转化率", industryaverageconversionrate: "行业平均转化率", - anomalyanalysis: "异常分析与判定", sourcefilename: "评论数据文件名", totalcomments: "评论总数", - topissues: "高频问题", topone: "高频问题第一名", toptwo: "高频问题第二名", - topthree: "高频问题第三名", sentiments: "情感评论统计", rootcauseanalysis: "根本原因分析", - wordcloud: "评论词云", previewcomments: "评论数据预览", analysisresult: "评论分析结果", - testplan: "AB测试方案", controlvalue: "对照组数据", experimentvalue: "实验组数据", - comparisonchartname: "对比图文件名", iterationrecommendation: "迭代优化建议", + conversionrates: "链路节点转化率", + linknode: "链路节点", + industryaverage: "行业平均转化率", + funnelchartname: "漏斗对比图文件名", + anomalyrows: "异常节点分析", + storeconversionrate: "店铺转化率", + industryaverageconversionrate: "行业平均转化率", + anomalyanalysis: "异常分析与判定", + sourcefilename: "评论数据文件名", + totalcomments: "评论总数", + topissues: "高频问题", + topone: "高频问题第一名", + toptwo: "高频问题第二名", + topthree: "高频问题第三名", + sentiments: "情感评论统计", + rootcauseanalysis: "根本原因分析", + wordcloud: "评论词云", + previewcomments: "评论数据预览", + analysisresult: "评论分析结果", + testplan: "AB测试方案", + controlvalue: "对照组数据", + experimentvalue: "实验组数据", + comparisonchartname: "对比图文件名", + iterationrecommendation: "迭代优化建议", iterationconclusion: "迭代优化结论", // 品类结构优化 - categoryrows: "品类数据", categoryname: "品类名称", revenue: "营收", revenueshare: "营收占比", - cumulativeshare: "累计占比", inventorycapital: "库存资金占用", capitaloccupancyrate: "资金占用率", - sellthroughrate: "动销率", abcclassification: "ABC分层判定", layer: "ABC分层", - matrixquadrant: "新品矩阵位置", matrixreason: "新品矩阵定位原因", necessityanalysis: "调整必要性分析", - recommendedclassification: "建议调整分层", trafficsupportmeasures: "流量扶持措施", - operationstrategy: "运营策略", inventorystrategy: "库存策略", resourcepriority: "资源配置优先级", - operation: "运营策略", inventory: "库存策略", support: "流量扶持措施", - targetclass: "建议调整分层", rationale: "选择原因", strategyrows: "运营策略行", + categoryrows: "品类数据", + categoryname: "品类名称", + revenue: "营收", + revenueshare: "营收占比", + cumulativeshare: "累计占比", + inventorycapital: "库存资金占用", + capitaloccupancyrate: "资金占用率", + sellthroughrate: "动销率", + abcclassification: "ABC分层判定", + layer: "ABC分层", + matrixquadrant: "新品矩阵位置", + matrixreason: "新品矩阵定位原因", + necessityanalysis: "调整必要性分析", + recommendedclassification: "建议调整分层", + trafficsupportmeasures: "流量扶持措施", + operationstrategy: "运营策略", + inventorystrategy: "库存策略", + resourcepriority: "资源配置优先级", + operation: "运营策略", + inventory: "库存策略", + support: "流量扶持措施", + targetclass: "建议调整分层", + rationale: "选择原因", + strategyrows: "运营策略行", // 产品定价 - pricingrows: "渠道加成定价", competitorrows: "竞品信息", producttype: "产品类型", - ecommerceprice: "电商售价", differentiator: "差异化优势", pricingdecision: "定价决策", - competitordecision: "竞品定价决策", pricingreason: "定价原因", benchmark: "对标竞品", - benchmarkcompetitor: "对标竞品", factorycost: "厂家出厂成本", valueitems: "用户感知价值", - perceivedvalue: "价值感知", discountrate: "利润让渡折扣率", perceivedvaluetotal: "感知价值合计", - valuebasedprice: "价值定价法价格", psmrows: "PSM价格敏感度测试", pricerange: "价格区间", - tooexpensive: "太贵不买占比", toocheap: "太便宜不买占比", expensiveacceptable: "偏高可接受占比", - cheapvalue: "便宜划算占比", pricepoints: "价格点", optimalprice: "最优价格点", - acceptableprice: "可接受价格点", acceptablerange: "可接受价格范围", chart: "PSM折线图", - methodresults: "定价方法汇总", methodname: "定价方法", measuredprice: "测算结果", - finalanalysis: "最终量产定价分析", finalpricinganalysis: "最终量产定价分析", + pricingrows: "渠道加成定价", + competitorrows: "竞品信息", + producttype: "产品类型", + ecommerceprice: "电商售价", + differentiator: "差异化优势", + pricingdecision: "定价决策", + competitordecision: "竞品定价决策", + pricingreason: "定价原因", + benchmark: "对标竞品", + benchmarkcompetitor: "对标竞品", + factorycost: "厂家出厂成本", + valueitems: "用户感知价值", + perceivedvalue: "价值感知", + discountrate: "利润让渡折扣率", + perceivedvaluetotal: "感知价值合计", + valuebasedprice: "价值定价法价格", + psmrows: "PSM价格敏感度测试", + pricerange: "价格区间", + tooexpensive: "太贵不买占比", + toocheap: "太便宜不买占比", + expensiveacceptable: "偏高可接受占比", + cheapvalue: "便宜划算占比", + pricepoints: "价格点", + optimalprice: "最优价格点", + acceptableprice: "可接受价格点", + acceptablerange: "可接受价格范围", + chart: "PSM折线图", + methodresults: "定价方法汇总", + methodname: "定价方法", + measuredprice: "测算结果", + finalanalysis: "最终量产定价分析", + finalpricinganalysis: "最终量产定价分析", valuerows: "感知价值", // 综合案例实训(GenericTrainingPage) - caseanalysis: "案例与问题分析", solution: "整体解决方案", validation: "验证与复盘", - workrows: "实训工作表", processrows: "流程任务", workcontent: "工作内容", - comprehensivestepone: "综合实训第一步", comprehensivesteptwo: "综合实训第二步", - comprehensivestepthree: "综合实训第三步", comprehensivestepfour: "综合实训第四步", - comprehensivestepfive: "综合实训第五步", comprehensivestepsix: "综合实训第六步", - comprehensivestepseven: "综合实训第七步", comprehensivestepeight: "综合实训第八步", - comprehensivestepnine: "综合实训第九步", requirements: "需求转化", ricerows: "RICE评分", - architecturerows: "产品架构分析", faultrows: "故障分级处理", policyrows: "售后政策框架", - summaryrows: "汇总", bomrows: "BOM物料", manufacturingrows: "制造费用", - weightrows: "评估维度权重", channelrows: "渠道评分", channelweightrows: "渠道评估权重", - channelscorerows: "渠道维度得分", abcrows: "品类ABC分析", forecastrows: "需求预测", - profiles: "用户画像", kanorows: "KANO分析", questionnaire: "问卷", attachments: "上传材料", + caseanalysis: "案例与问题分析", + solution: "整体解决方案", + validation: "验证与复盘", + workrows: "实训工作表", + processrows: "流程任务", + workcontent: "工作内容", + comprehensivestepone: "综合实训第一步", + comprehensivesteptwo: "综合实训第二步", + comprehensivestepthree: "综合实训第三步", + comprehensivestepfour: "综合实训第四步", + comprehensivestepfive: "综合实训第五步", + comprehensivestepsix: "综合实训第六步", + comprehensivestepseven: "综合实训第七步", + comprehensivestepeight: "综合实训第八步", + comprehensivestepnine: "综合实训第九步", + requirements: "需求转化", + ricerows: "RICE评分", + architecturerows: "产品架构分析", + faultrows: "故障分级处理", + policyrows: "售后政策框架", + summaryrows: "汇总", + bomrows: "BOM物料", + manufacturingrows: "制造费用", + weightrows: "评估维度权重", + channelrows: "渠道评分", + channelweightrows: "渠道评估权重", + channelscorerows: "渠道维度得分", + abcrows: "品类ABC分析", + forecastrows: "需求预测", + profiles: "用户画像", + kanorows: "KANO分析", + questionnaire: "问卷", + attachments: "上传材料", newproduct: "新品定义", // 首轮验证补漏 - scenarioanalyses: "采购场景对比分析", actualroi: "实际ROI", channelrates: "各渠道转化率", - sentimentrows: "情感评论统计", priorities: "优先级", form: "填写内容", projectoverview: "项目概况", - choiceanswers: "随堂选择题答案", valuepropositioncanvas: "价值主张画布", - profileanalysis: "画像数据分析", keyuserprofile: "重点用户画像", suggestion: "优化建议", - quality: "质量可靠性", advantage: "差异化优势", item: "条目", range: "价格区间", - acceptable: "偏高可接受占比", bargain: "便宜划算占比", margin: "毛利润率", - salesshare: "营收占比", diagnosis: "问题诊断", action: "优化动作", positioning: "渠道定位", - share: "预算占比", launchbudget: "冷启动期预算", direction: "功能方向", - code: "编号", user: "用户", actual: "实际采购量", + scenarioanalyses: "采购场景对比分析", + actualroi: "实际ROI", + channelrates: "各渠道转化率", + sentimentrows: "情感评论统计", + priorities: "优先级", + form: "填写内容", + projectoverview: "项目概况", + choiceanswers: "随堂选择题答案", + valuepropositioncanvas: "价值主张画布", + profileanalysis: "画像数据分析", + keyuserprofile: "重点用户画像", + suggestion: "优化建议", + quality: "质量可靠性", + advantage: "差异化优势", + item: "条目", + range: "价格区间", + acceptable: "偏高可接受占比", + bargain: "便宜划算占比", + margin: "毛利润率", + salesshare: "营收占比", + diagnosis: "问题诊断", + action: "优化动作", + positioning: "渠道定位", + share: "预算占比", + launchbudget: "冷启动期预算", + direction: "功能方向", + code: "编号", + user: "用户", + actual: "实际采购量", }; // 导出时无需展示的机器元数据(版本号、时间戳、校验状态、内部 ID 等)。 -const ANSWER_HIDDEN_KEYS = new Set([ - "saveaction", "submitted", "taskkey", "taskid", "taskname", "currentstep", "activestep", - "version", "updatedat", "flowsteps", "configuredsteps", "trainingsteps", "passed", - "checkresult", "validationresult", "previewcontent", "profilepreview", "isdefault", - "id", "uid", "key", "locked", "channelid", "journeycheckedcorrectly", -]); +const ANSWER_HIDDEN_KEYS = new Set(["saveaction", "submitted", "taskkey", "taskid", "taskname", "currentstep", "activestep", "version", "updatedat", "flowsteps", "configuredsteps", "trainingsteps", "passed", "checkresult", "validationresult", "previewcontent", "profilepreview", "isdefault", "id", "uid", "key", "locked", "channelid", "journeycheckedcorrectly"]); function normalizeAnswerKey(key) { - return String(key || "").toLowerCase().replace(/[\s_-]+/g, ""); + return String(key || "") + .toLowerCase() + .replace(/[\s_-]+/g, ""); } function answerLabel(key) { @@ -395,8 +792,13 @@ function isHiddenAnswerKey(key) { // 页面保存的少量英文枚举值,导出时统一翻译(仅整体精确匹配,避免误伤学生原文)。 const ANSWER_VALUE_LABELS = { - smallbatch: "小批量试产", largebatch: "大批量量产", - problem: "问题识别", search: "信息搜索", evaluate: "方案评估", purchase: "购买决策", post: "购后评价", + smallbatch: "小批量试产", + largebatch: "大批量量产", + problem: "问题识别", + search: "信息搜索", + evaluate: "方案评估", + purchase: "购买决策", + post: "购后评价", }; function answerValue(value) { @@ -410,7 +812,15 @@ function flattenAnswer(value, prefix = "") { if (typeof parsed !== "object") return [{ label: prefix || "填写内容", value: answerValue(parsed) }].filter((item) => item.value); if (Array.isArray(parsed)) { if (parsed.every((item) => typeof item !== "object" || item == null)) { - return [{ label: prefix || "填写内容", value: parsed.map((item) => answerValue(item)).filter(Boolean).join("、") }].filter((item) => item.value); + return [ + { + label: prefix || "填写内容", + value: parsed + .map((item) => answerValue(item)) + .filter(Boolean) + .join("、"), + }, + ].filter((item) => item.value); } return parsed.flatMap((item, index) => flattenAnswer(item, `${prefix || "填写内容"} ${index + 1}`)); } @@ -449,40 +859,48 @@ export function collectTrainingOutcomeDetails(task, studentAnswer) { function stepTablesMarkup(tables) { if (!tables?.length) return ""; - return tables.map((table, index) => ` + return tables + .map( + (table, index) => `

表 ${index + 1}

${table.headers.map((header) => ``).join("")}${table.rows.map((row) => `${table.headers.map((_, cellIndex) => ``).join("")}`).join("")}
${escapeHtml(header || "-")}
${escapeHtml(row[cellIndex] || "-")}
-
`).join(""); +
`, + ) + .join(""); } function stepDetailsMarkup(items) { if (!items.length) return "

未配置

"; - return `
${items.map((step, index) => ` + return `
${items + .map( + (step, index) => `

${String(index + 1).padStart(2, "0")}${escapeHtml(step.name)}

步骤内容

${step.content ? paragraphMarkup(step.content) : ""} ${stepTablesMarkup(step.tables)}

学生作答

- ${step.answers?.length - ? `
    ${step.answers.map((answer) => `
  • ${escapeHtml(answer.label)}:${escapeHtml(answer.value)}
  • `).join("")}
` - : "

该步骤暂未填写作答。

"} -
`).join("")}
`; + ${step.answers?.length ? `
    ${step.answers.map((answer) => `
  • ${escapeHtml(answer.label)}:${escapeHtml(answer.value)}
  • `).join("")}
` : '

该步骤暂未填写作答。

'} + `, + ) + .join("")}
`; } function createPrintDocument(task) { const title = normalizeText(task?.taskName || task?.title || "实训成果"); const objectives = normalizeList(task?.objectives); const steps = normalizeList(task?.steps); - const stepDetails = Array.isArray(task?.stepDetails) && task.stepDetails.length - ? task.stepDetails - : steps.map((name) => ({ name, content: "当前步骤内容以页面实训说明为准。", tables: [], answers: [] })); + const stepDetails = Array.isArray(task?.stepDetails) && task.stepDetails.length ? task.stepDetails : steps.map((name) => ({ name, content: "当前步骤内容以页面实训说明为准。", tables: [], answers: [] })); const generatedAt = new Intl.DateTimeFormat("zh-CN", { - year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", hour12: false, }).format(new Date()); const element = document.createElement("article"); @@ -520,7 +938,7 @@ function createPrintDocument(task) { .training-outcome-pdf footer { margin-top: 34px; padding-top: 12px; border-top: 1px solid #d9e8ef; color: #7c8b99; font-size: 12px; text-align: center; }
-

电商互联网产品开发实训系统

+

互联网产品开发实训系统

${escapeHtml(title)}

实训任务说明 · 导出时间:${escapeHtml(generatedAt)}
@@ -528,12 +946,20 @@ function createPrintDocument(task) { ${sectionMarkup("实训目标", listMarkup(objectives, "training-outcome-pdf__goals"))} ${sectionMarkup("实训要求", paragraphMarkup(task?.requirements))} ${sectionMarkup("实训步骤与学生作答", stepDetailsMarkup(stepDetails))} -
电商互联网产品开发实训系统 · ${escapeHtml(title)}
+
互联网产品开发实训系统 · ${escapeHtml(title)}
`; Object.assign(element.style, { - position: "fixed", left: "-10000px", top: "0", width: "794px", padding: "54px 58px 48px", - boxSizing: "border-box", color: "#11243b", background: "#ffffff", fontFamily: "Microsoft YaHei, PingFang SC, Arial, sans-serif", - lineHeight: "1.75", zIndex: "-1", + position: "fixed", + left: "-10000px", + top: "0", + width: "794px", + padding: "54px 58px 48px", + boxSizing: "border-box", + color: "#11243b", + background: "#ffffff", + fontFamily: "Microsoft YaHei, PingFang SC, Arial, sans-serif", + lineHeight: "1.75", + zIndex: "-1", }); return element; } @@ -541,7 +967,7 @@ function createPrintDocument(task) { function appendPdfPages(pdf, canvas) { const contentWidthMm = PAGE.width - PAGE.marginX * 2; const contentHeightMm = PAGE.height - PAGE.marginY * 2; - const sourceHeight = Math.floor(canvas.width * contentHeightMm / contentWidthMm); + const sourceHeight = Math.floor((canvas.width * contentHeightMm) / contentWidthMm); let sourceY = 0; let page = 0; @@ -552,7 +978,7 @@ function appendPdfPages(pdf, canvas) { slice.height = currentHeight; slice.getContext("2d").drawImage(canvas, 0, sourceY, canvas.width, currentHeight, 0, 0, canvas.width, currentHeight); if (page > 0) pdf.addPage(); - pdf.addImage(slice.toDataURL("image/jpeg", 0.96), "JPEG", PAGE.marginX, PAGE.marginY, contentWidthMm, currentHeight * contentWidthMm / canvas.width, undefined, "FAST"); + pdf.addImage(slice.toDataURL("image/jpeg", 0.96), "JPEG", PAGE.marginX, PAGE.marginY, contentWidthMm, (currentHeight * contentWidthMm) / canvas.width, undefined, "FAST"); sourceY += currentHeight; page += 1; } diff --git a/src/views/AI/index.vue b/src/views/AI/index.vue index 3d0c214..1d1e2a8 100644 --- a/src/views/AI/index.vue +++ b/src/views/AI/index.vue @@ -36,7 +36,7 @@
@@ -753,7 +753,7 @@ const onClick = (type, idx) => { if (!type?.isTemplateBased) { let arr = ["课程知识点", "核心知识点", "最难的部分", "实务知识概述", "学习建议", "业务现状", "理论研究", "发展趋势"] if (arr.includes(type.name)) { - inputMessage.value = type.com.replace("xxx", "电商互联网产品开发实训系统") + inputMessage.value = type.com.replace("xxx", "互联网产品开发实训系统") } else { inputMessage.value = type.com } @@ -908,7 +908,7 @@ onUnmounted(() => { if (resizeObserver) { resizeObserver.disconnect() } -}); +}) diff --git a/src/views/login.vue b/src/views/login.vue index 40299c4..1cc95b6 100644 --- a/src/views/login.vue +++ b/src/views/login.vue @@ -1,239 +1,857 @@ - - - - - + + + + + diff --git a/src/views/teacherEnd/class/index.vue b/src/views/teacherEnd/class/index.vue index 10aa90f..cb96973 100644 --- a/src/views/teacherEnd/class/index.vue +++ b/src/views/teacherEnd/class/index.vue @@ -157,6 +157,10 @@ +