|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="supplier-page student-training-shell" v-loading="loading">
|
|
|
|
|
|
<main class="supplier-core">
|
|
|
|
|
|
<section class="task-header">
|
|
|
|
|
|
<div class="task-head-main">
|
|
|
|
|
|
<h1 class="task-title">{{ taskTitle }}</h1>
|
|
|
|
|
|
<p class="task-brief">
|
|
|
|
|
|
<span class="task-brief-text">{{ taskBackground }}</span>
|
|
|
|
|
|
<button type="button" class="task-brief-toggle" @click="briefOpen = !briefOpen">
|
|
|
|
|
|
{{ briefOpen ? "收起" : "展开目标与要求" }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<span v-if="briefOpen" class="task-brief-more">
|
|
|
|
|
|
<b>实训目标</b>
|
|
|
|
|
|
<ul><li v-for="goal in taskGoals" :key="goal">{{ goal }}</li></ul>
|
|
|
|
|
|
<b>实训要求</b>
|
|
|
|
|
|
<p>{{ taskRequirement }}</p>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="supplier-toolbar">
|
|
|
|
|
|
<TrainingMaterialButton
|
|
|
|
|
|
:material-name="taskConfig?.materialName"
|
|
|
|
|
|
:material-url="taskConfig?.materialUrl"
|
|
|
|
|
|
:training-data-name="taskConfig?.trainingDataName"
|
|
|
|
|
|
:training-data-url="taskConfig?.trainingDataUrl"
|
|
|
|
|
|
:guide-manual-name="taskConfig?.guideManualName"
|
|
|
|
|
|
:guide-manual-url="taskConfig?.guideManualUrl"
|
|
|
|
|
|
:ai-manual-name="taskConfig?.aiManualName"
|
|
|
|
|
|
:ai-manual-url="taskConfig?.aiManualUrl"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="supplier-workbench">
|
|
|
|
|
|
<nav class="step-indicator" aria-label="供应渠道评估步骤">
|
|
|
|
|
|
<button type="button" :class="['step-tab', { active: currentStep === 1, complete: currentStep > 1 }]" :aria-current="currentStep === 1 ? 'step' : undefined" @click="currentStep = 1">
|
|
|
|
|
|
<span class="step-marker">01</span><span class="step-copy"><strong class="step-name">设置评估权重</strong></span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button type="button" :class="['step-tab', { active: currentStep === 2, complete: currentStep > 2 }]" :aria-current="currentStep === 2 ? 'step' : undefined" @click="openStepTwo">
|
|
|
|
|
|
<span class="step-marker">02</span><span class="step-copy"><strong class="step-name">渠道评分与对比</strong></span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button type="button" :class="['step-tab', { active: currentStep === 3 }]" :aria-current="currentStep === 3 ? 'step' : undefined" @click="openStepThree">
|
|
|
|
|
|
<span class="step-marker">03</span><span class="step-copy"><strong class="step-name">采购建议与风险</strong></span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
|
|
<template v-if="currentStep === 1">
|
|
|
|
|
|
<section class="intro-card">
|
|
|
|
|
|
<p class="step-caption">Step 01</p>
|
|
|
|
|
|
<h2>设置两种采购场景的评估权重</h2>
|
|
|
|
|
|
<p>试产更关注交付周期和采购灵活性;量产更关注采购成本和品质可靠性。每个场景的权重合计必须为 100%。</p>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="weights-grid">
|
|
|
|
|
|
<article class="weight-card">
|
|
|
|
|
|
<div class="card-title">
|
|
|
|
|
|
<div><span>场景 A</span><h3>小批量试产(100台)</h3></div>
|
|
|
|
|
|
<strong :class="{ danger: smallWeightTotal !== 100 }">{{ smallWeightTotal }}%</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<table class="training-table">
|
|
|
|
|
|
<thead><tr><th>评估维度</th><th>维度说明</th><th>权重(%)</th></tr></thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="row in rows" :key="row.key">
|
|
|
|
|
|
<td>{{ row.name }}</td><td>{{ row.description }}</td>
|
|
|
|
|
|
<td><el-input-number v-model="row.smallBatchWeight" :min="0" :max="100" :precision="0" controls-position="right" /></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
|
|
|
|
|
|
<article class="weight-card">
|
|
|
|
|
|
<div class="card-title">
|
|
|
|
|
|
<div><span>场景 B</span><h3>大规模量产(10000台)</h3></div>
|
|
|
|
|
|
<strong :class="{ danger: largeWeightTotal !== 100 }">{{ largeWeightTotal }}%</strong>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<table class="training-table">
|
|
|
|
|
|
<thead><tr><th>评估维度</th><th>维度说明</th><th>权重(%)</th></tr></thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="row in rows" :key="row.key">
|
|
|
|
|
|
<td>{{ row.name }}</td><td>{{ row.description }}</td>
|
|
|
|
|
|
<td><el-input-number v-model="row.largeBatchWeight" :min="0" :max="100" :precision="0" controls-position="right" /></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<footer class="page-actions">
|
|
|
|
|
|
<el-button type="primary" size="large" @click="saveStepOne">保存并进入渠道评分 <el-icon><CircleCheck /></el-icon></el-button>
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<template v-else-if="currentStep === 2">
|
|
|
|
|
|
<section class="intro-card compact">
|
|
|
|
|
|
<p class="step-caption">Step 02</p>
|
|
|
|
|
|
<h2>为供应渠道评分并比较结果</h2>
|
|
|
|
|
|
<p>每项评分为 0–10 分,渠道名称与评分由学生填写;系统按第一步已保存的试产/量产权重分别计算两个加权总分。</p>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="score-section">
|
|
|
|
|
|
<div class="section-heading">
|
|
|
|
|
|
<div><span>渠道评分</span><h3>供应渠道评分与加权对比</h3></div>
|
|
|
|
|
|
<small>试产加权总分 = Σ(评分 × 试产权重) · 量产加权总分 = Σ(评分 × 量产权重)</small>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="table-scroll">
|
|
|
|
|
|
<table class="training-table score-table merged-score-table">
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr class="group-header">
|
|
|
|
|
|
<th rowspan="2" class="col-channel">渠道</th>
|
|
|
|
|
|
<th :colspan="rows.length" class="group-dimensions">维度评分(0–10 分)</th>
|
|
|
|
|
|
<th class="group-total group-total-small">试产加权总分</th>
|
|
|
|
|
|
<th class="group-total group-total-large">量产加权总分</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th v-for="dimension in rows" :key="dimension.key" class="col-dimension">{{ dimension.name }}</th>
|
|
|
|
|
|
<th class="col-total col-total-small">Σ × 试产权重</th>
|
|
|
|
|
|
<th class="col-total col-total-large">Σ × 量产权重</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="row in channelRows" :key="row.id">
|
|
|
|
|
|
<td class="col-channel"><el-input v-model.trim="row.channel" maxlength="40" placeholder="填写渠道名称" /></td>
|
|
|
|
|
|
<td v-for="dimension in rows" :key="dimension.key" class="col-dimension">
|
|
|
|
|
|
<el-input-number v-model="scoreFor(row, dimension.key).score" :min="0" :max="10" :precision="0" controls-position="right" />
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="total-score col-total col-total-small">{{ weightedScore(row, 'smallBatch') }}</td>
|
|
|
|
|
|
<td class="total-score col-total col-total-large">{{ weightedScore(row, 'largeBatch') }}</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<footer class="page-actions split-actions">
|
|
|
|
|
|
<el-button size="large" @click="currentStep = 1">上一步</el-button>
|
|
|
|
|
|
<el-button type="primary" size="large" @click="saveStepTwo">保存评分结果</el-button>
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<section class="intro-card compact">
|
|
|
|
|
|
<p class="step-caption">Step 03</p>
|
|
|
|
|
|
<h2>渠道优选、采购建议与供应风险</h2>
|
|
|
|
|
|
<p>最优和次优渠道由第二步的加权评分自动排名生成,学生需要分析差异、形成采购建议,并完成供应风险评估。</p>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="analysis-panel">
|
|
|
|
|
|
<div class="section-heading">
|
|
|
|
|
|
<div><span>(一)渠道优选与场景分析</span><h3>根据评分结果形成综合采购建议</h3></div>
|
|
|
|
|
|
<small>排名结果为只读数据</small>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="table-scroll">
|
|
|
|
|
|
<table class="training-table ranking-table">
|
|
|
|
|
|
<thead><tr><th>采购场景</th><th>最优渠道(排名1)</th><th>次优渠道(排名2)</th><th>分差与分析</th></tr></thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="analysis in scenarioAnalyses" :key="analysis.scenario">
|
|
|
|
|
|
<td class="scene-name">{{ analysis.label }}</td>
|
|
|
|
|
|
<td><div class="rank-result"><strong>{{ rankAt(analysis.scenario, 0).channel || '--' }}</strong><span>{{ formatScore(rankAt(analysis.scenario, 0).score) }} 分</span></div></td>
|
|
|
|
|
|
<td><div class="rank-result"><strong>{{ rankAt(analysis.scenario, 1).channel || '--' }}</strong><span>{{ formatScore(rankAt(analysis.scenario, 1).score) }} 分</span></div></td>
|
|
|
|
|
|
<td><el-input v-model.trim="analysis.differenceAnalysis" type="textarea" :rows="3" maxlength="2000" show-word-limit placeholder="结合分差、采购侧重点与渠道能力进行分析" /></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="recommendation-card">
|
|
|
|
|
|
<label for="procurement-recommendation">综合采购建议</label>
|
|
|
|
|
|
<el-input id="procurement-recommendation" v-model.trim="procurementRecommendation" type="textarea" :rows="5" maxlength="3000" show-word-limit placeholder="说明试产与量产阶段的采购组合、切换原则及执行建议" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section class="analysis-panel">
|
|
|
|
|
|
<div class="section-heading risk-heading">
|
|
|
|
|
|
<div><span>(二)供应风险评估</span><h3>识别风险并制定应对措施</h3></div>
|
|
|
|
|
|
<div class="row-actions">
|
|
|
|
|
|
<el-button :icon="Plus" @click="addRiskRow">增加一行</el-button>
|
|
|
|
|
|
<el-button :icon="Delete" @click="removeRiskRow">删除一行</el-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="panel-tip">默认的 4 条风险示例不可删除;可新增至 10 条,删除操作仅作用于最后一条新增风险。斜体内容为可直接修改或复制的填写示例。</p>
|
|
|
|
|
|
<div class="table-scroll">
|
|
|
|
|
|
<table class="training-table risk-table">
|
|
|
|
|
|
<thead><tr><th>风险维度</th><th>风险说明</th><th>风险等级(高/中/低)</th><th>应对措施</th></tr></thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="(risk, index) in riskRows" :key="risk.id" :class="{ 'default-risk-row': risk.locked }">
|
|
|
|
|
|
<td><el-input v-model.trim="risk.dimension" maxlength="80" placeholder="填写风险维度" /></td>
|
|
|
|
|
|
<td><el-input v-model.trim="risk.description" type="textarea" :rows="3" maxlength="1000" placeholder="填写风险说明" /></td>
|
|
|
|
|
|
<td><el-select v-model="risk.level" placeholder="请选择"><el-option label="高" value="高" /><el-option label="中" value="中" /><el-option label="低" value="低" /></el-select></td>
|
|
|
|
|
|
<td><el-input v-model.trim="risk.measure" type="textarea" :rows="3" maxlength="2000" placeholder="填写应对措施" /></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<footer class="page-actions split-actions">
|
|
|
|
|
|
<el-button size="large" @click="currentStep = 2">上一步</el-button>
|
|
|
|
|
|
<el-button type="primary" size="large" @click="saveStepThree">保存第 3 步</el-button>
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
|
|
<TrainingAiSidebar
|
|
|
|
|
|
:task-key="TASK_KEY"
|
|
|
|
|
|
:has-saved-answer="hasSavedAnswer"
|
|
|
|
|
|
:progress-items="progressItems"
|
|
|
|
|
|
:study-tip="currentStudyTip"
|
|
|
|
|
|
reference-title="供应渠道评估提示"
|
|
|
|
|
|
reference-text="· 试产阶段重点关注交付周期与采购灵活性<br />· 量产阶段重点关注采购成本与质量可靠性<br />· 风险建议应包含明确的应对措施"
|
|
|
|
|
|
assist-text="建议先区分试产与量产的评估侧重点,再基于评分结果形成采购建议和供应风险应对方案。"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
|
|
|
|
|
import { computed, onMounted, ref } from "vue";
|
|
|
|
|
|
import { CircleCheck, DataAnalysis, Delete, Plus } from "@element-plus/icons-vue";
|
|
|
|
|
|
import { ElMessage } from "element-plus";
|
|
|
|
|
|
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
|
|
|
|
|
|
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue";
|
|
|
|
|
|
import { getTrainingTaskByKey } from "@/api/trainingTask";
|
|
|
|
|
|
import { parseTrainingArray } from "@/views/training/taskKeyMap";
|
|
|
|
|
|
import {
|
|
|
|
|
|
getStudentTrainingAnswer,
|
|
|
|
|
|
saveStudentTrainingAnswer,
|
|
|
|
|
|
validateSupplierEvaluationStepOne,
|
|
|
|
|
|
validateSupplierEvaluationStepTwo,
|
|
|
|
|
|
validateSupplierEvaluationStepThree,
|
|
|
|
|
|
} from "@/api/studentTrainingAnswer";
|
|
|
|
|
|
|
|
|
|
|
|
const TASK_KEY = "supplier-evaluation";
|
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
|
const briefOpen = ref(false);
|
|
|
|
|
|
const currentStep = ref(1);
|
|
|
|
|
|
const stepOneSaved = ref(false);
|
|
|
|
|
|
const stepTwoSaved = ref(false);
|
|
|
|
|
|
const taskConfig = ref({});
|
|
|
|
|
|
const taskTitle = computed(() => taskConfig.value?.taskName || "供应渠道评估");
|
|
|
|
|
|
const taskBackground = computed(() => taskConfig.value?.background || "供应渠道会直接影响产品的采购成本、交付稳定性与质量风险,需要分别针对试产和量产场景建立评估标准。 ");
|
|
|
|
|
|
const taskGoals = computed(() => parseTrainingArray(taskConfig.value?.objectives ?? taskConfig.value?.goals, ["根据试产与量产场景设置供应渠道评估权重", "完成渠道评分、采购建议与供应风险分析"]));
|
|
|
|
|
|
const taskRequirement = computed(() => taskConfig.value?.requirements || taskConfig.value?.requirement || "完成各步骤校验并保存,渠道评分、采购建议和供应风险应与案例资料及评估结果保持一致。");
|
|
|
|
|
|
const hasSavedAnswer = computed(() => stepOneSaved.value || stepTwoSaved.value || Boolean(procurementRecommendation.value.trim()));
|
|
|
|
|
|
const currentStudyTip = computed(() => {
|
|
|
|
|
|
const tips = {
|
|
|
|
|
|
1: "先区分试产与量产的采购重点,确保每个场景的权重合计为 100%。",
|
|
|
|
|
|
2: "逐项填写渠道评分,重点检查渠道名称和加权评分是否与已保存权重一致。",
|
|
|
|
|
|
3: "根据排序结果形成采购建议,并为每类供应风险制定可执行的应对措施。",
|
|
|
|
|
|
};
|
|
|
|
|
|
return tips[currentStep.value] || tips[1];
|
|
|
|
|
|
});
|
|
|
|
|
|
const progressItems = computed(() => [
|
|
|
|
|
|
{ text: "设置评估权重", status: stepOneSaved.value ? "done" : currentStep.value === 1 ? "doing" : "" },
|
|
|
|
|
|
{ text: "渠道评分与对比", status: stepTwoSaved.value ? "done" : currentStep.value === 2 ? "doing" : "" },
|
|
|
|
|
|
{ text: "采购建议与风险", status: currentStep.value === 3 ? "doing" : "" },
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
const rows = ref([
|
|
|
|
|
|
{ key: "procurementCost", name: "采购成本", description: "元器件/模组的单台采购总成本,含运费、税费", smallBatchWeight: null, largeBatchWeight: null },
|
|
|
|
|
|
{ key: "deliveryCycle", name: "交货周期", description: "从下单到收货的时间,影响试产/生产进度", smallBatchWeight: null, largeBatchWeight: null },
|
|
|
|
|
|
{ key: "qualityReliability", name: "品质可靠性", description: "元器件是否为正品,有无翻新/故障,规格匹配度", smallBatchWeight: null, largeBatchWeight: null },
|
|
|
|
|
|
{ key: "procurementFlexibility", name: "采购灵活性", description: "是否支持小批量拆卖、零散采购、退换货", smallBatchWeight: null, largeBatchWeight: null },
|
|
|
|
|
|
{ key: "serviceSupport", name: "服务支持", description: "是否提供技术文档、售后支持,退换货是否方便", smallBatchWeight: null, largeBatchWeight: null },
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_RISKS = [
|
|
|
|
|
|
{ dimension: "独家供应风险", description: "仅依赖单一渠道,供应中断时无替代方案", level: "中", measure: "建立2-3家备选渠道,试产阶段分散下单验证品质,量产阶段保留备选供应商每季度询价" },
|
|
|
|
|
|
{ dimension: "交期延误风险", description: "供应商交期不稳定,影响生产排期", level: "中", measure: "建立2-3家备选供应商,试产阶段零散采购,量产阶段多家供应商共同供货" },
|
|
|
|
|
|
{ dimension: "品质波动风险", description: "元器件批次间品质不一致,影响产品一致性", level: "高", measure: "建立2-3家备选供应商,不间断引入新供应商,每批次元器件到货后进行严格质检再入库" },
|
|
|
|
|
|
{ dimension: "价格波动风险", description: "原材料价格波动或供应商调价导致采购成本变化", level: "中", measure: "建立2-3家备选供应商,尝试引入新供应商,每季度询价,研判原料价格走势提前备货" },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeScores(source = {}) {
|
|
|
|
|
|
const savedScores = Array.isArray(source)
|
|
|
|
|
|
? new Map(source.map((item) => [item?.dimension, item?.score]))
|
|
|
|
|
|
: null;
|
|
|
|
|
|
return rows.value.reduce((result, item) => {
|
|
|
|
|
|
result[item.key] = Number(savedScores ? savedScores.get(item.name) : source[item.key]) || 0;
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}, {});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function createChannelRows(smallSaved = [], largeSaved = []) {
|
|
|
|
|
|
const small = Array.isArray(smallSaved) ? smallSaved : [];
|
|
|
|
|
|
const large = Array.isArray(largeSaved) ? largeSaved : [];
|
|
|
|
|
|
return Array.from({ length: 5 }, (_, index) => {
|
|
|
|
|
|
const s = small[index] || {};
|
|
|
|
|
|
const l = large[index] || {};
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: `${Date.now()}-${index}-${Math.random().toString(16).slice(2)}`,
|
|
|
|
|
|
channel: String(s.channel || l.channel || ""),
|
|
|
|
|
|
scores: normalizeScores(s.scores ?? l.scores),
|
|
|
|
|
|
smallWeightedTotal: s.weightedTotal ?? null,
|
|
|
|
|
|
largeWeightedTotal: l.weightedTotal ?? null,
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function createScenarioAnalyses(savedRows = []) {
|
|
|
|
|
|
const source = Array.isArray(savedRows) ? savedRows : [];
|
|
|
|
|
|
const byScenario = new Map(source.map((item) => [item?.scenario, item]));
|
|
|
|
|
|
return [
|
|
|
|
|
|
{ scenario: "smallBatch", label: "小批量试产(100台)", differenceAnalysis: String(byScenario.get("smallBatch")?.differenceAnalysis || "") },
|
|
|
|
|
|
{ scenario: "largeBatch", label: "大规模量产(10000台)", differenceAnalysis: String(byScenario.get("largeBatch")?.differenceAnalysis || "") },
|
|
|
|
|
|
];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function createRiskRows(savedRows = []) {
|
|
|
|
|
|
const source = Array.isArray(savedRows) && savedRows.length ? savedRows : DEFAULT_RISKS;
|
|
|
|
|
|
return source.map((item, index) => ({
|
|
|
|
|
|
id: `${Date.now()}-${index}-${Math.random().toString(16).slice(2)}`,
|
|
|
|
|
|
dimension: String(item?.dimension || ""),
|
|
|
|
|
|
description: String(item?.description || ""),
|
|
|
|
|
|
level: ["高", "中", "低"].includes(item?.level) ? item.level : "中",
|
|
|
|
|
|
measure: String(item?.measure || ""),
|
|
|
|
|
|
locked: index < DEFAULT_RISKS.length,
|
|
|
|
|
|
}));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const channelRows = ref(createChannelRows());
|
|
|
|
|
|
const scenarioAnalyses = ref(createScenarioAnalyses());
|
|
|
|
|
|
const procurementRecommendation = ref("");
|
|
|
|
|
|
const riskRows = ref(createRiskRows());
|
|
|
|
|
|
|
|
|
|
|
|
const smallWeightTotal = computed(() => rows.value.reduce((sum, row) => sum + Number(row.smallBatchWeight || 0), 0));
|
|
|
|
|
|
const largeWeightTotal = computed(() => rows.value.reduce((sum, row) => sum + Number(row.largeBatchWeight || 0), 0));
|
|
|
|
|
|
const smallWeights = computed(() => rows.value.reduce((result, row) => ({ ...result, [row.key]: Number(row.smallBatchWeight || 0) }), {}));
|
|
|
|
|
|
const largeWeights = computed(() => rows.value.reduce((result, row) => ({ ...result, [row.key]: Number(row.largeBatchWeight || 0) }), {}));
|
|
|
|
|
|
|
|
|
|
|
|
function scoreFor(row, key) {
|
|
|
|
|
|
if (!Object.prototype.hasOwnProperty.call(row.scores, key)) row.scores[key] = 0;
|
|
|
|
|
|
return {
|
|
|
|
|
|
get score() { return row.scores[key]; },
|
|
|
|
|
|
set score(value) { row.scores[key] = value; row.smallWeightedTotal = null; row.largeWeightedTotal = null; },
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toNumber(value) {
|
|
|
|
|
|
const numeric = Number(value);
|
|
|
|
|
|
return Number.isFinite(numeric) ? numeric : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function weightedScore(row, scenario) {
|
|
|
|
|
|
const weights = scenario === "smallBatch" ? smallWeights.value : largeWeights.value;
|
|
|
|
|
|
const score = rows.value.reduce((sum, item) => sum + toNumber(row.scores[item.key]) * toNumber(weights[item.key]) / 100, 0);
|
|
|
|
|
|
return score.toFixed(2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function channelRanking(scenario) {
|
|
|
|
|
|
const storedKey = scenario === "smallBatch" ? "smallWeightedTotal" : "largeWeightedTotal";
|
|
|
|
|
|
return channelRows.value
|
|
|
|
|
|
.map((row) => {
|
|
|
|
|
|
const stored = Number(row[storedKey]);
|
|
|
|
|
|
return { channel: String(row.channel || "").trim(), score: Number.isFinite(stored) ? stored : Number(weightedScore(row, scenario)) };
|
|
|
|
|
|
})
|
|
|
|
|
|
.filter((item) => item.channel)
|
|
|
|
|
|
.sort((left, right) => right.score - left.score || left.channel.localeCompare(right.channel, "zh-CN"));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function rankAt(scenario, index) {
|
|
|
|
|
|
return channelRanking(scenario)[index] || {};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatScore(value) {
|
|
|
|
|
|
const score = Number(value);
|
|
|
|
|
|
return Number.isFinite(score) ? score.toFixed(2) : "--";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildStepOnePayload() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
items: rows.value.map((row) => ({
|
|
|
|
|
|
dimension: row.name,
|
|
|
|
|
|
smallBatchWeight: Number(row.smallBatchWeight || 0),
|
|
|
|
|
|
largeBatchWeight: Number(row.largeBatchWeight || 0),
|
|
|
|
|
|
})),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildStepTwoPayload() {
|
|
|
|
|
|
const serializeChannels = (storedKey) => channelRows.value.map((row) => ({
|
|
|
|
|
|
channel: String(row.channel || "").trim(),
|
|
|
|
|
|
scores: rows.value.map((dimension) => ({
|
|
|
|
|
|
dimension: dimension.name,
|
|
|
|
|
|
score: toNumber(row.scores[dimension.key]),
|
|
|
|
|
|
})),
|
|
|
|
|
|
weightedTotal: row[storedKey],
|
|
|
|
|
|
}));
|
|
|
|
|
|
return {
|
|
|
|
|
|
smallBatchChannels: serializeChannels("smallWeightedTotal"),
|
|
|
|
|
|
largeBatchChannels: serializeChannels("largeWeightedTotal"),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildStepThreePayload() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
scenarioAnalyses: scenarioAnalyses.value.map((item) => ({
|
|
|
|
|
|
scenario: item.scenario,
|
|
|
|
|
|
bestChannel: rankAt(item.scenario, 0).channel || "",
|
|
|
|
|
|
secondChannel: rankAt(item.scenario, 1).channel || "",
|
|
|
|
|
|
differenceAnalysis: item.differenceAnalysis,
|
|
|
|
|
|
})),
|
|
|
|
|
|
procurementRecommendation: procurementRecommendation.value,
|
|
|
|
|
|
risks: riskRows.value.map((item) => ({ dimension: item.dimension, description: item.description, level: item.level, measure: item.measure })),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function parseJson(value) {
|
|
|
|
|
|
if (!value) return null;
|
|
|
|
|
|
if (typeof value === "object") return value;
|
|
|
|
|
|
try { return JSON.parse(value); } catch { return null; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function applyStepOne(answer) {
|
|
|
|
|
|
const result = parseJson(answer?.step1Answer);
|
|
|
|
|
|
if (!Array.isArray(result?.items)) return false;
|
|
|
|
|
|
const source = new Map(result.items.map((item) => [item.dimension, item]));
|
|
|
|
|
|
rows.value.forEach((row) => {
|
|
|
|
|
|
const saved = source.get(row.name);
|
|
|
|
|
|
if (!saved) return;
|
|
|
|
|
|
row.smallBatchWeight = toNumber(saved.smallBatchWeight);
|
|
|
|
|
|
row.largeBatchWeight = toNumber(saved.largeBatchWeight);
|
|
|
|
|
|
});
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function applyStepTwo(answer) {
|
|
|
|
|
|
const result = parseJson(answer?.step2Answer);
|
|
|
|
|
|
if (!Array.isArray(result?.smallBatchChannels) || !Array.isArray(result?.largeBatchChannels)) return false;
|
|
|
|
|
|
channelRows.value = createChannelRows(result.smallBatchChannels, result.largeBatchChannels);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function applyStepThree(answer) {
|
|
|
|
|
|
const result = parseJson(answer?.step3Answer);
|
|
|
|
|
|
if (!Array.isArray(result?.scenarioAnalyses) || !Array.isArray(result?.risks)) return false;
|
|
|
|
|
|
scenarioAnalyses.value = createScenarioAnalyses(result.scenarioAnalyses);
|
|
|
|
|
|
procurementRecommendation.value = String(result.procurementRecommendation || "");
|
|
|
|
|
|
riskRows.value = createRiskRows(result.risks);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function saveStepOne() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = (await validateSupplierEvaluationStepOne(buildStepOnePayload()))?.data || {};
|
|
|
|
|
|
if (!result?.valid) throw new Error(result?.message || "权重校验未通过");
|
|
|
|
|
|
await saveStudentTrainingAnswer(TASK_KEY, { currentStep: 1, saveAction: "SAVE", progressStatus: "IN_PROGRESS", step1Answer: JSON.stringify({ items: result.items }) });
|
|
|
|
|
|
stepOneSaved.value = true;
|
|
|
|
|
|
currentStep.value = 2;
|
|
|
|
|
|
ElMessage.success("权重已保存,请继续完成渠道评分");
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
ElMessage.error(error?.message || "保存失败,请检查权重填写");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function saveStepTwo() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = (await validateSupplierEvaluationStepTwo(buildStepTwoPayload()))?.data || {};
|
|
|
|
|
|
if (!result?.valid) throw new Error(result?.message || "渠道评分校验未通过");
|
|
|
|
|
|
channelRows.value = createChannelRows(result.smallBatchChannels, result.largeBatchChannels);
|
|
|
|
|
|
await saveStudentTrainingAnswer(TASK_KEY, { currentStep: 2, saveAction: "SAVE", progressStatus: "IN_PROGRESS", step2Answer: JSON.stringify({ weights: result.weights, smallBatchChannels: result.smallBatchChannels, largeBatchChannels: result.largeBatchChannels }) });
|
|
|
|
|
|
stepTwoSaved.value = true;
|
|
|
|
|
|
ElMessage.success("渠道评分已保存,可进入第 3 步形成采购建议");
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
ElMessage.error(error?.message || "保存失败,请检查渠道和评分");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function saveStepThree() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = (await validateSupplierEvaluationStepThree(buildStepThreePayload()))?.data || {};
|
|
|
|
|
|
if (!result?.valid) throw new Error(result?.message || "第 3 步校验未通过");
|
|
|
|
|
|
const step3Answer = { scenarioAnalyses: result.scenarioAnalyses, procurementRecommendation: result.procurementRecommendation, risks: result.risks };
|
|
|
|
|
|
await saveStudentTrainingAnswer(TASK_KEY, { currentStep: 3, saveAction: "SAVE", progressStatus: "IN_PROGRESS", step3Answer: JSON.stringify(step3Answer) });
|
|
|
|
|
|
applyStepThree({ step3Answer });
|
|
|
|
|
|
ElMessage.success("采购建议与供应风险已保存");
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
ElMessage.error(error?.message || "保存失败,请检查填写内容");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openStepTwo() {
|
|
|
|
|
|
if (!stepOneSaved.value) {
|
|
|
|
|
|
ElMessage.warning("请先完成并保存第一步权重设置");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
currentStep.value = 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function openStepThree() {
|
|
|
|
|
|
if (!stepOneSaved.value || !stepTwoSaved.value) {
|
|
|
|
|
|
ElMessage.warning("请先完成并保存前两步内容");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
currentStep.value = 3;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function addRiskRow() {
|
|
|
|
|
|
if (riskRows.value.length >= 10) {
|
|
|
|
|
|
ElMessage.warning("供应风险最多填写 10 条");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
riskRows.value.push({ id: `${Date.now()}-${Math.random().toString(16).slice(2)}`, dimension: "", description: "", level: "中", measure: "", locked: false });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removeRiskRow() {
|
|
|
|
|
|
const index = [...riskRows.value].map((row, index) => ({ row, index })).reverse().find((item) => !item.row.locked)?.index;
|
|
|
|
|
|
if (index === undefined) {
|
|
|
|
|
|
ElMessage.warning("默认的 4 条供应风险不可删除");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
riskRows.value.splice(index, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
loading.value = true;
|
|
|
|
|
|
try {
|
|
|
|
|
|
taskConfig.value = (await getTrainingTaskByKey(TASK_KEY))?.data || {};
|
|
|
|
|
|
} catch (_) {
|
|
|
|
|
|
taskConfig.value = {};
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const answer = (await getStudentTrainingAnswer(TASK_KEY))?.data || {};
|
|
|
|
|
|
stepOneSaved.value = applyStepOne(answer);
|
|
|
|
|
|
stepTwoSaved.value = applyStepTwo(answer);
|
|
|
|
|
|
applyStepThree(answer);
|
|
|
|
|
|
if (Number(answer?.currentStep) === 3 && stepOneSaved.value && stepTwoSaved.value) currentStep.value = 3;
|
|
|
|
|
|
else if (Number(answer?.currentStep) === 2 && stepOneSaved.value) currentStep.value = 2;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
ElMessage.error(error?.message || "页面数据加载失败");
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
loading.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
/* ===== 页面级令牌(教育蓝极简主题) ===== */
|
|
|
|
|
|
.supplier-page {
|
|
|
|
|
|
--student-accent: #2563eb;
|
|
|
|
|
|
--student-accent-deep: #1d4ed8;
|
|
|
|
|
|
--student-accent-light: #3b82f6;
|
|
|
|
|
|
--student-accent-soft: #eff6ff;
|
|
|
|
|
|
--student-danger: #ef4444;
|
|
|
|
|
|
--student-success: #2ba471;
|
|
|
|
|
|
--student-layer-1: #1d4ed8;
|
|
|
|
|
|
--student-layer-2: #3b82f6;
|
|
|
|
|
|
--student-layer-3: #93c5fd;
|
|
|
|
|
|
--student-panel-border: #e5e7eb;
|
|
|
|
|
|
--student-card-bg: #ffffff;
|
|
|
|
|
|
--student-panel-bg: #fafbfc;
|
|
|
|
|
|
--student-text: #1f2937;
|
|
|
|
|
|
--student-text-secondary: #6b7280;
|
|
|
|
|
|
--el-color-primary: #2563eb;
|
|
|
|
|
|
--el-color-primary-light-3: #60a5fa;
|
|
|
|
|
|
--el-color-primary-light-5: #93c5fd;
|
|
|
|
|
|
--el-color-primary-light-7: #bfdbfe;
|
|
|
|
|
|
--el-color-primary-light-8: #dbeafe;
|
|
|
|
|
|
--el-color-primary-light-9: #eff6ff;
|
|
|
|
|
|
--el-color-primary-dark-2: #1d4ed8;
|
|
|
|
|
|
--el-input-hover-border-color: #3b82f6;
|
|
|
|
|
|
--el-input-focus-border-color: #2563eb;
|
|
|
|
|
|
--el-input-focus-ring-color: rgba(59, 130, 246, 0.15);
|
|
|
|
|
|
--student-fs-display: 17px;
|
|
|
|
|
|
--student-fs-h1: 15px;
|
|
|
|
|
|
--student-fs-h2: 14px;
|
|
|
|
|
|
--student-fs-h3: 13px;
|
|
|
|
|
|
--student-fs-body: 13px;
|
|
|
|
|
|
--student-fs-body-sm: 12px;
|
|
|
|
|
|
--student-fs-caption: 11px;
|
|
|
|
|
|
--student-fs-button: 13px;
|
|
|
|
|
|
--student-fs-button-lg: 13px;
|
|
|
|
|
|
--student-fs-data: 14px;
|
|
|
|
|
|
|
|
|
|
|
|
min-height: 100%;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
padding: 24px;
|
|
|
|
|
|
color: var(--student-text);
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* supplier-core:只设 gap,无边框/背景/阴影 */
|
|
|
|
|
|
.supplier-core {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
align-content: start;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== task-header(独立白卡片) ===== */
|
|
|
|
|
|
.task-header {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 18px 20px 18px 24px;
|
|
|
|
|
|
border: 1px solid var(--student-panel-border);
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
background: var(--student-card-bg);
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
|
|
&::before {
|
|
|
|
|
|
content: "";
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
width: 4px;
|
|
|
|
|
|
background: var(--student-accent);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-head-main {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
justify-items: start;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
flex: 1 1 auto;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.task-title {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: var(--student-text);
|
|
|
|
|
|
font-size: var(--student-fs-display);
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
line-height: 1.35;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-brief {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: var(--student-text-secondary);
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-brief-text {
|
|
|
|
|
|
display: -webkit-box;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
|
-webkit-line-clamp: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-brief-toggle {
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
color: var(--student-accent-deep);
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
font: inherit;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover { text-decoration: underline; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-brief-more {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
padding-top: 8px;
|
|
|
|
|
|
border-top: 1px solid var(--student-panel-border);
|
|
|
|
|
|
|
|
|
|
|
|
b { color: var(--student-text); font-size: 12px; }
|
|
|
|
|
|
ul { display: grid; gap: 4px; margin: 0; padding-left: 18px; }
|
|
|
|
|
|
p { margin: 0; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.supplier-toolbar {
|
|
|
|
|
|
flex: 0 0 auto;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== supplier-workbench(透明容器) ===== */
|
|
|
|
|
|
.supplier-workbench {
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== intro-card:改为 section-eyebrow + step-desc 风格 ===== */
|
|
|
|
|
|
.intro-card {
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border: 0;
|
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.intro-card.compact { margin-bottom: 0; }
|
|
|
|
|
|
|
|
|
|
|
|
.step-caption {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: var(--student-text-secondary);
|
|
|
|
|
|
font-size: var(--student-fs-caption);
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
letter-spacing: 0.12em;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.intro-card h2 {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: var(--student-text);
|
|
|
|
|
|
font-size: var(--student-fs-h1);
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
line-height: 1.35;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.intro-card > p:last-child {
|
|
|
|
|
|
margin: 4px 0 0;
|
|
|
|
|
|
color: var(--student-text-secondary);
|
|
|
|
|
|
font-size: var(--student-fs-body);
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== weights-grid:去掉双列 grid(改为单列 + 内部 weight-card 透明化) ===== */
|
|
|
|
|
|
.weights-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
margin-top: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.weight-card, .score-section {
|
|
|
|
|
|
padding: 14px;
|
|
|
|
|
|
border: 1px solid var(--student-panel-border);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: var(--student-card-bg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-title, .section-heading {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-title span, .section-heading span {
|
|
|
|
|
|
color: var(--student-text-secondary);
|
|
|
|
|
|
font-size: var(--student-fs-body-sm);
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
letter-spacing: 0.07em;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-title h3, .section-heading h3 {
|
|
|
|
|
|
margin: 2px 0 0;
|
|
|
|
|
|
color: var(--student-text);
|
|
|
|
|
|
font-size: var(--student-fs-h2);
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-title strong {
|
|
|
|
|
|
color: var(--student-accent);
|
|
|
|
|
|
font-size: var(--student-fs-data);
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
|
|
|
|
|
|
&.danger { color: var(--student-danger); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== 表格 ===== */
|
|
|
|
|
|
.training-table {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
|
table-layout: fixed;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
border: 1px solid var(--student-panel-border);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.training-table th {
|
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
|
color: var(--student-text);
|
|
|
|
|
|
background: #fafbfc;
|
|
|
|
|
|
font-size: var(--student-fs-body-sm);
|
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.training-table td {
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
border: 1px solid var(--student-panel-border);
|
|
|
|
|
|
color: var(--student-text);
|
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
background: transparent;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.training-table tbody tr:nth-child(even) { background: transparent; }
|
|
|
|
|
|
|
|
|
|
|
|
.training-table :deep(.el-input__wrapper),
|
|
|
|
|
|
.training-table :deep(.el-textarea__inner),
|
|
|
|
|
|
.training-table :deep(.el-select__wrapper) {
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
box-shadow: 0 0 0 1px var(--student-panel-border) inset;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.training-table :deep(.el-input__inner),
|
|
|
|
|
|
.training-table :deep(.el-textarea__inner),
|
|
|
|
|
|
.training-table :deep(.el-select__selected-item),
|
|
|
|
|
|
.training-table :deep(.el-input-number__decrease),
|
|
|
|
|
|
.training-table :deep(.el-input-number__increase) {
|
|
|
|
|
|
color: var(--student-text);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.training-table :deep(.el-textarea__inner) {
|
|
|
|
|
|
min-height: 70px !important;
|
|
|
|
|
|
resize: vertical;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== 操作按钮区 ===== */
|
|
|
|
|
|
.page-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding-top: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.split-actions { justify-content: space-between; }
|
|
|
|
|
|
|
|
|
|
|
|
.page-actions :deep(.el-button),
|
|
|
|
|
|
.row-actions :deep(.el-button) {
|
|
|
|
|
|
height: 36px;
|
|
|
|
|
|
min-height: 36px;
|
|
|
|
|
|
padding: 0 14px;
|
|
|
|
|
|
border: 1px solid rgba(31, 41, 55, 0.4);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
color: var(--student-text);
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
font-size: var(--student-fs-button);
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-actions :deep(.el-button:hover),
|
|
|
|
|
|
.row-actions :deep(.el-button:hover) {
|
|
|
|
|
|
border-color: var(--student-accent);
|
|
|
|
|
|
background: var(--student-accent-soft);
|
|
|
|
|
|
color: var(--student-accent);
|
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
|
box-shadow: 0 3px 10px rgba(31, 41, 55, 0.12);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-actions :deep(.el-button--primary),
|
|
|
|
|
|
.row-actions :deep(.el-button--primary) {
|
|
|
|
|
|
border-color: var(--student-accent);
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
background: var(--student-accent);
|
|
|
|
|
|
box-shadow: 0 3px 10px rgba(31, 41, 55, 0.28);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.page-actions :deep(.el-button--primary:hover),
|
|
|
|
|
|
.row-actions :deep(.el-button--primary:hover) {
|
|
|
|
|
|
border-color: var(--student-accent-deep);
|
|
|
|
|
|
background: var(--student-accent-deep);
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
box-shadow: 0 5px 14px rgba(17, 24, 39, 0.32);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== score-section / analysis-panel ===== */
|
|
|
|
|
|
.score-section { margin-bottom: 10px; }
|
|
|
|
|
|
.section-heading small { color: var(--student-text-secondary); font-size: var(--student-fs-body-sm); }
|
|
|
|
|
|
.table-scroll { overflow-x: auto; }
|
|
|
|
|
|
|
|
|
|
|
|
.score-table { min-width: 960px; }
|
|
|
|
|
|
.score-table th:first-child, .score-table td:first-child { width: 175px; }
|
|
|
|
|
|
.score-table :deep(.el-input-number) { width: 100%; }
|
|
|
|
|
|
|
|
|
|
|
|
/* 合并评分表:双列组布局 */
|
|
|
|
|
|
.merged-score-table { min-width: 1080px; table-layout: fixed; }
|
|
|
|
|
|
.merged-score-table .col-channel { width: 150px; }
|
|
|
|
|
|
.merged-score-table .col-dimension { width: auto; }
|
|
|
|
|
|
.merged-score-table .col-total { width: 110px; }
|
|
|
|
|
|
|
|
|
|
|
|
/* 顶层分组表头:维度组中性底,总分组着色 */
|
|
|
|
|
|
.merged-score-table .group-header th {
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: var(--student-fs-body-sm);
|
|
|
|
|
|
}
|
|
|
|
|
|
.merged-score-table .group-dimensions {
|
|
|
|
|
|
background: #fafbfc;
|
|
|
|
|
|
color: var(--student-text);
|
|
|
|
|
|
}
|
|
|
|
|
|
.merged-score-table .group-total { color: #ffffff; font-weight: 800; }
|
|
|
|
|
|
.merged-score-table .group-total-small { background: var(--student-accent); }
|
|
|
|
|
|
.merged-score-table .group-total-large { background: var(--student-accent-deep); }
|
|
|
|
|
|
|
|
|
|
|
|
/* 第二行子表头 */
|
|
|
|
|
|
.merged-score-table .col-dimension,
|
|
|
|
|
|
.merged-score-table .col-total { background: #fafbfc; }
|
|
|
|
|
|
.merged-score-table .col-total-small { color: var(--student-accent); }
|
|
|
|
|
|
.merged-score-table .col-total-large { color: var(--student-accent-deep); }
|
|
|
|
|
|
|
|
|
|
|
|
/* 列组竖向分隔线 */
|
|
|
|
|
|
.merged-score-table .col-channel,
|
|
|
|
|
|
.merged-score-table .col-total-small { border-right: 2px solid var(--student-panel-border); }
|
|
|
|
|
|
|
|
|
|
|
|
.total-score {
|
|
|
|
|
|
color: var(--student-accent) !important;
|
|
|
|
|
|
font-size: var(--student-fs-data);
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.analysis-panel {
|
|
|
|
|
|
padding: 14px;
|
|
|
|
|
|
border: 1px solid var(--student-panel-border);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: var(--student-card-bg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.analysis-panel + .analysis-panel { margin-top: 10px; }
|
|
|
|
|
|
|
|
|
|
|
|
.ranking-table { min-width: 940px; }
|
|
|
|
|
|
.ranking-table th:first-child { width: 18%; }
|
|
|
|
|
|
.ranking-table th:nth-child(2), .ranking-table th:nth-child(3) { width: 19%; }
|
|
|
|
|
|
|
|
|
|
|
|
.scene-name {
|
|
|
|
|
|
color: var(--student-accent) !important;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.rank-result { display: flex; flex-direction: column; gap: 4px; }
|
|
|
|
|
|
.rank-result strong { color: var(--student-text); font-weight: 700; }
|
|
|
|
|
|
.rank-result span { color: var(--student-text-secondary); font-size: var(--student-fs-body-sm); }
|
|
|
|
|
|
|
|
|
|
|
|
.recommendation-card {
|
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
|
padding: 12px;
|
|
|
|
|
|
border: 1px solid var(--student-panel-border);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.recommendation-card label {
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
color: var(--student-text);
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
font-size: var(--student-fs-body-sm);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.recommendation-card :deep(.el-textarea__inner) {
|
|
|
|
|
|
min-height: 120px !important;
|
|
|
|
|
|
color: var(--student-text);
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
box-shadow: 0 0 0 1px var(--student-panel-border) inset;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.risk-heading { align-items: flex-end; }
|
|
|
|
|
|
.row-actions { display: flex; gap: 10px; }
|
|
|
|
|
|
|
|
|
|
|
|
.panel-tip {
|
|
|
|
|
|
margin: -2px 0 12px;
|
|
|
|
|
|
color: var(--student-text-secondary);
|
|
|
|
|
|
font-size: var(--student-fs-body-sm);
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.risk-table { min-width: 1080px; }
|
|
|
|
|
|
.risk-table th:nth-child(1) { width: 15%; }
|
|
|
|
|
|
.risk-table th:nth-child(2) { width: 29%; }
|
|
|
|
|
|
.risk-table th:nth-child(3) { width: 16%; }
|
|
|
|
|
|
.risk-table :deep(.el-select) { width: 100%; }
|
|
|
|
|
|
|
|
|
|
|
|
.default-risk-row :deep(.el-input__inner),
|
|
|
|
|
|
.default-risk-row :deep(.el-textarea__inner),
|
|
|
|
|
|
.default-risk-row :deep(.el-select__selected-item) {
|
|
|
|
|
|
color: var(--student-text-secondary);
|
|
|
|
|
|
font-style: italic;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ===== 响应式(仅基础断点) ===== */
|
|
|
|
|
|
@media (max-width: 840px) {
|
|
|
|
|
|
.weights-grid { grid-template-columns: 1fr; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 720px) {
|
|
|
|
|
|
.supplier-page { padding: 12px; }
|
|
|
|
|
|
.task-header { flex-direction: column; align-items: flex-start; }
|
|
|
|
|
|
.supplier-toolbar { width: 100%; justify-content: flex-start; }
|
|
|
|
|
|
.risk-heading { align-items: flex-start; flex-direction: column; gap: 8px; }
|
|
|
|
|
|
.split-actions { gap: 12px; flex-wrap: wrap; }
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 教育蓝极简换肤 · channel/supplier.vue
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
|
|
|
|
/* 画布背景 */
|
|
|
|
|
|
#app .studentLayout .app-main:has(.supplier-page) {
|
|
|
|
|
|
background: #ffffff !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* el-table 基础换肤 */
|
|
|
|
|
|
:has(.supplier-page) {
|
|
|
|
|
|
.el-table {
|
|
|
|
|
|
--el-table-bg-color: #ffffff !important;
|
|
|
|
|
|
--el-table-tr-bg-color: transparent !important;
|
|
|
|
|
|
--el-table-header-bg-color: #fafbfc !important;
|
|
|
|
|
|
--el-table-row-hover-bg-color: rgba(37, 99, 235, 0.04) !important;
|
|
|
|
|
|
line-height: 1.55;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 步骤导航:分段连接胶囊(教育蓝极简)
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
#app .studentLayout .app-main:has(.supplier-page) .step-indicator {
|
|
|
|
|
|
display: flex !important;
|
|
|
|
|
|
flex-wrap: nowrap !important;
|
|
|
|
|
|
align-items: stretch !important;
|
|
|
|
|
|
justify-content: center !important;
|
|
|
|
|
|
gap: 0 !important;
|
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
|
margin: 0 0 20px !important;
|
|
|
|
|
|
padding: 0 !important;
|
|
|
|
|
|
border: none !important;
|
|
|
|
|
|
border-radius: 0 !important;
|
|
|
|
|
|
background: transparent !important;
|
|
|
|
|
|
box-shadow: none !important;
|
|
|
|
|
|
|
|
|
|
|
|
.step-tab {
|
|
|
|
|
|
flex: 1 1 0 !important;
|
|
|
|
|
|
max-width: 240px !important;
|
|
|
|
|
|
display: flex !important;
|
|
|
|
|
|
flex-direction: row !important;
|
|
|
|
|
|
align-items: center !important;
|
|
|
|
|
|
justify-content: center !important;
|
|
|
|
|
|
gap: 8px !important;
|
|
|
|
|
|
min-width: 0 !important;
|
|
|
|
|
|
height: auto !important;
|
|
|
|
|
|
min-height: 40px !important;
|
|
|
|
|
|
margin: 0 !important;
|
|
|
|
|
|
padding: 8px 14px !important;
|
|
|
|
|
|
border: 1px solid #e5e7eb !important;
|
|
|
|
|
|
border-right: none !important;
|
|
|
|
|
|
border-radius: 0 !important;
|
|
|
|
|
|
background: #ffffff !important;
|
|
|
|
|
|
box-shadow: none !important;
|
|
|
|
|
|
color: #6b7280 !important;
|
|
|
|
|
|
font-size: 13px !important;
|
|
|
|
|
|
font-weight: 600 !important;
|
|
|
|
|
|
line-height: 1.3 !important;
|
|
|
|
|
|
white-space: normal !important;
|
|
|
|
|
|
text-align: center !important;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.16s ease !important;
|
|
|
|
|
|
|
|
|
|
|
|
&:first-child {
|
|
|
|
|
|
border-radius: 8px 0 0 8px !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
|
border-radius: 0 8px 8px 0 !important;
|
|
|
|
|
|
border-right: 1px solid #e5e7eb !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-bar {
|
|
|
|
|
|
display: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-no,
|
|
|
|
|
|
.step-marker {
|
|
|
|
|
|
position: static !important;
|
|
|
|
|
|
flex: 0 0 auto !important;
|
|
|
|
|
|
display: inline-flex !important;
|
|
|
|
|
|
align-items: center !important;
|
|
|
|
|
|
justify-content: center !important;
|
|
|
|
|
|
min-width: 22px !important;
|
|
|
|
|
|
height: 22px !important;
|
|
|
|
|
|
padding: 0 5px !important;
|
|
|
|
|
|
border-radius: 6px !important;
|
|
|
|
|
|
background: #f3f4f6 !important;
|
|
|
|
|
|
color: #6b7280 !important;
|
|
|
|
|
|
font-family: var(--student-font-data), ui-monospace, "SF Mono", Consolas, monospace !important;
|
|
|
|
|
|
font-size: 12px !important;
|
|
|
|
|
|
font-weight: 700 !important;
|
|
|
|
|
|
line-height: 1 !important;
|
|
|
|
|
|
transition: all 0.16s ease !important;
|
|
|
|
|
|
&::after {
|
|
|
|
|
|
display: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-name {
|
|
|
|
|
|
color: inherit !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-copy {
|
|
|
|
|
|
margin-left: 0 !important;
|
|
|
|
|
|
gap: 4px !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:hover:not(.active):not(.complete) {
|
|
|
|
|
|
border-color: #2563eb !important;
|
|
|
|
|
|
background: #eff6ff !important;
|
|
|
|
|
|
color: #2563eb !important;
|
|
|
|
|
|
z-index: 1 !important;
|
|
|
|
|
|
.step-no,
|
|
|
|
|
|
.step-marker {
|
|
|
|
|
|
background: #2563eb !important;
|
|
|
|
|
|
color: #ffffff !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.complete:not(.active) {
|
|
|
|
|
|
border-color: #e5e7eb !important;
|
|
|
|
|
|
color: #374151 !important;
|
|
|
|
|
|
.step-no,
|
|
|
|
|
|
.step-marker {
|
|
|
|
|
|
background: rgba(43, 164, 113, 0.12) !important;
|
|
|
|
|
|
color: #16915f !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #2ba471 !important;
|
|
|
|
|
|
background: #f0fdf6 !important;
|
|
|
|
|
|
z-index: 1 !important;
|
|
|
|
|
|
.step-no,
|
|
|
|
|
|
.step-marker {
|
|
|
|
|
|
background: #2ba471 !important;
|
|
|
|
|
|
color: #ffffff !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
|
|
border-color: #2563eb !important;
|
|
|
|
|
|
background: #2563eb !important;
|
|
|
|
|
|
color: #ffffff !important;
|
|
|
|
|
|
font-weight: 700 !important;
|
|
|
|
|
|
z-index: 2 !important;
|
|
|
|
|
|
.step-no,
|
|
|
|
|
|
.step-marker {
|
|
|
|
|
|
background: #ffffff !important;
|
|
|
|
|
|
color: #2563eb !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background: #1d4ed8 !important;
|
|
|
|
|
|
border-color: #1d4ed8 !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-connector {
|
|
|
|
|
|
display: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 共用组件换肤:TrainingMaterialButton 资料按钮
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
.supplier-page .workbench-actions .material-button.el-button,
|
|
|
|
|
|
.supplier-page .supplier-toolbar .material-button.el-button {
|
|
|
|
|
|
height: 34px !important;
|
|
|
|
|
|
min-height: 34px !important;
|
|
|
|
|
|
border: 1px solid rgba(37, 99, 235, 0.35) !important;
|
|
|
|
|
|
border-radius: 8px !important;
|
|
|
|
|
|
color: #2563eb !important;
|
|
|
|
|
|
background: #ffffff !important;
|
|
|
|
|
|
font-size: 13px !important;
|
|
|
|
|
|
font-weight: 600 !important;
|
|
|
|
|
|
transition:
|
|
|
|
|
|
border-color 0.16s ease,
|
|
|
|
|
|
background 0.16s ease,
|
|
|
|
|
|
color 0.16s ease,
|
|
|
|
|
|
box-shadow 0.16s ease !important;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #2563eb !important;
|
|
|
|
|
|
color: #2563eb !important;
|
|
|
|
|
|
background: #eff6ff !important;
|
|
|
|
|
|
box-shadow: 0 2px 6px rgba(37, 99, 235, 0.12) !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.is-plain {
|
|
|
|
|
|
border-color: #e5e7eb !important;
|
|
|
|
|
|
color: #9ca3af !important;
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #e5e7eb !important;
|
|
|
|
|
|
color: #9ca3af !important;
|
|
|
|
|
|
background: #ffffff !important;
|
|
|
|
|
|
box-shadow: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 顶部导出按钮(outline-action)
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
.supplier-page .workbench-actions > button.outline-action {
|
|
|
|
|
|
display: inline-flex !important;
|
|
|
|
|
|
align-items: center !important;
|
|
|
|
|
|
justify-content: center !important;
|
|
|
|
|
|
gap: 6px !important;
|
|
|
|
|
|
height: 34px !important;
|
|
|
|
|
|
min-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: 13px !important;
|
|
|
|
|
|
font-weight: 600 !important;
|
|
|
|
|
|
line-height: 1 !important;
|
|
|
|
|
|
transition:
|
|
|
|
|
|
border-color 0.16s ease,
|
|
|
|
|
|
background 0.16s ease,
|
|
|
|
|
|
color 0.16s ease,
|
|
|
|
|
|
box-shadow 0.16s ease !important;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #2563eb !important;
|
|
|
|
|
|
color: #2563eb !important;
|
|
|
|
|
|
background: #eff6ff !important;
|
|
|
|
|
|
box-shadow: 0 2px 6px rgba(37, 99, 235, 0.12) !important;
|
|
|
|
|
|
transform: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 底部步骤按钮(el-button)
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
.supplier-page .page-actions .el-button {
|
|
|
|
|
|
display: inline-flex !important;
|
|
|
|
|
|
align-items: center !important;
|
|
|
|
|
|
justify-content: center !important;
|
|
|
|
|
|
gap: 8px !important;
|
|
|
|
|
|
height: 34px !important;
|
|
|
|
|
|
min-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: 13px !important;
|
|
|
|
|
|
font-weight: 700 !important;
|
|
|
|
|
|
line-height: 1 !important;
|
|
|
|
|
|
transition:
|
|
|
|
|
|
transform 0.16s ease,
|
|
|
|
|
|
border-color 0.16s ease,
|
|
|
|
|
|
background 0.16s ease,
|
|
|
|
|
|
box-shadow 0.16s ease !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.supplier-page .page-actions .el-button:hover:not(.is-disabled) {
|
|
|
|
|
|
transform: translateY(-1px) !important;
|
|
|
|
|
|
border-color: #2563eb !important;
|
|
|
|
|
|
color: #2563eb !important;
|
|
|
|
|
|
background: #eff6ff !important;
|
|
|
|
|
|
box-shadow: 0 3px 10px rgba(37, 99, 235, 0.12) !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 主按钮(el-button--primary) */
|
|
|
|
|
|
.supplier-page .page-actions .el-button--primary {
|
|
|
|
|
|
border-color: #2563eb !important;
|
|
|
|
|
|
color: #ffffff !important;
|
|
|
|
|
|
background: #2563eb !important;
|
|
|
|
|
|
box-shadow: 0 3px 10px rgba(37, 99, 235, 0.28) !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.supplier-page .page-actions .el-button--primary:hover:not(.is-disabled) {
|
|
|
|
|
|
border-color: #1d4ed8 !important;
|
|
|
|
|
|
color: #ffffff !important;
|
|
|
|
|
|
background: #1d4ed8 !important;
|
|
|
|
|
|
box-shadow: 0 5px 14px rgba(29, 78, 216, 0.32) !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ============================================================
|
|
|
|
|
|
* 窄屏:横向滑动
|
|
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
@media (max-width: 900px) {
|
|
|
|
|
|
#app .studentLayout .app-main:has(.supplier-page) .step-indicator {
|
|
|
|
|
|
justify-content: flex-start !important;
|
|
|
|
|
|
overflow-x: auto !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
#app .studentLayout .app-main:has(.supplier-page) .step-indicator .step-tab {
|
|
|
|
|
|
flex: 0 0 140px !important;
|
|
|
|
|
|
max-width: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|