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