revert(material): 物料跟踪页恢复到 9cf422e 布局,仅保留段位区分

回退所有 Tab / 扁平化 / 瓦片网格重构(45abd35..ea39d92)。
保留两项:
- SVG 总图顶部段位色带(5 段不同色)
- 物料跟踪表新增「段」列

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-26 14:26:57 +08:00
parent ea39d92364
commit 8c4a0512c4

View File

@@ -32,34 +32,69 @@
</div>
</div>
<!-- 在线计划队列 + 入口移动 -->
<div class="card" style="margin-bottom:8px;">
<div class="card-header">
在线计划入口队列
<span class="ch-badge">在线 {{ onlinePlans.length }} / 生产中 {{ producingPlan ? 1 : 0 }}</span>
<span style="margin-left:auto;font-size:11px;color:var(--text-muted);">点击移动把队列卷推到入口并开始生产</span>
</div>
<div style="padding:8px 14px;">
<div v-if="producingPlan" class="producing-row">
<span class="badge badge-yellow">生产中</span>
<span class="kv-label">冷卷号</span><span class="kv-value">{{ producingPlan.cold_coil_no || producingPlan.plan_no }}</span>
<span class="kv-label">钢种</span><span class="kv-value">{{ producingPlan.steel_grade || '—' }}</span>
<span class="kv-label">规格</span><span class="kv-value">{{ fmt(producingPlan.product_thickness) }}×{{ fmt(producingPlan.product_width, 0) }}</span>
<span class="kv-label">分卷</span><span class="kv-value">{{ producingPlan.split_count || 1 }}</span>
</div>
<table class="data-table compact" v-if="onlinePlans.length">
<thead><tr><th>冷卷号</th><th>钢种</th><th>厚度</th><th>宽度</th><th>分卷</th><th>下达时间</th><th>操作</th></tr></thead>
<tbody>
<tr v-for="p in onlinePlans" :key="p.id">
<td class="td-num">{{ p.cold_coil_no || p.plan_no }}</td>
<td>{{ p.steel_grade || '—' }}</td>
<td class="td-num">{{ fmt(p.product_thickness) }}</td>
<td class="td-num">{{ fmt(p.product_width, 0) }}</td>
<td class="td-num">{{ p.split_count || 1 }}</td>
<td class="td-muted">{{ fmtTime(p.plan_date) }}</td>
<td>
<button class="btn btn-primary btn-sm" :disabled="moving" @click="movePlan(p)">移动 </button>
</td>
</tr>
</tbody>
</table>
<div v-else-if="!producingPlan" class="td-muted" style="text-align:center;padding:10px;font-size:12px;">暂无在线计划</div>
</div>
</div>
<!-- 产线总图 -->
<div class="line-wrap card">
<div class="card-header">推拉酸洗线 - 物料跟踪总图</div>
<div class="line-body">
<svg viewBox="0 0 1900 310" preserveAspectRatio="xMidYMid meet" class="line-svg">
<rect x="0" y="0" width="1900" height="310" fill="#0a1218" />
<svg viewBox="0 0 1900 305" preserveAspectRatio="xMidYMid meet" class="line-svg">
<rect x="0" y="0" width="1900" height="305" fill="#0a1218" />
<!-- 段位标签带 -->
<g v-for="s in sections" :key="'sec-'+s.name">
<rect :x="s.bandX" y="4" :width="s.bandW" height="22" :fill="s.color" opacity="0.18" rx="3"/>
<rect :x="s.bandX" y="4" :width="s.bandW" height="22" fill="none" :stroke="s.color" stroke-width="1" opacity="0.7" rx="3"/>
<text :x="s.labelX" y="19" text-anchor="middle" font-size="12" font-weight="bold" :fill="s.color"
<rect :x="s.bandX" y="2" :width="s.bandW" height="20" :fill="s.color" opacity="0.18" rx="3"/>
<rect :x="s.bandX" y="2" :width="s.bandW" height="20" fill="none" :stroke="s.color" stroke-width="1" opacity="0.7" rx="3"/>
<text :x="s.labelX" y="16" text-anchor="middle" font-size="11" font-weight="bold" :fill="s.color"
font-family="Arial,sans-serif">{{ s.name }}</text>
</g>
<!-- 顶部设备标签 -->
<!-- 顶部标签 -->
<g v-for="eq in equipments" :key="'lab-'+eq.k" font-family="Arial,sans-serif">
<text :x="eq.x" y="44" text-anchor="middle" font-size="10.5" fill="#c8d4e0">{{ eq.label }}</text>
<text :x="eq.x" y="40" text-anchor="middle" font-size="10.5" fill="#c8d4e0">{{ eq.label }}</text>
</g>
<!-- 主带钢线 -->
<path d="M 40 190 L 1860 190" stroke="#5a6a75" stroke-width="3" fill="none"/>
<path d="M 40 190 L 1860 190" stroke="#aabbcc" stroke-width="1.2" fill="none" stroke-dasharray="6 10">
<path d="M 40 185 L 1860 185" stroke="#5a6a75" stroke-width="3" fill="none"/>
<path d="M 40 185 L 1860 185" stroke="#aabbcc" stroke-width="1.2" fill="none" stroke-dasharray="6 10">
<animate attributeName="stroke-dashoffset" from="16" to="0" dur="0.7s" repeatCount="indefinite"/>
</path>
<!-- 各设备图形 -->
<g v-for="eq in equipments" :key="eq.k" :transform="`translate(${eq.x}, 190)`">
<g v-for="eq in equipments" :key="eq.k" :transform="`translate(${eq.x}, 185)`">
<!-- 开卷机 -->
<template v-if="eq.type==='coiler'">
<circle r="38" fill="#1a232c" stroke="#3a4a55" stroke-width="2"/>
@@ -213,7 +248,7 @@
</g>
<!-- 焊缝标记 -->
<g :transform="`translate(${weldX}, 190)`">
<g :transform="`translate(${weldX}, 185)`">
<circle r="11" fill="#ffdd00" opacity="0.35">
<animate attributeName="r" values="9;22;9" dur="1.0s" repeatCount="indefinite"/>
<animate attributeName="opacity" values="0.7;0.05;0.7" dur="1.0s" repeatCount="indefinite"/>
@@ -225,7 +260,7 @@
</g>
<!-- 图例 -->
<g transform="translate(20,295)" font-size="10" fill="#8b949e">
<g transform="translate(20,290)" font-size="10" fill="#8b949e">
<circle cx="6" cy="-3" r="5" fill="#ffee44"/>
<text x="18" y="0">焊缝位置 {{ (weld.position * 100).toFixed(1) }}%</text>
<rect x="160" y="-7" width="12" height="8" fill="#ffaa44" opacity="0.5"/>
@@ -240,80 +275,60 @@
</div>
</div>
<!-- 在线计划 / 移动按钮 -->
<div class="card plan-card">
<div class="plan-head">
<span class="sec-pill" :style="{ background: sectionColor('入口段') }">入口段·在线计划</span>
<span v-if="producingPlan" class="badge badge-yellow" style="margin-left:6px;">生产中 {{ producingPlan.cold_coil_no || producingPlan.plan_no }}</span>
<span v-else class="td-muted" style="margin-left:6px;font-size:11px;">无在线生产</span>
<span class="td-muted" style="margin-left:auto;font-size:11px;">{{ onlinePlans.length }} 卷待入</span>
</div>
<div class="plan-list">
<div v-for="p in onlinePlans" :key="p.id" class="plan-item">
<span class="badge badge-gray">在线</span>
<span class="pi-coil">{{ p.cold_coil_no || p.plan_no }}</span>
<span class="pi-grade">{{ p.steel_grade || '—' }}</span>
<span class="pi-spec">{{ fmt(p.product_thickness) }}×{{ fmt(p.product_width, 0) }}</span>
<span class="pi-split">{{ p.split_count || 1 }}</span>
<button class="btn btn-primary btn-sm" :disabled="moving" @click="movePlan(p)">移动 </button>
<!-- 下半: 跟踪表 | 实时数据 -->
<div class="split-row">
<div class="card split-left">
<div class="card-header">物料跟踪表 <span class="hd-cnt"> {{ equipments.length }} 台设备</span></div>
<div class="card-body" style="padding:0;">
<div class="track-scroll">
<table class="data-table compact tracking-table">
<thead>
<tr>
<th style="width:32px;">#</th>
<th style="width:72px;"></th>
<th>设备</th>
<th style="width:64px;">状态</th>
<th>当前钢卷</th>
<th style="width:80px;">辊缝 (mm)</th>
<th style="width:78px;">速度</th>
<th style="width:78px;">张力/温度</th>
</tr>
</thead>
<tbody>
<tr v-for="(eq, i) in equipments" :key="eq.k"
:class="{ 'row-active': eq.k === currentEquipment.k, 'row-passed': i < currentEquipment.idx, 'row-pending': i > currentEquipment.idx }">
<td class="td-num">{{ i + 1 }}</td>
<td><span class="sec-tag" :style="{ color: sectionColor(eq.section), borderColor: sectionColor(eq.section) }">{{ eq.section }}</span></td>
<td>{{ eq.label }}</td>
<td>
<span v-if="eq.k === currentEquipment.k" class="badge badge-yellow">加工中</span>
<span v-else-if="i < currentEquipment.idx" class="badge badge-blue">已过</span>
<span v-else class="badge badge-gray">待入</span>
</td>
<td class="td-num">{{ rowOf(eq, i).coil }}</td>
<td class="td-num">{{ rowOf(eq, i).gap }}</td>
<td class="td-num">{{ rowOf(eq, i).speed }}</td>
<td class="td-num">{{ rowOf(eq, i).aux }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="card split-right">
<div class="card-header">实时数据 <span class="hd-cnt">{{ rtItems.length }} </span></div>
<div class="card-body sec-body">
<div class="dg">
<div v-for="it in rtItems" :key="it.k" class="dg-item">
<span class="lbl">{{ it.label }}</span>
<span class="vbox">{{ it.val }}</span>
<span v-if="it.unit" class="unit">{{ it.unit }}</span>
</div>
</div>
</div>
<div v-if="!onlinePlans.length" class="td-muted" style="text-align:center;padding:6px;font-size:11px;">暂无队列</div>
</div>
</div>
<!-- 全段参数统一瓦片网格 -->
<div class="tile-grid">
<div v-for="t in allTiles" :key="t.k" class="tile" :style="{ borderLeftColor: t.color }">
<div class="tile-tag" :style="{ color: t.color }">{{ t.section }}</div>
<div class="tile-label">{{ t.label }}</div>
<div class="tile-value">{{ t.val }}<span v-if="t.unit" class="tile-unit">{{ t.unit }}</span></div>
</div>
</div>
<!-- 物料跟踪表 -->
<div class="card track-card">
<div class="card-header">
<span class="sec-pill" style="background:#5a6a78;">物料跟踪表</span>
<span class="td-muted" style="margin-left:auto;font-size:11px;">{{ equipments.length }} 台设备 · 焊缝 {{ (weld.position*100).toFixed(1) }}%</span>
</div>
<div class="tbl-scroll" style="max-height:280px;">
<table class="data-table compact tracking-table">
<thead>
<tr>
<th style="width:32px;">#</th>
<th style="width:78px;"></th>
<th>设备</th>
<th style="width:64px;">状态</th>
<th>当前钢卷</th>
<th style="width:72px;">辊缝</th>
<th style="width:70px;">速度</th>
<th style="width:78px;">辅助</th>
</tr>
</thead>
<tbody>
<tr v-for="(eq, i) in equipments" :key="eq.k"
:class="{ 'row-active': eq.k === currentEquipment.k, 'row-passed': i < currentEquipment.idx, 'row-pending': i > currentEquipment.idx }">
<td class="td-num">{{ i + 1 }}</td>
<td>
<span class="sec-tag" :style="{ color: sectionColor(eq.section), borderColor: sectionColor(eq.section) }">{{ eq.section }}</span>
</td>
<td>{{ eq.label }}</td>
<td>
<span v-if="eq.k === currentEquipment.k" class="badge badge-yellow">加工中</span>
<span v-else-if="i < currentEquipment.idx" class="badge badge-blue">已过</span>
<span v-else class="badge badge-gray">待入</span>
</td>
<td class="td-num">{{ rowOf(eq, i).coil }}</td>
<td class="td-num">{{ rowOf(eq, i).gap }}</td>
<td class="td-num">{{ rowOf(eq, i).speed }}</td>
<td class="td-num">{{ rowOf(eq, i).aux }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
@@ -413,92 +428,6 @@ export default {
}
},
computed: {
allTiles() {
const f = (v, n=1) => Number(v).toFixed(n)
const c = (s) => SECTION_COLORS[s] || '#9aa8b6'
const out = []
const push = (section, label, val, unit) => out.push({
k: section + ':' + label, section, color: c(section), label, val, unit,
})
// 入口段
push('入口段', '开卷张力', f(this.uncoiler.tension, 1), 'kN')
push('入口段', '工艺段速度', f(this.current.speed, 1), 'm/min')
push('入口段', '开卷机速度', f(this.uncoiler.speed, 1), 'm/min')
push('入口段', '开卷机卷径', f(this.uncoiler.diameter, 0),'mm')
push('入口段', '开卷机电流', f(this.uncoiler.current, 0), 'A')
push('入口段', '开卷机扭矩', f(this.uncoiler.torque, 2), 'kN·m')
push('入口段', '矫直机速度', f(this.straightener.speed, 1), 'm/min')
push('入口段', '矫直机辊缝', f(this.straightener.gap, 2), 'mm')
push('入口段', '矫直机电流', f(this.straightener.current, 0),'A')
push('入口段', '矫直机扭矩', f(this.straightener.torque, 2), 'kN·m')
push('入口段', '1#夹送辊速度', f(this.br1.speed, 1), 'm/min')
push('入口段', '1#夹送辊辊缝', f(this.br1.gap, 2), 'mm')
// 酸洗段5 槽
this.acid.forEach((a, i) => {
const t = (i+1) + '#酸槽 '
push('酸洗段', t + '温度', f(a.temp, 1), '°C')
push('酸洗段', t + '浓度', f(a.conc, 1), 'g/L')
push('酸洗段', t + '液位', f(a.level, 2), 'm')
push('酸洗段', t + '电导率', f(a.cond, 1), 'mS/cm')
})
push('酸洗段', '酸雾塔 PH', f(this.acid_mist.ph, 2), '')
push('酸洗段', '酸雾变频频率', f(this.acid_mist.vfd_speed, 1), 'Hz')
push('酸洗段', '酸雾变频电流', f(this.acid_mist.vfd_current, 1), 'A')
push('酸洗段', '冷凝罐液位', f(this.acid_cond.level, 2), 'm')
push('酸洗段', '冷凝罐温度', f(this.acid_cond.temp, 1), '°C')
push('酸洗段', '冷凝电导率', f(this.acid_cond.cond, 1), 'μS/cm')
// 清洗段5 级
this.rinse.forEach((r, i) => {
const t = (i+1) + '#漂洗 '
push('清洗段', t + '温度', f(this.rinse_tank_temp[i], 1), '°C')
push('清洗段', t + '浓度', f(r.conc, 2), 'g/L')
push('清洗段', t + '液位', f(r.level, 2), 'm')
push('清洗段', t + '电导率', f(r.cond, 2), 'μS/cm')
})
push('清洗段', '漂洗雾塔 PH', f(this.rinse_mist.ph, 2), '')
push('清洗段', '雾塔变频频率', f(this.rinse_mist.vfd_speed, 1), 'Hz')
push('清洗段', '雾塔变频电流', f(this.rinse_mist.vfd_current, 1), 'A')
push('清洗段', '冷凝液位', f(this.rinse_cond.level, 2), 'm')
push('清洗段', '冷凝温度', f(this.rinse_cond.temp, 1), '°C')
push('清洗段', '冷凝电导率', f(this.rinse_cond.cond, 2), 'μS/cm')
// 烘干段
push('烘干段', '烘干1段', f(this.dryer.t1, 0), '°C')
push('烘干段', '烘干2段', f(this.dryer.t2, 0), '°C')
push('烘干段', '烘干3段', f(this.dryer.t3, 0), '°C')
// 出口段
this.tension_vfd.forEach((v, i) => {
const t = '三辊VFD-' + (i+1) + ' '
push('出口段', t + '速度', f(v.speed, 1), 'm/min')
push('出口段', t + '电流', f(v.current, 0), 'A')
push('出口段', t + '扭矩', f(v.torque, 2), 'kN·m')
})
push('出口段', '平整辊缝', f(this.leveler.gap, 2), 'mm')
push('出口段', '平整轧制力', f(this.leveler.force, 0), 'kN')
push('出口段', '平整延伸率', f(this.leveler.elongation, 2), '%')
push('出口段', '收卷张力', f(this.recoiler.tension, 1), 'kN')
push('出口段', '收卷直径', f(this.recoiler.diameter, 0), 'mm')
push('出口段', '收卷速度', f(this.recoiler.speed, 1), 'm/min')
return out
},
entryItems() {
const f = (v, n=1) => Number(v).toFixed(n)
return [
{ k:'u_t', label:'开卷机张力', val: f(this.uncoiler.tension, 1), unit:'kN' },
{ k:'p_s', label:'工艺段速度', val: f(this.current.speed, 1), unit:'m/min' },
{ k:'u_s', label:'开卷机速度', val: f(this.uncoiler.speed, 1), unit:'m/min' },
{ k:'u_d', label:'开卷机卷径', val: f(this.uncoiler.diameter, 0),unit:'mm' },
{ k:'u_c', label:'开卷机电流', val: f(this.uncoiler.current, 0), unit:'A' },
{ k:'u_q', label:'开卷机扭矩', val: f(this.uncoiler.torque, 2), unit:'kN·m' },
{ k:'st_s',label:'九辊矫直机 速度', val: f(this.straightener.speed, 1), unit:'m/min' },
{ k:'st_g',label:'九辊矫直机 辊缝', val: f(this.straightener.gap, 2), unit:'mm' },
{ k:'st_c',label:'九辊矫直机 电流', val: f(this.straightener.current, 0),unit:'A' },
{ k:'st_q',label:'九辊矫直机 扭矩', val: f(this.straightener.torque, 2), unit:'kN·m' },
{ k:'b1s', label:'1号夹送辊 速度', val: f(this.br1.speed, 1), unit:'m/min' },
{ k:'b1g', label:'1号夹送辊 辊缝', val: f(this.br1.gap, 2), unit:'mm' },
{ k:'b1c', label:'1号夹送辊 电流', val: f(this.br1.current, 0), unit:'A' },
{ k:'b1q', label:'1号夹送辊 扭矩', val: f(this.br1.torque, 2), unit:'kN·m' },
]
},
onlinePlans() { return this.plans.filter(p => p.status === 'online') },
producingPlan() { return this.plans.find(p => p.status === 'producing') || null },
equipments() {
@@ -509,20 +438,18 @@ export default {
},
sections() {
const eqs = this.equipments
if (!eqs.length) return []
const groups = []
let cur = null
eqs.forEach((e, i) => {
if (!cur || cur.name !== e.section) {
if (cur) groups.push(cur)
cur = { name: e.section, color: SECTION_COLORS[e.section] || '#9aa8b6',
startIdx: i, endIdx: i, x0: e.x, x1: e.x }
} else {
cur.endIdx = i
cur.x1 = e.x
}
x0: e.x, x1: e.x }
} else { cur.x1 = e.x }
})
if (cur) groups.push(cur)
const half = (eqs[1].x - eqs[0].x) / 2
const half = eqs.length > 1 ? (eqs[1].x - eqs[0].x) / 2 : 30
return groups.map(g => ({
...g,
bandX: g.x0 - half + 4,
@@ -567,7 +494,7 @@ export default {
push('r_t', '收卷机 收卷张力', fix(this.recoiler.tension, 1), 'kN')
this.acid.forEach((a, i) => {
push(`at${i}`, `酸洗${i+1}# 槽温度`, fix(a.temp, 1), '°C')
push(`at${i}`, `酸洗${i+1}# 槽/罐温度(公用)`, fix(a.temp, 1), '°C')
push(`al${i}`, `酸洗${i+1}# 罐液位`, fix(a.level, 2), 'm')
push(`ac${i}`, `酸洗${i+1}# 槽浓度`, fix(a.conc, 1), 'g/L')
push(`ae${i}`, `酸洗${i+1}# 槽电导率`, fix(a.cond, 1), 'mS/cm')
@@ -583,7 +510,7 @@ export default {
this.rinse.forEach((r, i) => {
const t = this.rinse_tank_temp[i]
push(`rt${i}`, `漂洗${i+1}# 槽温度`, fix(t, 1), '°C')
push(`rt${i}`, `漂洗${i+1}# 槽/罐温度(公用)`, fix(t, 1), '°C')
push(`rl${i}`, `漂洗${i+1}# 罐液位`, fix(r.level, 2), 'm')
push(`rc${i}`, `漂洗${i+1}# 槽浓度`, fix(r.conc, 2), 'g/L')
push(`re${i}`, `漂洗${i+1}# 槽电导率`, fix(r.cond, 2), 'μS/cm')
@@ -610,7 +537,6 @@ export default {
},
methods: {
fmt(v, n = 2) { return v != null && v !== '' ? Number(v).toFixed(n) : '—' },
fix(v, n = 1) { return Number(v).toFixed(n) },
sectionColor(s) { return SECTION_COLORS[s] || '#9aa8b6' },
fmtTime(t) { return t ? t.slice(0, 16).replace('T', ' ') : '—' },
async loadPlans() {
@@ -754,12 +680,7 @@ export default {
<style lang="scss" scoped>
@import '@/assets/styles/variables';
.mat-page { display: flex; flex-direction: column; gap: 10px; min-width: 0; overflow-x: hidden; }
.sec-tag {
display: inline-block; font-size: 10.5px; padding: 1px 6px;
border: 1px solid; border-radius: 3px; background: rgba(0,0,0,.25);
font-weight: 600; letter-spacing: 0.5px;
}
.mat-page { display: flex; flex-direction: column; gap: 10px; }
.status-bar {
display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
@@ -773,78 +694,40 @@ export default {
.line-wrap { padding: 0; }
.line-body { padding: 6px 10px 10px; background: #0a1218; }
.line-svg { width: 100%; height: 310px; display: block; }
.line-svg { width: 100%; height: 305px; display: block; }
.sec-pill {
display: inline-block; padding: 2px 10px;
font-size: 11.5px; font-weight: 700; color: #0a1218;
border-radius: 3px; letter-spacing: 1px;
}
.split-row { display: grid; grid-template-columns: 1.05fr 1fr; gap: 10px; align-items: stretch; }
.split-left, .split-right { display: flex; flex-direction: column; min-height: 540px; }
.split-right .card-body { flex: 1; overflow-y: auto; }
/* ─── 在线计划卡 ─── */
.plan-card { padding: 0; }
.plan-head {
display: flex; align-items: center; gap: 6px;
padding: 7px 12px; font-size: 12px;
background: #161d24; border-bottom: 1px solid $border;
}
.plan-list { padding: 6px 10px; display: flex; flex-direction: column; gap: 4px; }
.plan-item {
display: flex; align-items: center; gap: 10px;
padding: 4px 8px;
background: #0f161c; border: 1px solid $border; border-radius: 3px;
font-size: 12px;
.pi-coil { color: $sms-highlight; font-weight: 600; font-family: monospace; min-width: 110px; }
.pi-grade { color: #c8d4e0; min-width: 60px; }
.pi-spec { color: #8b9aab; min-width: 90px; font-family: monospace; }
.pi-split { color: #8b9aab; min-width: 40px; }
button { margin-left: auto; }
}
.track-scroll { max-height: 640px; overflow-y: auto; }
/* ─── 统一瓦片网格 ─── */
.tile-grid {
display: grid; gap: 6px;
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
min-width: 0;
.producing-row { display: flex; align-items: center; gap: 10px; padding: 6px 4px 10px; font-size: 12px; border-bottom: 1px dashed $border; margin-bottom: 6px;
.kv-label { color: $text-muted; font-size: 11px; margin-left: 6px; }
.kv-value { color: $sms-highlight; font-weight: 600; }
}
.tile {
display: flex; flex-direction: column; gap: 1px;
padding: 5px 8px 6px;
background: $bg-card; border: 1px solid $border;
border-left: 3px solid transparent;
border-radius: 3px;
min-width: 0;
}
.tile-tag {
font-size: 9px; font-weight: 700; letter-spacing: 1px;
text-transform: uppercase;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tile-label {
font-size: 11px; color: #8b9aab;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tile-value {
font-family: monospace; font-size: 15px; font-weight: 700;
color: #00c8ff; text-align: right; margin-top: 1px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tile-unit { font-size: 10px; color: #6b7c8d; font-weight: 400; margin-left: 4px; }
.btn-sm { padding: 2px 10px; font-size: 11px; }
/* ─── 跟踪表卡 ─── */
.track-card { padding: 0; }
.track-card .card-header {
display: flex; align-items: center; padding: 7px 12px;
background: #161d24; border-bottom: 1px solid $border; font-size: 12px;
}
.tbl-scroll { width: 100%; overflow: auto; }
.hd-cnt { font-size: 11px; color: #6b7c8d; margin-left: 8px; font-weight: 400; }
.empty-row { text-align: center; padding: 10px; font-size: 12px; }
.btn-sm { padding: 2px 10px; font-size: 11px; white-space: nowrap; }
.sec-body { padding: 10px 14px; background: #161d24; }
.dg { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 4px 18px; }
.dg-item { display: flex; align-items: center; gap: 6px; font-size: 12px; color: #c8d4e0; padding: 2px 0; }
.dg-item .lbl { color: #8b9aab; flex: 1; min-width: 140px; }
.dg-item .vbox {
background: #0e1418; border: 1px solid #2a3540; padding: 1px 8px;
min-width: 70px; text-align: right; font-family: monospace;
color: #00c8ff; border-radius: 2px;
}
.dg-item .unit { color: #6b7c8d; font-size: 11px; min-width: 44px; }
.data-table.compact th, .data-table.compact td { padding: 5px 8px; font-size: 11.5px; }
.tracking-table tr.row-active { background: rgba(255, 221, 68, 0.10); }
.tracking-table tr.row-active td { color: #ffdd44 !important; font-weight: 600; }
.tracking-table tr.row-passed td { color: #6b8aaa; }
.tracking-table tr.row-pending td { color: #5a6a78; }
.sec-tag { display: inline-block; font-size: 10.5px; padding: 1px 6px;
border: 1px solid; border-radius: 3px; background: rgba(0,0,0,.25);
font-weight: 600; letter-spacing: 0.5px; }
</style>