You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/src/views/channel/supplier.vue

514 lines
28 KiB
Vue

2 months ago
<template>
<main class="supplier-page" v-loading="loading">
<section class="workspace-shell">
<header class="workspace-header">
<div>
<p class="eyebrow">SUPPLIER CHANNEL EVALUATION</p>
<h1>供应渠道评估</h1>
<p>分别从试产与量产场景评估供应渠道形成可执行的采购与风险管理建议</p>
</div>
<span class="task-badge">实训任务</span>
</header>
<nav class="step-nav" aria-label="">
<button :class="['step-tab', { active: currentStep === 1 }]" @click="currentStep = 1">
<span>01</span><b>设置评估权重</b>
</button>
<i></i>
<button :class="['step-tab', { active: currentStep === 2 }]" @click="openStepTwo">
<span>02</span><b>渠道评分与对比</b>
</button>
<i></i>
<button :class="['step-tab', { active: currentStep === 3 }]" @click="openStepThree">
<span>03</span><b>采购建议与风险</b>
</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>每项评分为 010 渠道名称和评分均由学生填写系统会按第一步已保存的权重自动计算加权总分</p>
</section>
<section v-for="section in scoreSections" :key="section.key" class="score-section">
<div class="section-heading">
<div><span>{{ section.label }}</span><h3>{{ section.title }}</h3></div>
<small>加权总分 = Σ维度评分 × 权重</small>
</div>
<div class="table-scroll">
<table class="training-table score-table">
<thead>
<tr><th>渠道</th><th v-for="dimension in rows" :key="dimension.key">{{ dimension.name }}</th><th>加权总分</th></tr>
</thead>
<tbody>
<tr v-for="row in section.rows" :key="row.id">
<td><el-input v-model.trim="row.channel" maxlength="40" placeholder="填写渠道名称" :disabled="section.key === 'large'" @input="section.key === 'small' && syncChannelNames()" /></td>
<td v-for="dimension in rows" :key="dimension.key">
<el-input-number v-model="scoreFor(row, dimension.key).score" :min="0" :max="10" :precision="0" controls-position="right" />
</td>
<td class="total-score">{{ weightedScore(row, section.weights) }}</td>
</tr>
</tbody>
</table>
2 months ago
</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>
2 months ago
</template>
<script setup>
import { computed, onMounted, ref } from "vue";
import { CircleCheck, Delete, Plus } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import {
getStudentTrainingAnswer,
saveStudentTrainingAnswer,
validateSupplierEvaluationStepOne,
validateSupplierEvaluationStepTwo,
validateSupplierEvaluationStepThree,
} from "@/api/studentTrainingAnswer";
const TASK_KEY = "supplier-evaluation";
const loading = ref(false);
const currentStep = ref(1);
const stepOneSaved = ref(false);
const stepTwoSaved = ref(false);
const rows = ref([
{ key: "procurementCost", name: "采购成本", description: "元器件/模组的单台采购总成本,含运费、税费", smallBatchWeight: 20, largeBatchWeight: 35 },
{ key: "deliveryCycle", name: "交货周期", description: "从下单到收货的时间,影响试产/生产进度", smallBatchWeight: 30, largeBatchWeight: 15 },
{ key: "qualityReliability", name: "品质可靠性", description: "元器件是否为正品,有无翻新/故障,规格匹配度", smallBatchWeight: 20, largeBatchWeight: 30 },
{ key: "procurementFlexibility", name: "采购灵活性", description: "是否支持小批量拆卖、零散采购、退换货", smallBatchWeight: 20, largeBatchWeight: 10 },
{ key: "serviceSupport", name: "服务支持", description: "是否提供技术文档、售后支持,退换货是否方便", smallBatchWeight: 10, largeBatchWeight: 10 },
2 months ago
]);
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家备选供应商尝试引入新供应商每季度询价研判原料价格走势提前备货" },
2 months ago
];
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 createScoreRows(savedRows = []) {
const source = Array.isArray(savedRows) ? savedRows : [];
return Array.from({ length: 5 }, (_, index) => {
const saved = source[index] || {};
return {
id: `${Date.now()}-${index}-${Math.random().toString(16).slice(2)}`,
channel: String(saved.channel || ""),
scores: normalizeScores(saved.scores),
weightedTotal: saved.weightedTotal ?? null,
};
2 months ago
});
}
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 smallBatchRows = ref(createScoreRows());
const largeBatchRows = ref(createScoreRows());
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) }), {}));
const scoreSections = computed(() => [
{ key: "small", label: "场景 A", title: "小批量试产100台渠道评分", rows: smallBatchRows.value, weights: smallWeights.value },
{ key: "large", label: "场景 B", title: "大规模量产10000台渠道评分", rows: largeBatchRows.value, weights: largeWeights.value },
]);
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.weightedTotal = null; },
};
}
function toNumber(value) {
const numeric = Number(value);
return Number.isFinite(numeric) ? numeric : 0;
}
function weightedScore(row, weights) {
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 source = scenario === "smallBatch" ? smallBatchRows.value : largeBatchRows.value;
const weights = scenario === "smallBatch" ? smallWeights.value : largeWeights.value;
return source
.map((row) => {
const stored = Number(row.weightedTotal);
return { channel: String(row.channel || "").trim(), score: Number.isFinite(stored) ? stored : Number(weightedScore(row, weights)) };
})
.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 syncChannelNames() {
largeBatchRows.value.forEach((row, index) => {
row.channel = String(smallBatchRows.value[index]?.channel || "").trim();
2 months ago
});
}
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 = (source) => source.map((row) => ({
channel: String(row.channel || "").trim(),
scores: rows.value.map((dimension) => ({
dimension: dimension.name,
score: toNumber(row.scores[dimension.key]),
})),
}));
return {
smallBatchChannels: serializeChannels(smallBatchRows.value),
largeBatchChannels: serializeChannels(largeBatchRows.value),
};
}
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;
smallBatchRows.value = createScoreRows(result.smallBatchChannels);
largeBatchRows.value = createScoreRows(result.largeBatchChannels);
syncChannelNames();
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());
if (!result?.valid) throw new Error(result?.message || "权重校验未通过");
await saveStudentTrainingAnswer({ taskKey: 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 {
syncChannelNames();
const result = await validateSupplierEvaluationStepTwo(buildStepTwoPayload());
if (!result?.valid) throw new Error(result?.message || "渠道评分校验未通过");
smallBatchRows.value = createScoreRows(result.smallBatchChannels);
largeBatchRows.value = createScoreRows(result.largeBatchChannels);
await saveStudentTrainingAnswer({ taskKey: TASK_KEY, currentStep: 2, saveAction: "SAVE", progressStatus: "IN_PROGRESS", step2Answer: JSON.stringify({ weights: result.weights, smallBatchChannels: result.smallBatchChannels, largeBatchChannels: result.largeBatchChannels }) });
stepTwoSaved.value = true;
syncChannelNames();
ElMessage.success("渠道评分已保存,可进入第 3 步形成采购建议");
} catch (error) {
ElMessage.error(error?.message || "保存失败,请检查渠道和评分");
}
}
async function saveStepThree() {
try {
const result = await validateSupplierEvaluationStepThree(buildStepThreePayload());
if (!result?.valid) throw new Error(result?.message || "第 3 步校验未通过");
const step3Answer = { scenarioAnalyses: result.scenarioAnalyses, procurementRecommendation: result.procurementRecommendation, risks: result.risks };
await saveStudentTrainingAnswer({ taskKey: 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;
2 months ago
}
riskRows.value.push({ id: `${Date.now()}-${Math.random().toString(16).slice(2)}`, dimension: "", description: "", level: "中", measure: "", locked: false });
2 months ago
}
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 {
const answer = await getStudentTrainingAnswer(TASK_KEY);
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 scoped>
.supplier-page { min-height: 100%; padding: 24px; color: #dce9f2; background: radial-gradient(circle at top right, #123e58 0, #071723 38%, #050e16 100%); }
.workspace-shell { max-width: 1520px; margin: 24px auto 0; padding: 28px; border: 1px solid rgba(69, 180, 227, .35); border-radius: 18px; background: rgba(7, 27, 41, .86); box-shadow: 0 20px 60px rgba(0, 0, 0, .24); }
.workspace-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 24px; margin-bottom: 30px; }
.eyebrow, .step-caption { margin: 0 0 7px; color: #63dcff; font-weight: 700; letter-spacing: .1em; }
.workspace-header h1, .intro-card h2 { margin: 0; color: #fff; }
.workspace-header p:not(.eyebrow), .intro-card > p:last-child { margin: 10px 0 0; color: #9db4c4; line-height: 1.75; }
.task-badge { padding: 8px 14px; border: 1px solid #3dc4ec; border-radius: 99px; color: #5fdbff; white-space: nowrap; }
.step-nav { display: grid; grid-template-columns: 1fr 44px 1fr 44px 1fr; align-items: center; margin-bottom: 28px; }
.step-nav i { height: 2px; background: linear-gradient(90deg, #2b9cc4, #174963); }
.step-tab { display: flex; align-items: center; gap: 11px; padding: 13px 16px; border: 1px solid #21556e; border-radius: 12px; color: #afc8d7; background: #092231; cursor: pointer; text-align: left; }
.step-tab span { display: grid; width: 30px; height: 30px; place-items: center; border: 1px solid #397a96; border-radius: 50%; font-weight: 700; }
.step-tab.active { color: white; border-color: #36c5f5; background: linear-gradient(90deg, #0b84b3, #126181); box-shadow: inset 0 0 24px rgba(77, 213, 255, .22); }
.step-tab.active span { border-color: #9af0ff; background: #0b98ce; }
.intro-card, .analysis-panel { padding: 24px; border: 1px solid rgba(71, 167, 209, .35); border-radius: 14px; background: rgba(3, 20, 31, .72); }
.intro-card.compact { margin-bottom: 20px; }
.weights-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; margin-top: 20px; }
.weight-card, .score-section { padding: 20px; border: 1px solid rgba(71, 167, 209, .28); border-radius: 14px; background: rgba(4, 23, 36, .68); }
.card-title, .section-heading { display: flex; align-items: center; justify-content: space-between; gap: 18px; margin-bottom: 16px; }
.card-title span, .section-heading span { color: #52cef6; font-size: 13px; font-weight: 700; letter-spacing: .07em; }
.card-title h3, .section-heading h3 { margin: 4px 0 0; color: #fff; font-size: 20px; }
.card-title strong { color: #6de4ff; font-size: 25px; }.card-title strong.danger { color: #ff8888; }
.training-table { width: 100%; border-collapse: collapse; table-layout: fixed; overflow: hidden; border: 1px solid #2b5c73; }
.training-table th { padding: 13px 10px; color: #effbff; background: #0c91c8; font-weight: 700; }
.training-table td { padding: 10px; border: 1px solid #2b5c73; color: #cfe2ec; vertical-align: middle; text-align: center; }
.training-table tbody tr:nth-child(even) { background: rgba(15, 48, 66, .42); }
.training-table :deep(.el-input__wrapper), .training-table :deep(.el-textarea__inner), .training-table :deep(.el-select__wrapper) { background: rgba(2, 15, 24, .7); box-shadow: 0 0 0 1px #315f75 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: #e7f6fd; }
.training-table :deep(.el-textarea__inner) { min-height: 70px !important; resize: vertical; }
.page-actions { display: flex; justify-content: center; padding-top: 28px; }.split-actions { justify-content: space-between; }
.page-actions :deep(.el-button--primary), .row-actions :deep(.el-button) { border-color: #18b6ec; background: #0e96ca; }
.score-section { margin-bottom: 20px; }.section-heading small { color: #93adbd; }.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%; }.total-score { color: #6de4ff !important; font-size: 17px; font-weight: 700; }
.analysis-panel + .analysis-panel { margin-top: 20px; }.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: #71dcfb !important; font-weight: 700; }.rank-result { display: flex; flex-direction: column; gap: 5px; }.rank-result strong { color: #f3fbff; }.rank-result span { color: #62cef1; font-size: 13px; }
.recommendation-card { margin-top: 20px; }.recommendation-card label { display: block; margin-bottom: 9px; color: #e7f5fb; font-weight: 700; }.recommendation-card :deep(.el-textarea__inner) { min-height: 130px !important; color: #e7f6fd; background: rgba(2, 15, 24, .7); box-shadow: 0 0 0 1px #315f75 inset; }
.risk-heading { align-items: flex-end; }.row-actions { display: flex; gap: 10px; }.panel-tip { margin: -4px 0 16px; color: #9db8c6; line-height: 1.7; }.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: #b7cbd6; font-style: italic; }
@media (max-width: 980px) { .weights-grid { grid-template-columns: 1fr; }.step-nav { grid-template-columns: 1fr; gap: 8px; }.step-nav i { display: none; }.workspace-header { flex-direction: column; }.risk-heading { align-items: flex-start; flex-direction: column; }.split-actions { gap: 12px; }.supplier-page { padding: 12px; }.workspace-shell { padding: 16px; } }
2 months ago
</style>