diff --git a/src/views/demand/consumer-behavior.vue b/src/views/demand/consumer-behavior.vue index 4538b43..90d36f2 100644 --- a/src/views/demand/consumer-behavior.vue +++ b/src/views/demand/consumer-behavior.vue @@ -22,7 +22,7 @@
- +
@@ -306,11 +306,6 @@ -
- - 重新实训 - -
diff --git a/src/views/demand/consumer-scenario.vue b/src/views/demand/consumer-scenario.vue index 23a267a..6bc913e 100644 --- a/src/views/demand/consumer-scenario.vue +++ b/src/views/demand/consumer-scenario.vue @@ -22,7 +22,7 @@
- +
@@ -254,11 +254,6 @@ -
- - 重新实训 - -
diff --git a/src/views/demand/feature-conversion.vue b/src/views/demand/feature-conversion.vue index 0ae34f6..824aa15 100644 --- a/src/views/demand/feature-conversion.vue +++ b/src/views/demand/feature-conversion.vue @@ -22,7 +22,7 @@
- +
@@ -248,12 +248,7 @@ -
- - 清空填写 - -
-
+ -
- - 重新实训 - -
diff --git a/src/views/product/differentiation.vue b/src/views/product/differentiation.vue index 3169922..e8c53d6 100644 --- a/src/views/product/differentiation.vue +++ b/src/views/product/differentiation.vue @@ -22,7 +22,8 @@
- +
@@ -306,29 +307,32 @@
- - - -
- -
- 清空填写 +
+ + +
+
+ Step {{ String(form.activeStep).padStart(2, "0") }} / {{ + String(configuredSteps.length).padStart(2, "0") }} + +
@@ -1532,15 +1536,36 @@ async function handleValueCanvasUpload(event) { color: var(--student-success); } -/* ===== 底部步骤导航按钮 ===== */ +/* ============================================================ + * 底部导航:工具栏式 + * ============================================================ */ .step-navigation { + position: relative; display: flex; - justify-content: center; - gap: 14px; - margin-top: 20px; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-top: 16px; + padding: 12px 16px; + border: 1px solid var(--student-panel-border); + border-radius: 10px; + background: #ffffff; +} + +.step-nav-left, +.step-nav-right { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: nowrap; } -.step-navigation .btn-nav { +.step-nav-right { + margin-left: auto; +} + +.btn-nav { display: inline-flex; align-items: center; justify-content: center; @@ -1552,61 +1577,125 @@ async function handleValueCanvasUpload(event) { border-radius: 8px; color: var(--student-accent); background: #ffffff; + font-family: inherit; font-size: var(--student-fs-button); font-weight: 600; cursor: pointer; - text-shadow: none; - transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease, transform 0.16s ease, box-shadow 0.16s ease; -} + transition: all 0.16s ease; + white-space: nowrap; -.step-navigation .btn-nav:hover:not(:disabled) { - border-color: var(--student-accent); - background: var(--student-accent-soft); - color: var(--student-accent); - transform: translateY(-1px); - box-shadow: 0 3px 10px rgba(37, 99, 235, 0.12); + .el-icon { + font-size: 18px; + } + + &:hover:not(:disabled) { + border-color: var(--student-accent); + background: var(--student-accent-soft); + transform: translateY(-1px); + box-shadow: 0 2px 6px rgba(37, 99, 235, 0.12); + } } -.step-navigation .btn-nav.btn-save { - border-color: #e5e7eb; - color: #374151; - background: #f3f4f6; +.btn-primary { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + min-height: 36px; + height: 36px; + padding: 0 14px; + border: 1px solid var(--student-accent); + border-radius: 8px; + color: #ffffff; + background: var(--student-accent); + font-family: inherit; + font-size: var(--student-fs-button); + font-weight: 600; + box-shadow: 0 1px 2px rgba(37, 99, 235, 0.2), 0 2px 8px rgba(37, 99, 235, 0.15); + cursor: pointer; + transition: all 0.16s ease; + white-space: nowrap; + + .el-icon { + font-size: 18px; + } + + &:hover:not(:disabled) { + border-color: var(--student-accent-deep); + background: var(--student-accent-deep); + transform: translateY(-1px); + box-shadow: 0 2px 6px rgba(29, 78, 216, 0.3), 0 4px 12px rgba(29, 78, 216, 0.22); + } + + &:active:not(:disabled) { + transform: translateY(0); + } } -.step-navigation .btn-nav.btn-save:hover:not(:disabled) { - border-color: var(--student-accent); - background: #e9ecef; +.btn-ghost { + border-color: rgba(37, 99, 235, 0.35); color: var(--student-accent); -} + background: #ffffff; -.step-navigation .btn-nav.btn-primary { - border-color: var(--student-accent); - color: #ffffff; - background: var(--student-accent); - font-weight: 700; - box-shadow: 0 3px 10px rgba(37, 99, 235, 0.28); + &:hover:not(:disabled) { + border-color: var(--student-accent); + background: var(--student-accent-soft); + } } -.step-navigation .btn-nav.btn-primary:hover:not(:disabled) { - background: var(--student-accent-deep); - border-color: var(--student-accent-deep); - box-shadow: 0 5px 14px rgba(29, 78, 216, 0.32); +.btn-save { + border-color: #e5e7eb; + color: #4b5563; + background: #f9fafb; + + &:hover:not(:disabled) { + border-color: #d1d5db; + background: #f3f4f6; + transform: none; + box-shadow: none; + } } -.step-navigation .btn-nav:disabled { +.btn-primary:disabled, +.btn-ghost:disabled, +.btn-save:disabled, +.btn-nav:disabled { cursor: not-allowed; opacity: 0.5; transform: none; box-shadow: none; } -/* training-actions:容器保留,el-button 由全局接管 */ -.training-actions { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 14px; - margin-top: 20px; +.step-progress { + color: var(--student-text-secondary); + font-family: var(--student-font-data); + font-size: var(--student-fs-caption); + font-weight: 700; + letter-spacing: 0.05em; + white-space: nowrap; +} + +@media (max-width: 1100px) { + .step-nav-right { + margin-left: 0; + } +} + +@media (max-width: 900px) { + .step-navigation { + flex-direction: column; + align-items: stretch; + } + + .step-nav-left, + .step-nav-right { + justify-content: center; + } + + .btn-nav, + .btn-primary { + width: 100%; + } } /* ===== 响应式 ===== */ @@ -1627,15 +1716,13 @@ async function handleValueCanvasUpload(event) { } .workbench-head, - .workbench-actions, - .step-navigation { + .workbench-actions { flex-direction: column; align-items: stretch; } .workbench-actions, - .outline-action, - .btn-nav { + .outline-action { width: 100%; } @@ -1873,58 +1960,4 @@ async function handleValueCanvasUpload(event) { box-shadow: 0 2px 6px rgba(37, 99, 235, 0.12) !important; transform: none !important; } - -/* ============================================================ - * 底部提交/清空按钮:training-actions - * ============================================================ */ -#app .studentLayout section.app-main:has(.product-positioning-page) .training-actions .el-button.training-action { - min-width: 140px !important; - min-height: 34px !important; - height: 34px !important; - padding: 0 14px !important; - border: 1px solid rgba(37, 99, 235, 0.35) !important; - border-radius: 8px !important; - color: #2563eb !important; - background: #ffffff !important; - font-size: var(--student-fs-body) !important; - font-weight: 700 !important; - line-height: 1 !important; - letter-spacing: 0 !important; - text-shadow: none !important; - box-shadow: none !important; -} - -#app .studentLayout section.app-main:has(.product-positioning-page) .training-actions .el-button.training-action:hover:not(.is-disabled) { - transform: translateY(-1px) !important; - border-color: #2563eb !important; - background: #eff6ff !important; - color: #2563eb !important; - box-shadow: 0 3px 10px rgba(37, 99, 235, 0.12) !important; -} - -#app .studentLayout section.app-main:has(.product-positioning-page) .training-actions .el-button.training-action--submit { - border-color: #2563eb !important; - color: #ffffff !important; - background: #2563eb !important; - box-shadow: 0 3px 10px rgba(37, 99, 235, 0.28) !important; -} - -#app .studentLayout section.app-main:has(.product-positioning-page) .training-actions .el-button.training-action--submit:hover:not(.is-disabled) { - border-color: #1d4ed8 !important; - background: #1d4ed8 !important; - color: #ffffff !important; - box-shadow: 0 5px 14px rgba(29, 78, 216, 0.32) !important; -} - -#app .studentLayout section.app-main:has(.product-positioning-page) .training-actions .el-button.training-action--reset { - border-color: #e5e7eb !important; - color: #374151 !important; - background: #f3f4f6 !important; -} - -#app .studentLayout section.app-main:has(.product-positioning-page) .training-actions .el-button.training-action--reset:hover:not(.is-disabled) { - border-color: #2563eb !important; - color: #2563eb !important; - background: #eff6ff !important; -} diff --git a/src/views/product/market-opportunity-selection.vue b/src/views/product/market-opportunity-selection.vue index 1919e3a..ff6da41 100644 --- a/src/views/product/market-opportunity-selection.vue +++ b/src/views/product/market-opportunity-selection.vue @@ -69,11 +69,6 @@ -

第 {{ currentStep }} 步内容待配置

已保存的步骤可从上方步骤栏返回查看。
+ +
+
+ + +
+
+ Step {{ String(currentStep).padStart(2, "0") }} / {{ + String(trainingSteps.length).padStart(2, "0") }} + +
+
import * as XLSX from "xlsx" -import { Download, RefreshLeft, UploadFilled } from "@element-plus/icons-vue" +import { ArrowLeft, ArrowRight, CircleCheck, Download, RefreshLeft, UploadFilled } from "@element-plus/icons-vue" import { checkMarketOpportunitySelectionStepOne, checkMarketOpportunitySelectionStepTwo, checkMarketOpportunitySelectionStepThree, checkMarketOpportunitySelectionStepFour, checkMarketOpportunitySelectionStepFive, checkMarketOpportunitySelectionStepSix, deleteStudentTrainingAnswer, getStudentTrainingAnswer, saveStudentTrainingAnswer, uploadStudentTrainingFile } from "@/api/studentTrainingAnswer" import { getTrainingTaskByKey } from "@/api/trainingTask" import { parseTrainingArray } from "@/views/training/taskKeyMap" @@ -358,6 +358,39 @@ function buildStepFourOutcome() { return { title: taskTitle.value, visualization function buildStepFiveOutcome() { return { title: taskTitle.value, reviewInsights: Object.fromEntries(products.map((product) => [product.id, { ...reviewInsights.value[product.id] }])) } } function buildStepSixOutcome() { return { title: taskTitle.value, developmentConclusion: developmentConclusion.value.trim() } } function buildDynamicStepAnswers(overrides) { return { ...dynamicStepAnswers.value, ...overrides } } + +/* ===== 底部步骤导航 ===== */ +function prevStep() { if (currentStep.value > 1) switchStep(currentStep.value - 1) } +async function saveCurrentProgress() { + if (saving.value) return + saving.value = true + try { + const step = currentStep.value + const payload = { currentStep: step, submitted: false, saveAction: "SAVE" } + if (step === 1) payload.step1Answer = JSON.stringify(buildStepOneOutcome()) + if (step === 2) payload.step2Answer = JSON.stringify(buildStepTwoOutcome()) + if (step === 3) payload.step3Answer = JSON.stringify(buildStepThreeOutcome()) + if (step === 4) payload.step4Answer = JSON.stringify(buildStepFourOutcome()) + if (step === 5) { dynamicStepAnswers.value = buildDynamicStepAnswers({ step5: buildStepFiveOutcome() }); payload.dynamicStepAnswers = JSON.stringify(dynamicStepAnswers.value) } + if (step === 6) { dynamicStepAnswers.value = buildDynamicStepAnswers({ step5: buildStepFiveOutcome(), step6: buildStepSixOutcome() }); payload.dynamicStepAnswers = JSON.stringify(dynamicStepAnswers.value) } + await saveStudentTrainingAnswer(TASK_KEY, payload) + proxy?.$modal?.msgSuccess("进度已保存") + } catch (error) { + proxy?.$modal?.msgError?.(error?.message || "保存失败,请重试") + } finally { + saving.value = false + } +} +function nextStep() { + const step = currentStep.value + if (step === 1) checkAndContinue() + else if (step === 2) saveAndGoToStepThree() + else if (step === 3) saveAndGoToStepFour() + else if (step === 4) saveAndGoToStepFive() + else if (step === 5) saveAndGoToStepSix() + else if (step === 6) saveAndCompleteTraining() +} + async function checkAndContinue() { if (saving.value) return; saving.value = true; try { const result = await checkMarketOpportunitySelectionStepOne(indicators.map((item) => ({ id: item.id, category: answers.value[item.id] }))); stepOneCheckResults.value = Object.fromEntries((result?.data?.items || []).map((item) => [item.id, item])); if (!result?.data?.allCorrect) throw new Error(`当前答对 ${result?.data?.correctCount || 0}/${indicators.length} 项,请根据表内提示修改`); await saveStudentTrainingAnswer(TASK_KEY, { step1Answer: JSON.stringify(buildStepOneOutcome()), currentStep: 2, submitted: false, saveAction: "SAVE" }); stepOnePassed.value = true; currentStep.value = 2; proxy?.$modal?.msgSuccess("分类正确,已进入第 2 步") } catch (error) { proxy?.$modal?.msgError?.(error?.message || "请完成指标分类并根据提示修改") } finally { saving.value = false } } function isIntegerWeight(value) { return value !== null && value !== "" && Number.isInteger(Number(value)) && Number(value) >= 1 && Number(value) <= 100 } function dimensionWeightTotal(group) { return calculateDimensionWeightTotal(group, weights.value) } @@ -986,14 +1019,6 @@ function exportOutcome() { const blob = new Blob([JSON.stringify({ taskName: tas outline-offset: -2px; } -/* ===== 底部按钮 ===== */ -.step-actions { - display: flex; - justify-content: center; - gap: 10px; - margin-top: 18px; -} - .btn { display: inline-flex; align-items: center; @@ -1043,6 +1068,168 @@ function exportOutcome() { const blob = new Blob([JSON.stringify({ taskName: tas box-shadow: none; } +/* ============================================================ + * 底部导航:工具栏式 + * ============================================================ */ +.step-navigation { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 10px; + margin-top: 16px; + padding: 12px 16px; + border: 1px solid var(--student-panel-border); + border-radius: 10px; + background: #ffffff; +} + +.step-nav-left, +.step-nav-right { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: nowrap; +} + +.step-nav-right { + margin-left: auto; +} + +.btn-nav { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + min-height: 36px; + height: 36px; + padding: 0 14px; + border: 1px solid rgba(37, 99, 235, 0.35); + border-radius: 8px; + color: var(--student-accent); + background: #ffffff; + font-family: inherit; + font-size: var(--student-fs-button); + font-weight: 600; + cursor: pointer; + transition: all 0.16s ease; + white-space: nowrap; + + .el-icon { + font-size: 18px; + } + + &:hover:not(:disabled) { + border-color: var(--student-accent); + background: var(--student-accent-soft); + transform: translateY(-1px); + box-shadow: 0 2px 6px rgba(37, 99, 235, 0.12); + } +} + +.btn-primary { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + min-height: 36px; + height: 36px; + padding: 0 14px; + border: 1px solid var(--student-accent); + border-radius: 8px; + color: #ffffff; + background: var(--student-accent); + font-family: inherit; + font-size: var(--student-fs-button); + font-weight: 600; + box-shadow: 0 1px 2px rgba(37, 99, 235, 0.2), 0 2px 8px rgba(37, 99, 235, 0.15); + cursor: pointer; + transition: all 0.16s ease; + white-space: nowrap; + + .el-icon { + font-size: 18px; + } + + &:hover:not(:disabled) { + border-color: var(--student-accent-deep); + background: var(--student-accent-deep); + transform: translateY(-1px); + box-shadow: 0 2px 6px rgba(29, 78, 216, 0.3), 0 4px 12px rgba(29, 78, 216, 0.22); + } + + &:active:not(:disabled) { + transform: translateY(0); + } +} + +.btn-ghost { + border-color: rgba(37, 99, 235, 0.35); + color: var(--student-accent); + background: #ffffff; + + &:hover:not(:disabled) { + border-color: var(--student-accent); + background: var(--student-accent-soft); + } +} + +.btn-save { + border-color: #e5e7eb; + color: #4b5563; + background: #f9fafb; + + &:hover:not(:disabled) { + border-color: #d1d5db; + background: #f3f4f6; + transform: none; + box-shadow: none; + } +} + +.btn-primary:disabled, +.btn-ghost:disabled, +.btn-save:disabled, +.btn-nav:disabled { + cursor: not-allowed; + opacity: 0.5; + transform: none; + box-shadow: none; +} + +.step-progress { + color: var(--student-text-secondary); + font-family: var(--student-font-data); + font-size: var(--student-fs-caption); + font-weight: 700; + letter-spacing: 0.05em; + white-space: nowrap; +} + +@media (max-width: 1100px) { + .step-nav-right { + margin-left: 0; + } +} + +@media (max-width: 900px) { + .step-navigation { + flex-direction: column; + align-items: stretch; + } + + .step-nav-left, + .step-nav-right { + justify-content: center; + } + + .btn-nav, + .btn-primary { + width: 100%; + } +} + .placeholder { padding: 60px 20px; text-align: center;