From 7b8644a8ec842d633272f3890a02fbe093ff9b89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=B2=85?= <907037276@qq.com> Date: Tue, 25 Aug 2026 16:18:34 +0800 Subject: [PATCH] feat: improve student training experience --- src/api/studentTrainingAnswer.js | 169 ++++ src/assets/styles/student-training.scss | 73 +- src/views/components/TrainingAiSidebar.vue | 6 +- .../components/TrainingMaterialButton.vue | 2 +- src/views/components/TrainingTaskBrief.vue | 22 +- .../product-development-process.vue | 12 +- .../product/market-opportunity-selection.vue | 2 +- src/views/product/price.vue | 769 +++++++++++++++++- 8 files changed, 996 insertions(+), 59 deletions(-) diff --git a/src/api/studentTrainingAnswer.js b/src/api/studentTrainingAnswer.js index bcb5bde..32b2708 100644 --- a/src/api/studentTrainingAnswer.js +++ b/src/api/studentTrainingAnswer.js @@ -64,6 +64,175 @@ export function uploadStudentTrainingFile(data) { }); } +export function validateProductPricingStepOne(payload) { + const value = payload || {}; + const rows = Array.isArray(value.pricingRows) ? value.pricingRows : []; + if (isStudentDemo()) { + const ranges = { dealer: [15, 25], "direct-retail": [40, 60], "premium-brand": [70, 80] }; + const unitCost = Number(value.unitCost); + if (!Number.isFinite(unitCost) || unitCost <= 0) return Promise.reject(new Error("单台综合成本必须大于 0")); + if (rows.length !== 3) return Promise.reject(new Error("请填写三个渠道的加成率")); + try { + const pricingRows = Object.keys(ranges).map((id) => { + const markupRate = Number(rows.find((item) => item?.id === id)?.markupRate); + const [min, max] = ranges[id]; + if (!Number.isFinite(markupRate) || markupRate < min || markupRate > max) { + throw new Error(`渠道加成率需在 ${min}%~${max}% 范围内`); + } + return { id, markupRate: Number(markupRate.toFixed(2)), salePrice: Number((unitCost * (1 + markupRate / 100)).toFixed(2)) }; + }); + return Promise.resolve({ code: 200, data: { valid: true, message: "渠道加成定价已校验", unitCost: Number(unitCost.toFixed(2)), pricingRows } }); + } catch (error) { + return Promise.reject(error); + } + } + return request({ url: "/api/student-training-answers/product-pricing/step-1/validate", method: "post", data: value, headers: { repeatSubmit: false } }); +} + +export function validateProductPricingStepTwo(payload) { + const value = payload || {}; + const rows = Array.isArray(value.competitorRows) ? value.competitorRows : []; + const decision = value.decision || {}; + if (isStudentDemo()) { + const unitCost = Number(value.unitCost); + if (!Number.isFinite(unitCost) || unitCost <= 0) return Promise.reject(new Error("请先完成第 1 步并填写单台综合成本")); + if (rows.length !== 4) return Promise.reject(new Error("请完整填写 4 个竞品信息")); + try { + const productTypes = rows.map((row, index) => { + const productType = String(row?.productType || "").trim(); + const brand = String(row?.brand || "").trim(); + const differentiator = String(row?.differentiator || "").trim(); + const ecommercePrice = Number(row?.ecommercePrice); + if (!productType || !brand || !differentiator || !Number.isFinite(ecommercePrice) || ecommercePrice <= 0) throw new Error(`请完整填写竞品 ${index + 1} 的信息`); + return productType; + }); + if (new Set(productTypes).size !== productTypes.length) { + throw new Error("竞品产品类型不能重复,请使用可区分的竞品名称"); + } + const targetPrice = Number(decision.targetPrice); + if (!productTypes.includes(String(decision.benchmarkCompetitor || "").trim())) throw new Error("对标竞品必须从上方已填写的竞品中选择"); + if (!Number.isFinite(targetPrice) || targetPrice <= 0 || !String(decision.differentiator || "").trim() || !String(decision.pricingReason || "").trim()) throw new Error("请完整填写本产品定价、差异化优势和定价原因"); + return Promise.resolve({ code: 200, data: { valid: true, message: "竞品价格带与定价已校验", competitorRows: rows, decision: { ...decision, targetPrice: Number(targetPrice.toFixed(2)), grossMarginRate: Number((((targetPrice - unitCost) / targetPrice) * 100).toFixed(2)) } } }); + } catch (error) { + return Promise.reject(error); + } + } + return request({ url: "/api/student-training-answers/product-pricing/step-2/validate", method: "post", data: value, headers: { repeatSubmit: false } }); +} + +export function validateProductPricingStepThree(payload) { + const value = payload || {}; + const valueItems = Array.isArray(value.valueItems) ? value.valueItems : []; + if (isStudentDemo()) { + try { + if (!valueItems.length) throw new Error("请至少填写一项可替代产品或功能的价值感知"); + const normalizedItems = valueItems.map((item, index) => { + const name = String(item?.name || "").trim(); + const perceivedValue = Number(item?.perceivedValue); + if (!String(item?.id || "").trim() || !name || !Number.isFinite(perceivedValue) || perceivedValue <= 0) { + throw new Error(`请完整填写第 ${index + 1} 项价值感知`); + } + return { ...item, id: String(item.id).trim(), name, perceivedValue: Number(perceivedValue.toFixed(2)) }; + }); + if (new Set(normalizedItems.map((item) => item.id)).size !== normalizedItems.length) throw new Error("价值感知条目不能重复"); + const discountRate = Number(value.discountRate); + if (!Number.isFinite(discountRate) || discountRate <= 0 || discountRate > 100) throw new Error("利润让渡折扣率应大于 0 且不超过 100%"); + const perceivedValueTotal = Number(normalizedItems.reduce((total, item) => total + item.perceivedValue, 0).toFixed(2)); + return Promise.resolve({ + code: 200, + data: { + valid: true, + message: "用户感知价值定价已校验", + valueItems: normalizedItems, + perceivedValueTotal, + discountRate: Number(discountRate.toFixed(2)), + valueBasedPrice: Number((perceivedValueTotal * discountRate / 100).toFixed(2)), + }, + }); + } catch (error) { + return Promise.reject(error); + } + } + return request({ url: "/api/student-training-answers/product-pricing/step-3/validate", method: "post", data: value, headers: { repeatSubmit: false } }); +} + +export function validateProductPricingStepFour(payload) { + const value = payload || {}; + const psmRows = Array.isArray(value.psmRows) ? value.psmRows : []; + const pricePoints = value.pricePoints || {}; + if (isStudentDemo()) { + try { + const requiredRanges = ["≤100元", "100~150元", "150~200元", "200~250元", "250~300元", "≥300元"]; + if (psmRows.length < requiredRanges.length) throw new Error("请完整填写 6 个固定价格区间的 PSM 累计占比"); + const seenIds = new Set(); + const seenRanges = new Set(); + const normalizedRows = psmRows.map((row, index) => { + const id = String(row?.id || "").trim(); + const priceRange = String(row?.priceRange || "").trim(); + if (!id || !priceRange || seenIds.has(id) || seenRanges.has(priceRange)) throw new Error("PSM 价格区间不能重复且不能为空"); + seenIds.add(id); + seenRanges.add(priceRange); + const normalized = { ...row, id, priceRange }; + [["tooExpensive", "太贵不买"], ["tooCheap", "太便宜不买"], ["expensiveAcceptable", "偏高可接受"], ["cheapValue", "便宜划算"]].forEach(([field, label]) => { + const percentage = Number(row?.[field]); + if (!Number.isFinite(percentage) || percentage < 0 || percentage > 100) throw new Error(`第 ${index + 1} 行“${label}”累计占比应在 0%–100% 之间`); + normalized[field] = Number(percentage.toFixed(2)); + }); + return normalized; + }); + if (!requiredRanges.every((range) => seenRanges.has(range))) throw new Error("请保留并填写 6 个固定价格区间"); + const optimalPrice = Number(pricePoints.optimalPrice); + const acceptablePrice = Number(pricePoints.acceptablePrice); + const acceptableRange = String(pricePoints.acceptableRange || "").trim(); + if (!Number.isFinite(optimalPrice) || optimalPrice <= 0 || !Number.isFinite(acceptablePrice) || acceptablePrice <= 0 || !acceptableRange) { + throw new Error("请完整填写最优价格点、可接受价格点和可接受价格范围"); + } + return Promise.resolve({ code: 200, data: { + valid: true, + message: "PSM 价格敏感度测试已校验", + psmRows: normalizedRows, + pricePoints: { optimalPrice: Number(optimalPrice.toFixed(2)), acceptablePrice: Number(acceptablePrice.toFixed(2)), acceptableRange }, + } }); + } catch (error) { + return Promise.reject(error); + } + } + return request({ url: "/api/student-training-answers/product-pricing/step-4/validate", method: "post", data: value, headers: { repeatSubmit: false } }); +} + +export function validateProductPricingStepFive(payload) { + const value = payload || {}; + const methodResults = Array.isArray(value.methodResults) ? value.methodResults : []; + if (isStudentDemo()) { + try { + const requiredKeys = ["cost-markup", "competitive", "value-based", "psm"]; + const selectedCostMarkupChannel = String(value.selectedCostMarkupChannel || "").trim(); + if (!selectedCostMarkupChannel) throw new Error("请选择用于汇总的成本加成定价渠道"); + if (methodResults.length !== requiredKeys.length) throw new Error("请完整汇总四种定价方法的测算结果"); + const seenKeys = new Set(); + const normalizedResults = methodResults.map((item) => { + const key = String(item?.key || "").trim(); + const methodName = String(item?.methodName || "").trim(); + const measuredPrice = Number(item?.measuredPrice); + const grossMarginRate = Number(item?.grossMarginRate); + if (!requiredKeys.includes(key) || seenKeys.has(key) || !methodName || !Number.isFinite(measuredPrice) || measuredPrice <= 0 || !Number.isFinite(grossMarginRate)) { + throw new Error("请完整填写四种定价方法的测算结果和毛利润率"); + } + if (grossMarginRate < -100 || grossMarginRate > 100) throw new Error("毛利润率应在 -100% 至 100% 之间"); + seenKeys.add(key); + return { key, methodName, measuredPrice: Number(measuredPrice.toFixed(2)), grossMarginRate: Number(grossMarginRate.toFixed(2)) }; + }); + if (!requiredKeys.every((key) => seenKeys.has(key))) throw new Error("定价方法汇总项不正确或重复"); + const finalAnalysis = String(value.finalAnalysis || "").trim(); + if (!finalAnalysis) throw new Error("请填写最终量产定价分析"); + return Promise.resolve({ code: 200, data: { valid: true, message: "最终量产定价汇总已校验", selectedCostMarkupChannel, methodResults: normalizedResults, finalAnalysis } }); + } catch (error) { + return Promise.reject(error); + } + } + return request({ url: "/api/student-training-answers/product-pricing/step-5/validate", method: "post", data: value, headers: { repeatSubmit: false } }); +} + export function checkNewProductSurveyStepOne(items) { if (isStudentDemo()) { const results = Array.isArray(items) diff --git a/src/assets/styles/student-training.scss b/src/assets/styles/student-training.scss index e8fff74..3a88609 100644 --- a/src/assets/styles/student-training.scss +++ b/src/assets/styles/student-training.scss @@ -28,17 +28,47 @@ #app .teacherLayout .app-main:has(.student-training-shell) .student-training-shell { box-sizing: border-box !important; display: grid !important; - grid-template-columns: minmax(720px, 1fr) 340px !important; + grid-template-columns: minmax(660px, 1fr) 300px !important; align-items: start !important; - gap: 24px !important; + gap: 14px !important; width: 100% !important; max-width: none !important; min-height: calc(100vh - 90px) !important; margin: 0 !important; - padding: 24px 24px 32px !important; + padding: 14px 14px 20px !important; background: #06111d !important; } +/* + * Student pages use different local names for their main and workbench cards. + * Keep the layout density here so a page-specific card cannot reclaim the + * space reserved for the hands-on activity area. + */ +.student-training-shell > main { + min-width: 0 !important; + padding: 20px !important; + border-radius: 24px !important; +} + +.student-training-shell > main > .task-card, +.student-training-shell > main > .process-workbench, +.student-training-shell > main > .factors-workbench, +.student-training-shell > main > .workbench { + padding: 14px 16px !important; + border-radius: 20px !important; +} + +.student-training-shell .step-card, +.student-training-shell .process-step-card, +.student-training-shell .factor-step-card { + padding: 16px !important; + border-radius: 20px !important; +} + +.student-training-shell .task-header { + margin-bottom: 16px !important; +} + .student-training-shell .task-header .task-title { font-size: 30px !important; line-height: 1.3 !important; @@ -76,7 +106,7 @@ justify-content: space-between !important; gap: 6px !important; width: 100% !important; - margin: 0 0 32px !important; + margin: 0 0 18px !important; padding: 6px !important; border: 1px solid var(--student-panel-border) !important; border-radius: var(--student-radius-pill) !important; @@ -443,7 +473,7 @@ display: flex !important; align-items: flex-start !important; justify-content: space-between !important; - gap: 18px !important; + gap: 10px !important; } .student-training-shell .workbench-actions { @@ -453,7 +483,7 @@ align-items: center !important; justify-content: flex-end !important; align-self: flex-start !important; - gap: 10px !important; + gap: 8px !important; min-width: max-content !important; margin: 0 0 0 auto !important; padding: 0 !important; @@ -467,16 +497,16 @@ flex: 0 0 auto !important; min-width: 0 !important; width: auto !important; - height: 40px !important; - min-height: 40px !important; - max-height: 40px !important; + height: 34px !important; + min-height: 34px !important; + max-height: 34px !important; margin: 0 !important; - padding: 0 18px !important; + padding: 0 12px !important; border: 1px solid rgba(99, 217, 255, 0.55) !important; border-radius: var(--student-radius-pill) !important; color: #e9fbff !important; background: rgba(17, 126, 178, 0.36) !important; - font-size: 14px !important; + font-size: 13px !important; font-weight: 800 !important; line-height: 1 !important; white-space: nowrap !important; @@ -527,10 +557,13 @@ .legacy-training-page { display: grid !important; - grid-template-columns: minmax(0, 1fr) 360px !important; - gap: 24px !important; + box-sizing: border-box !important; + grid-template-columns: minmax(0, 1fr) 300px !important; + gap: 14px !important; align-items: start !important; width: 100% !important; + padding: 14px !important; + background: #06111d !important; } .legacy-training-page > :not(.training-ai-sidebar) { @@ -553,22 +586,22 @@ } .legacy-training-page .top_item { - margin-bottom: 18px !important; + margin-bottom: 12px !important; } .legacy-training-page .top_item .el-button { display: inline-flex !important; align-items: center !important; justify-content: center !important; - min-width: 150px !important; - height: 42px !important; - padding: 0 18px !important; + min-width: 136px !important; + height: 36px !important; + padding: 0 12px !important; border: 1px solid #2c83aa !important; border-radius: 6px !important; color: #e9fbff !important; background: rgba(13, 64, 88, 0.62) !important; box-shadow: none !important; - font-size: 15px !important; + font-size: 14px !important; font-weight: 800 !important; line-height: 1 !important; letter-spacing: 0 !important; @@ -628,6 +661,10 @@ padding: 14px 14px 18px !important; } + .legacy-training-page { + padding: 12px !important; + } + .student-training-shell .workbench-head { flex-direction: column !important; } diff --git a/src/views/components/TrainingAiSidebar.vue b/src/views/components/TrainingAiSidebar.vue index d70250b..15657dc 100644 --- a/src/views/components/TrainingAiSidebar.vue +++ b/src/views/components/TrainingAiSidebar.vue @@ -163,12 +163,12 @@ async function requestEvaluation() { .training-ai-sidebar { display: flex; flex-direction: column; - gap: 22px; + gap: 16px; align-self: start; min-height: calc(100vh - 106px); - padding: 10px; + padding: 8px; border: 1px solid rgba(0, 174, 255, 0.22); - border-radius: 26px; + border-radius: 22px; background: linear-gradient(180deg, rgba(13, 30, 44, 0.92), rgba(5, 15, 26, 0.96)), radial-gradient(circle at 50% 0%, rgba(0, 220, 255, 0.16), transparent 42%); diff --git a/src/views/components/TrainingMaterialButton.vue b/src/views/components/TrainingMaterialButton.vue index b087ba3..a56d29f 100644 --- a/src/views/components/TrainingMaterialButton.vue +++ b/src/views/components/TrainingMaterialButton.vue @@ -47,5 +47,5 @@ function handleDownload() { diff --git a/src/views/components/TrainingTaskBrief.vue b/src/views/components/TrainingTaskBrief.vue index 92bfb9d..ae1052b 100644 --- a/src/views/components/TrainingTaskBrief.vue +++ b/src/views/components/TrainingTaskBrief.vue @@ -131,10 +131,10 @@ function normalizeGoals(value) { diff --git a/src/views/foundation/product-development-process.vue b/src/views/foundation/product-development-process.vue index a14816c..35acece 100644 --- a/src/views/foundation/product-development-process.vue +++ b/src/views/foundation/product-development-process.vue @@ -582,25 +582,25 @@ function exportOutcome() { .process-core { min-width: 0; - padding: 28px; + padding: 20px; border: 1px solid rgba(0, 180, 255, 0.25); - border-radius: 36px; + border-radius: 24px; background: rgba(8, 18, 28, 0.6); box-shadow: 0 22px 48px rgba(0, 0, 0, 0.22), inset 0 1px 0 rgba(169, 229, 255, 0.08); backdrop-filter: blur(8px); } -.task-header { margin-bottom: 24px; } +.task-header { margin-bottom: 16px; } .task-badge { display: inline-flex; align-items: center; gap: 7px; margin-bottom: 12px; padding: 4px 14px; border: 1px solid rgba(0, 170, 255, 0.5); border-radius: 30px; color: #d6f6ff; background: rgba(0, 170, 255, 0.2); font-size: 12px; font-weight: 700; } .task-title { margin: 0; color: transparent; background: linear-gradient(135deg, #fff, #8bcbff); -webkit-background-clip: text; background-clip: text; font-size: 30px; font-weight: 900; line-height: 1.3; } -.process-workbench { min-width: 0; margin: 0; padding: 18px 22px; border: 1px solid rgba(82, 174, 226, 0.42); border-radius: 26px; background: rgba(0, 25, 42, 0.64); } +.process-workbench { min-width: 0; margin: 0; padding: 14px 16px; border: 1px solid rgba(82, 174, 226, 0.42); border-radius: 20px; background: rgba(0, 25, 42, 0.64); } .process-workbench::before { display: none !important; content: none !important; } .workbench-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; margin-bottom: 22px; padding-bottom: 18px; border-bottom: 1px solid rgba(0, 180, 255, 0.2); } .workbench-actions { display: flex; flex: 0 0 auto; flex-wrap: wrap; justify-content: flex-end; gap: 10px; } .outline-action, .btn-nav { display: inline-flex; align-items: center; justify-content: center; gap: 8px; font-family: inherit; cursor: pointer; } .outline-action, .workbench-actions :deep(.material-download) { min-height: 40px; padding: 0 18px; border: 1px solid rgba(99, 217, 255, 0.55); border-radius: 999px; color: #e9fbff; background: rgba(17, 126, 178, 0.36); box-shadow: none; font-size: 14px; font-weight: 800; line-height: 1; } .outline-action:hover, .workbench-actions :deep(.material-download:hover) { border-color: rgba(116, 235, 255, 0.85); color: #fff; background: rgba(18, 146, 204, 0.5); transform: translateY(-1px); } -.workbench-head--actions-only { justify-content: flex-end; margin-bottom: 18px; padding-bottom: 0; border-bottom: 0; } +.workbench-head--actions-only { justify-content: flex-end; margin-bottom: 10px; padding-bottom: 0; border-bottom: 0; } .step-indicator { position: relative; display: flex; align-items: center; gap: 0; width: calc(100% + 24px); margin: 0 -12px 32px; padding: 18px 22px; overflow-x: auto; border: 1px solid rgba(0, 180, 255, 0.3); border-radius: 18px; background: linear-gradient(90deg, rgba(6, 42, 61, 0.58), rgba(1, 18, 31, 0.42)), rgba(0, 0, 0, 0.3); box-shadow: inset 0 1px 0 rgba(145, 233, 255, 0.08); scrollbar-width: thin; } .step-tab { display: flex; flex: 1 0 145px; align-items: center; min-width: 0; min-height: 66px; padding: 8px 10px; border: 1px solid rgba(68, 141, 177, 0.35); border-radius: 8px; color: #9ab3d0; background: rgba(3, 27, 43, 0.52); box-shadow: inset 0 1px 0 rgba(135, 221, 255, 0.04); font-family: inherit; cursor: pointer; text-align: left; transition: transform .2s ease, color .2s ease, border-color .2s ease, background .2s ease, box-shadow .2s ease; } @@ -608,7 +608,7 @@ function exportOutcome() { .step-tab.active { border-color: rgba(89, 226, 255, .85); color: #fff; background: linear-gradient(115deg, rgba(4, 109, 155, .78), rgba(5, 62, 102, .64)); box-shadow: 0 0 18px rgba(24, 183, 235, .22), inset 0 1px 0 rgba(176, 246, 255, .2); }.step-tab.active .step-marker { border-color: #72e8ff; color: #fff; background: linear-gradient(135deg, #08b7e8, #1375d0); box-shadow: 0 0 0 5px rgba(25, 179, 237, .14), 0 0 18px rgba(46, 208, 255, .58); }.step-tab.complete { color: #dffbff; }.step-tab.complete .step-marker { border-color: rgba(84, 230, 221, .85); color: #062b37; background: #6ef0e1; box-shadow: 0 0 12px rgba(79, 231, 218, .35); } .step-marker { display: inline-flex; flex: 0 0 38px; align-items: center; justify-content: center; width: 38px; height: 38px; border: 1px solid rgba(101, 176, 208, .6); border-radius: 50%; color: #a6c4d9; background: rgba(3, 25, 40, .8); font-size: 12px; font-weight: 900; letter-spacing: .04em; }.step-copy { display: grid; min-width: 0; gap: 4px; margin-left: 10px; }.step-name { display: -webkit-box; overflow: hidden; color: inherit; font-size: 14px; font-weight: 800; line-height: 1.25; text-overflow: ellipsis; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }.step-action { color: #66b6d4; font-size: 10px; font-weight: 800; letter-spacing: .06em; line-height: 1; }.step-connector { position: relative; flex: 0 1 40px; min-width: 18px; height: 2px; margin: 0 8px; background: rgba(98, 169, 201, .34); }.step-connector::after { position: absolute; top: 50%; right: -1px; width: 6px; height: 6px; border-top: 1px solid #73b1ce; border-right: 1px solid #73b1ce; content: ''; transform: translateY(-50%) rotate(45deg); }.step-tab.complete + .step-connector { background: linear-gradient(90deg, #6ef0e1, #1cb8e9); box-shadow: 0 0 9px rgba(53, 218, 235, .46); }.step-tab.complete + .step-connector::after { border-color: #63e6f1; } -.process-step-card { margin: 0; padding: 18px 22px; border: 1px solid rgba(82, 174, 226, 0.42); border-radius: 26px; background: rgba(0, 25, 42, 0.64); }.step-title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; margin-bottom: 14px; }.step-title-row h3 { margin: 4px 0 0; color: #fff; font-size: 22px; font-weight: 900; line-height: 1.3; }.section-eyebrow { margin: 0; color: #71dfff; font-size: 12px; font-weight: 800; line-height: 1.4; }.step-chip { padding: 6px 12px; border: 1px solid rgba(94, 219, 255, .54); border-radius: 999px; color: #a6ebff; background: rgba(9, 104, 149, .2); font-size: 13px; font-weight: 800; } +.process-step-card { margin: 0; padding: 16px; border: 1px solid rgba(82, 174, 226, 0.42); border-radius: 20px; background: rgba(0, 25, 42, 0.64); }.step-title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; margin-bottom: 12px; }.step-title-row h3 { margin: 4px 0 0; color: #fff; font-size: 22px; font-weight: 900; line-height: 1.3; }.section-eyebrow { margin: 0; color: #71dfff; font-size: 12px; font-weight: 800; line-height: 1.4; }.step-chip { padding: 6px 12px; border: 1px solid rgba(94, 219, 255, .54); border-radius: 999px; color: #a6ebff; background: rgba(9, 104, 149, .2); font-size: 13px; font-weight: 800; } .process-intro { margin: 0 0 18px; color: #d7ecff; font-size: 15px; line-height: 1.8; }.training-table-wrap { overflow-x: auto; border: 1px solid rgba(0, 180, 255, .22); border-radius: 22px; background: rgba(1, 14, 26, .72); }.process-table { width: 100%; min-width: 920px; border-collapse: collapse; table-layout: fixed; }.process-table th { padding: 12px; border-right: 1px solid rgba(77, 167, 220, .2); color: #eaffff; background: rgba(8, 55, 82, .98); font-size: 14px; font-weight: 900; text-align: center; }.process-table td { padding: 12px; border-top: 1px solid rgba(77, 167, 220, .2); border-right: 1px solid rgba(77, 167, 220, .2); color: #d7f1ff; background: rgba(1, 14, 26, .48); vertical-align: middle; }.process-table th:last-child, .process-table td:last-child { border-right: 0; }.stage-cell { width: 17%; color: #eafaff; font-size: 14px; font-weight: 900; text-align: center; }.task-no-cell { width: 12%; color: #eafaff; font-size: 14px; font-weight: 800; text-align: center; }.task-cell { color: #cfe8f8; font-size: 14px; line-height: 1.7; text-align: center; }.select-cell { width: 23%; }.select-cell select, .schedule-table input { box-sizing: border-box; width: 100%; min-height: 38px; padding: 7px 10px; border: 1px solid rgba(45, 95, 126, .95); border-radius: 10px; outline: none; color: #eef5ff; background: #0f1a24; font: inherit; }.select-cell select:focus, .schedule-table input:focus { border-color: rgba(92, 224, 255, .7); box-shadow: 0 0 0 3px rgba(0, 174, 255, .13); }.classification-feedback { margin: 6px 0 0; color: #ff9b8f; font-size: 12px; line-height: 1.5; }.is-correct { background: rgba(55, 196, 147, .08); }.is-incorrect { background: rgba(255, 91, 82, .1); }.classification-summary { margin: 16px 0 0; color: #ffbc86; }.classification-summary.is-success { color: #6ee8b1; } .task-actions { display: flex; justify-content: center; gap: 52px; margin-top: 36px; }.btn-nav { min-width: 172px; min-height: 64px; padding: 0 28px; border: 1px solid #2c83aa; border-radius: 6px; color: #dff7ff; background: rgba(13, 64, 88, .62); box-shadow: none; font-size: 24px; font-weight: 900; line-height: 1; }.btn-submit { border-color: #2c83aa; background: rgba(13, 64, 88, .62); }.task-actions .btn-nav .el-icon { display: none; }.btn-nav:hover:not(:disabled) { border-color: #46b5e7; color: #fff; background: rgba(18, 85, 116, .78); }.btn-nav:disabled { cursor: not-allowed; opacity: .6; }.step-placeholder { display: grid; place-items: center; min-height: 280px; padding: 28px; border: 1px dashed rgba(101, 207, 239, .42); border-radius: 18px; color: #b8d9ec; background: rgba(1, 14, 26, .42); text-align: center; }.step-placeholder p { margin: 0 0 8px; color: #eaffff; font-size: 20px; font-weight: 800; } .step-warning { margin: 0 0 16px; padding: 10px 14px; border: 1px solid rgba(255, 173, 93, .62); border-radius: 10px; color: #ffd19f; background: rgba(187, 102, 27, .16); }.schedule-table { min-width: 1180px; }.schedule-table .stage-cell { width: 12%; }.schedule-table .task-no-cell { width: 8%; }.schedule-table .task-cell { width: 21%; }.classification-cell { width: 13%; color: #eafaff; font-weight: 800; text-align: center; }.schedule-table .dependency-input { min-width: 180px; }.schedule-table input:disabled { cursor: not-allowed; opacity: .6; } diff --git a/src/views/product/market-opportunity-selection.vue b/src/views/product/market-opportunity-selection.vue index 46e2da7..7b78065 100644 --- a/src/views/product/market-opportunity-selection.vue +++ b/src/views/product/market-opportunity-selection.vue @@ -141,7 +141,7 @@ function exportOutcome() { const blob = new Blob([JSON.stringify({ taskName: tas