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"> <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="新产品定价步骤"> <nav class="step-indicator" aria-label="新产品定价步骤">
<template v-for="step in pricingSteps" :key="step.no"> <template v-for="step in pricingSteps" :key="step.no">
<button <button
@ -38,15 +50,12 @@
</template> </template>
</nav> </nav>
<header class="workbench-head"> <section class="step-card">
<header class="step-title-row">
<div> <div>
<p class="section-eyebrow">STEP {{ String(currentStep).padStart(2, "0") }}</p> <p class="section-eyebrow">STEP {{ String(currentStep).padStart(2, "0") }}</p>
<h2>{{ currentStepTitle }}</h2> <h3>{{ currentStepTitle }}</h3>
</div> </div>
<TrainingMaterialButton
:material-name="taskConfig?.materialName"
:material-url="taskConfig?.materialUrl"
/>
</header> </header>
<template v-if="currentStep === 1"> <template v-if="currentStep === 1">
@ -440,6 +449,7 @@
</footer> </footer>
</template> </template>
</section> </section>
</section>
</main> </main>
<TrainingAiSidebar <TrainingAiSidebar
@ -454,6 +464,7 @@
</template> </template>
<script setup> <script setup>
import { Download } from "@element-plus/icons-vue";
import { computed, getCurrentInstance, onMounted, reactive, ref } from "vue"; import { computed, getCurrentInstance, onMounted, reactive, ref } from "vue";
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue"; import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue"; import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue";
@ -934,6 +945,25 @@ function exportPsmTable() {
URL.revokeObjectURL(url); 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() { function downloadPsmGuide() {
const content = [ const content = [
"PSM价格敏感度测试实训操作说明", "PSM价格敏感度测试实训操作说明",
@ -1560,20 +1590,59 @@ onMounted(async () => {
} }
.price-workbench { .price-workbench {
overflow: hidden; margin-top: 28px;
margin-top: 18px; padding: 18px 22px 22px;
border: 1px solid var(--line-strong); border: 1px solid rgba(82, 174, 226, 0.42);
border-radius: 18px; border-radius: 26px;
background: var(--surface); background: rgba(0, 25, 42, 0.64);
box-shadow: 0 20px 45px rgba(0, 0, 0, 0.2); }
.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 { .step-indicator {
display: flex; display: flex;
align-items: center; align-items: center;
width: calc(100% - 48px); gap: 0;
margin: 18px 24px 0; width: calc(100% + 24px);
padding: 12px 14px; margin: 0 -12px 32px;
padding: 18px 22px;
overflow-x: auto; overflow-x: auto;
border: 1px solid rgba(0, 180, 255, 0.3); border: 1px solid rgba(0, 180, 255, 0.3);
border-radius: 14px; border-radius: 14px;
@ -1583,11 +1652,11 @@ onMounted(async () => {
.step-tab { .step-tab {
display: flex; display: flex;
flex: 1 0 136px; flex: 1 0 145px;
align-items: center; align-items: center;
min-width: 0; min-width: 0;
min-height: 52px; min-height: 66px;
padding: 7px 9px; padding: 8px 10px;
color: #9ab3d0; color: #9ab3d0;
cursor: pointer; cursor: pointer;
border: 1px solid rgba(68, 141, 177, 0.35); border: 1px solid rgba(68, 141, 177, 0.35);
@ -1619,11 +1688,11 @@ onMounted(async () => {
.step-marker { .step-marker {
display: inline-flex; display: inline-flex;
flex: 0 0 30px; flex: 0 0 38px;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 30px; width: 38px;
height: 30px; height: 38px;
color: #a6c4d9; color: #a6c4d9;
border: 1px solid rgba(101, 176, 208, 0.6); border: 1px solid rgba(101, 176, 208, 0.6);
border-radius: 50%; border-radius: 50%;
@ -1637,14 +1706,14 @@ onMounted(async () => {
.step-copy { .step-copy {
display: grid; display: grid;
min-width: 0; min-width: 0;
gap: 3px; gap: 4px;
margin-left: 8px; margin-left: 10px;
strong { strong {
display: -webkit-box; display: -webkit-box;
overflow: hidden; overflow: hidden;
color: inherit; color: inherit;
font-size: 13px; font-size: 14px;
line-height: 1.25; line-height: 1.25;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
@ -1749,22 +1818,31 @@ onMounted(async () => {
gap: 10px; 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; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 16px; gap: 16px;
padding: 24px 26px 18px; margin-bottom: 14px;
border-bottom: 1px solid var(--line);
h2 { h3 {
margin: 4px 0 0; margin: 4px 0 0;
font-size: 24px; color: #fff;
font-size: 22px;
font-weight: 900;
line-height: 1.3;
} }
} }
.intro-copy { .intro-copy {
margin: 20px 26px; margin: 20px 0;
color: var(--muted); color: var(--muted);
line-height: 1.75; line-height: 1.75;
} }
@ -1774,7 +1852,7 @@ onMounted(async () => {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 20px; gap: 20px;
margin: 0 26px 22px; margin: 0 0 22px;
padding: 18px 20px; padding: 18px 20px;
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: 14px; border-radius: 14px;
@ -1813,7 +1891,7 @@ onMounted(async () => {
.pricing-table-wrap { .pricing-table-wrap {
overflow-x: auto; overflow-x: auto;
margin: 0 26px; margin: 0;
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: 14px; border-radius: 14px;
} }
@ -1855,7 +1933,7 @@ onMounted(async () => {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
margin: 0 26px 12px; margin: 0 0 12px;
span { span {
display: grid; display: grid;
@ -1930,7 +2008,7 @@ onMounted(async () => {
} }
.validation-message { .validation-message {
margin: 14px 26px 0; margin: 14px 0 0;
color: #ff9e9e; color: #ff9e9e;
line-height: 1.5; line-height: 1.5;
} }
@ -2073,7 +2151,7 @@ onMounted(async () => {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 20px; gap: 20px;
margin: 0 26px; margin: 0;
padding: 16px 18px; padding: 16px 18px;
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: 12px; border-radius: 12px;
@ -2117,7 +2195,7 @@ onMounted(async () => {
} }
.final-analysis-section { .final-analysis-section {
margin: 0 26px; margin: 0;
h3 { h3 {
margin: 0 0 12px; margin: 0 0 12px;
@ -2160,7 +2238,7 @@ onMounted(async () => {
.psm-section { .psm-section {
display: grid; display: grid;
gap: 20px; gap: 20px;
padding: 0 26px 26px; padding: 0;
} }
.psm-toolbar, .psm-toolbar,

@ -13,6 +13,10 @@ const stepper = page.match(
assert.ok(stepper, "product pricing must use the shared step-indicator navigation pattern"); 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, /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(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"); assert.match(page, /const pricingSteps = computed\(\(\) => \[/, "the page must define its pricing steps");
for (const label of ["pricingTitle", "competitorPricingTitle", "valueBasedPricingTitle", "psmTitle", "finalPricingTitle"]) { 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`); assert.match(page, new RegExp(`title: labels\\.${label}`), `${label} must appear in the five-step navigation`);

Loading…
Cancel
Save