fix: align pricing page with training standard

bugfix/ai-assessment-evidence-rescore
陈沅 2 months ago
parent 2c8615c47e
commit c05ea5ed3f

@ -14,6 +14,18 @@
/>
<section class="price-workbench">
<div class="workbench-head workbench-head--actions-only">
<div class="workbench-actions">
<TrainingMaterialButton
:material-name="taskConfig?.materialName"
:material-url="taskConfig?.materialUrl"
/>
<button type="button" class="outline-action" @click="exportPriceOutcome">
<el-icon><Download /></el-icon>导出实训成果
</button>
</div>
</div>
<nav class="step-indicator" aria-label="新产品定价步骤">
<template v-for="step in pricingSteps" :key="step.no">
<button
@ -38,15 +50,12 @@
</template>
</nav>
<header class="workbench-head">
<section class="step-card">
<header class="step-title-row">
<div>
<p class="section-eyebrow">STEP {{ String(currentStep).padStart(2, "0") }}</p>
<h2>{{ currentStepTitle }}</h2>
<h3>{{ currentStepTitle }}</h3>
</div>
<TrainingMaterialButton
:material-name="taskConfig?.materialName"
:material-url="taskConfig?.materialUrl"
/>
</header>
<template v-if="currentStep === 1">
@ -439,6 +448,7 @@
</div>
</footer>
</template>
</section>
</section>
</main>
@ -454,6 +464,7 @@
</template>
<script setup>
import { Download } from "@element-plus/icons-vue";
import { computed, getCurrentInstance, onMounted, reactive, ref } from "vue";
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue";
@ -934,6 +945,25 @@ function exportPsmTable() {
URL.revokeObjectURL(url);
}
function exportPriceOutcome() {
const payload = {
task: taskTitle.value,
exportedAt: new Date().toISOString(),
stepOne: buildStepOneProgress(),
stepTwo: buildStepTwoProgress(),
stepThree: buildStepThreeProgress(),
stepFour: buildStepFourProgress(),
stepFive: buildStepFiveProgress(),
};
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json;charset=utf-8" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `${taskTitle.value}-实训成果.json`;
link.click();
URL.revokeObjectURL(url);
}
function downloadPsmGuide() {
const content = [
"PSM价格敏感度测试实训操作说明",
@ -1560,20 +1590,59 @@ onMounted(async () => {
}
.price-workbench {
overflow: hidden;
margin-top: 18px;
border: 1px solid var(--line-strong);
border-radius: 18px;
background: var(--surface);
box-shadow: 0 20px 45px rgba(0, 0, 0, 0.2);
margin-top: 28px;
padding: 18px 22px 22px;
border: 1px solid rgba(82, 174, 226, 0.42);
border-radius: 26px;
background: rgba(0, 25, 42, 0.64);
}
.workbench-head--actions-only {
display: flex;
justify-content: flex-end;
margin-bottom: 18px;
}
.workbench-actions {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 10px;
margin-left: auto;
}
.outline-action {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 40px;
padding: 0 18px;
color: #e9fbff;
cursor: pointer;
border: 1px solid rgba(99, 217, 255, 0.55);
border-radius: 999px;
background: rgba(17, 126, 178, 0.36);
font: inherit;
font-size: 14px;
font-weight: 800;
&:hover,
&:focus-visible {
border-color: rgba(116, 235, 255, 0.85);
outline: none;
background: rgba(18, 146, 204, 0.5);
transform: translateY(-1px);
}
}
.step-indicator {
display: flex;
align-items: center;
width: calc(100% - 48px);
margin: 18px 24px 0;
padding: 12px 14px;
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: 14px;
@ -1583,11 +1652,11 @@ onMounted(async () => {
.step-tab {
display: flex;
flex: 1 0 136px;
flex: 1 0 145px;
align-items: center;
min-width: 0;
min-height: 52px;
padding: 7px 9px;
min-height: 66px;
padding: 8px 10px;
color: #9ab3d0;
cursor: pointer;
border: 1px solid rgba(68, 141, 177, 0.35);
@ -1619,11 +1688,11 @@ onMounted(async () => {
.step-marker {
display: inline-flex;
flex: 0 0 30px;
flex: 0 0 38px;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
width: 38px;
height: 38px;
color: #a6c4d9;
border: 1px solid rgba(101, 176, 208, 0.6);
border-radius: 50%;
@ -1637,14 +1706,14 @@ onMounted(async () => {
.step-copy {
display: grid;
min-width: 0;
gap: 3px;
margin-left: 8px;
gap: 4px;
margin-left: 10px;
strong {
display: -webkit-box;
overflow: hidden;
color: inherit;
font-size: 13px;
font-size: 14px;
line-height: 1.25;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
@ -1749,22 +1818,31 @@ onMounted(async () => {
gap: 10px;
}
.workbench-head {
.step-card {
padding: 18px 22px 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: center;
justify-content: space-between;
gap: 16px;
padding: 24px 26px 18px;
border-bottom: 1px solid var(--line);
margin-bottom: 14px;
h2 {
h3 {
margin: 4px 0 0;
font-size: 24px;
color: #fff;
font-size: 22px;
font-weight: 900;
line-height: 1.3;
}
}
.intro-copy {
margin: 20px 26px;
margin: 20px 0;
color: var(--muted);
line-height: 1.75;
}
@ -1774,7 +1852,7 @@ onMounted(async () => {
align-items: center;
justify-content: space-between;
gap: 20px;
margin: 0 26px 22px;
margin: 0 0 22px;
padding: 18px 20px;
border: 1px solid var(--line);
border-radius: 14px;
@ -1813,7 +1891,7 @@ onMounted(async () => {
.pricing-table-wrap {
overflow-x: auto;
margin: 0 26px;
margin: 0;
border: 1px solid var(--line);
border-radius: 14px;
}
@ -1855,7 +1933,7 @@ onMounted(async () => {
display: flex;
align-items: center;
gap: 10px;
margin: 0 26px 12px;
margin: 0 0 12px;
span {
display: grid;
@ -1930,7 +2008,7 @@ onMounted(async () => {
}
.validation-message {
margin: 14px 26px 0;
margin: 14px 0 0;
color: #ff9e9e;
line-height: 1.5;
}
@ -2073,7 +2151,7 @@ onMounted(async () => {
align-items: center;
justify-content: space-between;
gap: 20px;
margin: 0 26px;
margin: 0;
padding: 16px 18px;
border: 1px solid var(--line);
border-radius: 12px;
@ -2117,7 +2195,7 @@ onMounted(async () => {
}
.final-analysis-section {
margin: 0 26px;
margin: 0;
h3 {
margin: 0 0 12px;
@ -2160,7 +2238,7 @@ onMounted(async () => {
.psm-section {
display: grid;
gap: 20px;
padding: 0 26px 26px;
padding: 0;
}
.psm-toolbar,

@ -13,6 +13,10 @@ const stepper = page.match(
assert.ok(stepper, "product pricing must use the shared step-indicator navigation pattern");
assert.match(stepper, /v-for="step in pricingSteps"/, "the step navigation must be rendered from actual step data");
assert.match(stepper, /@click="switchStep\(step\.no\)"/, "each pricing step must be navigable");
assert.match(page, /class="workbench-head workbench-head--actions-only"/, "pricing must use the standard top action row");
assert.match(page, /@click="exportPriceOutcome"/, "pricing must provide the standard export action");
assert.ok(page.indexOf("workbench-head--actions-only") < page.indexOf('class="step-indicator"'), "actions must appear before the step indicator");
assert.ok(page.indexOf('class="step-indicator"') < page.indexOf('<section class="step-card">'), "step content must be inside a separate standard step card");
assert.match(page, /const pricingSteps = computed\(\(\) => \[/, "the page must define its pricing steps");
for (const label of ["pricingTitle", "competitorPricingTitle", "valueBasedPricingTitle", "psmTitle", "finalPricingTitle"]) {
assert.match(page, new RegExp(`title: labels\\.${label}`), `${label} must appear in the five-step navigation`);

Loading…
Cancel
Save