|
|
@@ -2,7 +2,12 @@
|
|
|
import { computed, type Component } from 'vue';
|
|
|
import CardBlockLayout from './CardBlockLayout.vue';
|
|
|
|
|
|
-const DEFAULT_BLOCK_ORDER = ['metric-label', 'values', 'health-rate', 'status-label', 'progress', 'wide-meter'];
|
|
|
+// S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:
|
|
|
+// 默认布局移除 `health-rate` / `status-label`:
|
|
|
+// - 「闭环率」原本被同时渲染在 health-rate 块和 wide-meter 块两次,去重后仅保留 wide-meter。
|
|
|
+// - status-label 原显示 "N 异常" / "N 超时" 字符串,改由 values 块内"关注/严重/超时"摘要行替代。
|
|
|
+// 旧值仍保留作为已有 localStorage 配置的兼容渲染目标,但默认顺序不再包含。
|
|
|
+const DEFAULT_BLOCK_ORDER = ['metric-label', 'values', 'progress', 'wide-meter'];
|
|
|
|
|
|
const props = defineProps<{
|
|
|
code: string;
|
|
|
@@ -17,6 +22,10 @@ const props = defineProps<{
|
|
|
// S8-MODULE-CARD-ZERO-TOTAL-EMPTY-STATE-1:空态文本(如「--」),优先于 `${healthRate}%` 渲染。
|
|
|
healthRateText?: string;
|
|
|
statusLabel: string;
|
|
|
+ // S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:
|
|
|
+ // 旧版 statusLabel 是「N 异常」/「N 超时」字符串,新布局需要分别拆出"关注/严重/超时"三个数字。
|
|
|
+ // 不解析 statusLabel 字符串,而是新增 timeoutCount 数字 prop(关注/严重直接复用 values.yellow/red)。
|
|
|
+ timeoutCount?: number;
|
|
|
progress: {
|
|
|
green: number;
|
|
|
yellow: number;
|
|
|
@@ -57,6 +66,18 @@ const shouldShowMetricLabel = computed(() => props.showMetricLabel ?? true);
|
|
|
const shouldShowStatusLabel = computed(() => props.showStatusLabel ?? true);
|
|
|
const shouldShowProgress = computed(() => props.showProgress ?? true);
|
|
|
const isWide = computed(() => (props.cardSpan ?? 1) > 1);
|
|
|
+
|
|
|
+// S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:从 values 解析自然数(绿=正常,黄=关注,红=严重)。
|
|
|
+function safeNum(v: unknown): number {
|
|
|
+ const n = Number(v);
|
|
|
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 0;
|
|
|
+}
|
|
|
+
|
|
|
+const normalCount = computed(() => safeNum(props.values?.green));
|
|
|
+const followCount = computed(() => safeNum(props.values?.yellow));
|
|
|
+const seriousCount = computed(() => safeNum(props.values?.red));
|
|
|
+const pendingCount = computed(() => followCount.value + seriousCount.value);
|
|
|
+const timeoutDisplayCount = computed(() => safeNum(props.timeoutCount));
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
@@ -75,32 +96,46 @@ const isWide = computed(() => (props.cardSpan ?? 1) > 1);
|
|
|
@update:blocks="emit('update:blockOrder', $event)"
|
|
|
>
|
|
|
<template #metric-label>
|
|
|
- <div v-if="shouldShowMetricLabel" class="stage-card__metric" :style="metricLabelFontSize ? { fontSize: `${metricLabelFontSize}px` } : {}">{{ metricLabel }}</div>
|
|
|
+ <div v-if="shouldShowMetricLabel" class="stage-card__pending">
|
|
|
+ <div class="stage-card__pending-head">
|
|
|
+ <span class="stage-card__pending-label">当前待处理异常</span>
|
|
|
+ <span class="stage-card__pending-num">{{ pendingCount }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<template #values>
|
|
|
- <div v-if="values" class="stage-card__values" :class="{ 'stage-card__values--wide': isWide }">
|
|
|
- <span class="stage-card__value-item stage-card__value-item--green">
|
|
|
- <span v-if="valueLabels?.green" class="stage-card__value-label">{{ valueLabels.green }},</span>{{ values.green }}
|
|
|
+ <div v-if="values" class="stage-card__summary-row">
|
|
|
+ <span class="stage-card__summary-item stage-card__summary-item--yellow">
|
|
|
+ <span class="stage-card__summary-key">关注</span>
|
|
|
+ <span class="stage-card__summary-val">{{ followCount }}</span>
|
|
|
</span>
|
|
|
- <span v-if="!isWide" class="stage-card__value-sep"> </span>
|
|
|
- <span class="stage-card__value-item stage-card__value-item--yellow">
|
|
|
- <span v-if="valueLabels?.yellow" class="stage-card__value-label">{{ valueLabels.yellow }},</span>{{ values.yellow }}
|
|
|
+ <span class="stage-card__summary-item stage-card__summary-item--red">
|
|
|
+ <span class="stage-card__summary-key">严重</span>
|
|
|
+ <span class="stage-card__summary-val">{{ seriousCount }}</span>
|
|
|
</span>
|
|
|
- <span v-if="!isWide" class="stage-card__value-sep"> </span>
|
|
|
- <span class="stage-card__value-item stage-card__value-item--red">
|
|
|
- <span v-if="valueLabels?.red" class="stage-card__value-label">{{ valueLabels.red }},</span>{{ values.red }}
|
|
|
+ <span class="stage-card__summary-item stage-card__summary-item--orange">
|
|
|
+ <span class="stage-card__summary-key">超时</span>
|
|
|
+ <span class="stage-card__summary-val">{{ timeoutDisplayCount }}</span>
|
|
|
</span>
|
|
|
</div>
|
|
|
<div v-else class="stage-card__value" :class="{ 'stage-card__value--wide': isWide }">{{ value }}</div>
|
|
|
+ <div v-if="values" class="stage-card__normal-row">
|
|
|
+ <span class="stage-card__normal-key">正常执行订单</span>
|
|
|
+ <span class="stage-card__normal-val">{{ normalCount }}</span>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
+ <!-- S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:health-rate 旧块("闭环率 X%")已与 wide-meter 合并去重;
|
|
|
+ 仅当用户的 localStorage 中显式包含 `health-rate` 时才会渲染(向后兼容)。 -->
|
|
|
<template #health-rate>
|
|
|
<div class="stage-card__stats stage-card__stats--health">
|
|
|
<span>闭环率 {{ healthRateText ?? `${healthRate}%` }}</span>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
+ <!-- S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:status-label 旧块("N 异常 / N 超时")已被 summary-row 替代;
|
|
|
+ 仅当 stored blockOrder 显式包含 `status-label` 时才会渲染。 -->
|
|
|
<template #status-label>
|
|
|
<div v-if="shouldShowStatusLabel" class="stage-card__stats stage-card__stats--status">
|
|
|
<span>{{ statusLabel }}</span>
|
|
|
@@ -116,7 +151,8 @@ const isWide = computed(() => (props.cardSpan ?? 1) > 1);
|
|
|
</template>
|
|
|
|
|
|
<template #wide-meter>
|
|
|
- <div v-if="isWide" class="stage-card__wide-meter">
|
|
|
+ <div class="stage-card__wide-meter">
|
|
|
+ <div class="stage-card__efficiency-title">处理效率</div>
|
|
|
<div class="stage-card__wide-label">{{ wideRateLabel || `闭环率 ${healthRateText ?? `${healthRate}%`}` }}</div>
|
|
|
<div class="stage-card__wide-track">
|
|
|
<div class="stage-card__wide-fill" :style="{ width: `${clampedWideRate}%` }" />
|
|
|
@@ -249,42 +285,83 @@ const isWide = computed(() => (props.cardSpan ?? 1) > 1);
|
|
|
justify-content: space-between;
|
|
|
}
|
|
|
|
|
|
-.stage-card__values {
|
|
|
+/* S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:当前待处理异常 主指标块 */
|
|
|
+.stage-card__pending {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.stage-card__pending-head {
|
|
|
display: flex;
|
|
|
align-items: baseline;
|
|
|
- gap: 12px;
|
|
|
+ gap: 10px;
|
|
|
}
|
|
|
|
|
|
-.stage-card__values--wide {
|
|
|
- flex-wrap: wrap;
|
|
|
+.stage-card__pending-label {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #c6c6cd;
|
|
|
+ letter-spacing: 0.04em;
|
|
|
}
|
|
|
|
|
|
-.stage-card__value-item {
|
|
|
- display: inline-flex;
|
|
|
- align-items: baseline;
|
|
|
- gap: 2px;
|
|
|
- min-width: 56px;
|
|
|
- font-size: 28px;
|
|
|
+.stage-card__pending-num {
|
|
|
+ font-size: 32px;
|
|
|
line-height: 1;
|
|
|
font-weight: 700;
|
|
|
+ color: #e1e2eb;
|
|
|
+}
|
|
|
+
|
|
|
+/* S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:摘要行(关注 / 严重 / 超时) */
|
|
|
+.stage-card__summary-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ gap: 16px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ margin-top: 4px;
|
|
|
}
|
|
|
|
|
|
-.stage-card__value-label {
|
|
|
+.stage-card__summary-item {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: baseline;
|
|
|
+ gap: 4px;
|
|
|
font-size: 13px;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.stage-card__summary-key {
|
|
|
font-weight: 500;
|
|
|
+ color: #c6c6cd;
|
|
|
opacity: 0.85;
|
|
|
}
|
|
|
|
|
|
-.stage-card__value-item--green {
|
|
|
- color: #6de039;
|
|
|
+.stage-card__summary-val {
|
|
|
+ font-family: 'Roboto Mono', monospace;
|
|
|
+ font-size: 16px;
|
|
|
+ line-height: 1;
|
|
|
}
|
|
|
|
|
|
-.stage-card__value-item--yellow {
|
|
|
- color: #ffc107;
|
|
|
+.stage-card__summary-item--yellow .stage-card__summary-val { color: #ffc107; }
|
|
|
+.stage-card__summary-item--red .stage-card__summary-val { color: #ff6450; }
|
|
|
+.stage-card__summary-item--orange .stage-card__summary-val { color: #ff9b5a; }
|
|
|
+
|
|
|
+/* S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:正常执行订单 次级信息 */
|
|
|
+.stage-card__normal-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ gap: 6px;
|
|
|
+ margin-top: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #c6c6cd;
|
|
|
+}
|
|
|
+
|
|
|
+.stage-card__normal-key {
|
|
|
+ opacity: 0.85;
|
|
|
}
|
|
|
|
|
|
-.stage-card__value-item--red {
|
|
|
- color: #ff6450;
|
|
|
+.stage-card__normal-val {
|
|
|
+ font-family: 'Roboto Mono', monospace;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #6de039;
|
|
|
}
|
|
|
|
|
|
.stage-card__value {
|
|
|
@@ -300,6 +377,7 @@ const isWide = computed(() => (props.cardSpan ?? 1) > 1);
|
|
|
font-size: 42px;
|
|
|
}
|
|
|
|
|
|
+/* 旧 metric 单行(保留作为 health-rate / status-label 兼容样式根) */
|
|
|
.stage-card__metric {
|
|
|
margin-top: 6px;
|
|
|
font-size: 12px;
|
|
|
@@ -343,12 +421,22 @@ const isWide = computed(() => (props.cardSpan ?? 1) > 1);
|
|
|
.stage-card__wide-meter {
|
|
|
flex: 1;
|
|
|
max-width: 100%;
|
|
|
+ margin-top: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+/* S8-STAGE-CARD-SEMANTIC-LAYOUT-EXEC-1:处理效率小标题 */
|
|
|
+.stage-card__efficiency-title {
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #c6c6cd;
|
|
|
+ letter-spacing: 0.04em;
|
|
|
+ margin-bottom: 4px;
|
|
|
}
|
|
|
|
|
|
.stage-card__wide-label {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
- margin-bottom: 10px;
|
|
|
+ margin-bottom: 6px;
|
|
|
font-size: 11px;
|
|
|
color: #c6c6cd;
|
|
|
}
|