瀏覽代碼

fix(s8): show order execution stages as comparison table

YY968XX 2 月之前
父節點
當前提交
dfdeb6affd
共有 1 個文件被更改,包括 236 次插入7 次删除
  1. 236 7
      Web/src/views/aidop/s8/monitoring/components/order-execution/OrderExecutionOrderList.vue

+ 236 - 7
Web/src/views/aidop/s8/monitoring/components/order-execution/OrderExecutionOrderList.vue

@@ -1,9 +1,11 @@
 <script setup lang="ts" name="OrderExecutionOrderList">
 // ORDER-FLOW-OVERVIEW-LIST-EXPAND-1:统一订单列表 + 单行点击展开订单档案详情。
-// 替代客户分组卡片布局;行内展开复用 StageMatrixCard,不重写五阶段矩阵
+// ORDER-FLOW-OVERVIEW-EXPAND-MATRIX-TABLE-1:行内五阶段改为横向对照表(行=指标、列=阶段)
 import { computed, ref } from 'vue';
-import type { SalesOrderExecution } from '/@/views/aidop/s8/monitoring/data/order-execution/types';
-import StageMatrixCard from './StageMatrixCard.vue';
+import type {
+	SalesOrderExecution,
+	StageSnapshot,
+} from '/@/views/aidop/s8/monitoring/data/order-execution/types';
 
 interface Props {
 	orders: SalesOrderExecution[];
@@ -39,6 +41,49 @@ const fmtVar = (v: number | null | undefined) => {
 const varTone = (v: number | null | undefined): 'over' | 'under' | 'neutral' =>
 	v == null ? 'neutral' : v > 0 ? 'over' : v < 0 ? 'under' : 'neutral';
 
+const STAGE_STATUS_LABEL: Record<StageSnapshot['status'], string> = {
+	green: '正常',
+	yellow: '关注',
+	red: '严重',
+	pending: '未开始',
+};
+
+interface StageColumn {
+	key: string;
+	name: string;
+	kpiText: string;
+	targetDate: string;
+	actualReachedAt: string;
+	status: StageSnapshot['status'];
+	statusLabel: string;
+	nodeVarText: string;
+	nodeVarTone: 'over' | 'under' | 'neutral';
+	cumVarText: string;
+	cumVarTone: 'over' | 'under' | 'neutral';
+}
+
+function buildStageColumns(stages: StageSnapshot[]): StageColumn[] {
+	return stages.map((stage) => {
+		const kpi =
+			stage.actualDays != null && stage.nodeVarianceDays != null
+				? Number((stage.actualDays - stage.nodeVarianceDays).toFixed(1))
+				: null;
+		return {
+			key: stage.key,
+			name: stage.name,
+			kpiText: kpi != null ? kpi.toFixed(1) : '--',
+			targetDate: stage.targetDate || '--',
+			actualReachedAt: stage.actualReachedAt ?? '尚未达成',
+			status: stage.status,
+			statusLabel: STAGE_STATUS_LABEL[stage.status],
+			nodeVarText: fmtVar(stage.nodeVarianceDays),
+			nodeVarTone: varTone(stage.nodeVarianceDays),
+			cumVarText: fmtVar(stage.cumulativeVarianceDays),
+			cumVarTone: varTone(stage.cumulativeVarianceDays),
+		};
+	});
+}
+
 const rows = computed(() =>
 	props.orders.map((o) => ({
 		raw: o,
@@ -201,10 +246,84 @@ const rows = computed(() =>
 							</table>
 						</div>
 
-						<StageMatrixCard
-							:stages="row.raw.lifecycle"
-							:focus-key="row.raw.focusNodeKey ?? row.raw.currentNodeKey ?? null"
-						/>
+						<div class="oe-detail-matrix">
+							<header class="oe-detail-section-head">
+								<span class="oe-detail-section-bar" />
+								<h3 class="oe-detail-section-title">五阶段对照</h3>
+								<span class="oe-detail-matrix__legend">
+									<span class="oe-detail-matrix__legend-item"><span class="oe-detail-matrix__legend-dot oe-detail-matrix__legend-dot--green" />达成规划</span>
+									<span class="oe-detail-matrix__legend-item"><span class="oe-detail-matrix__legend-dot oe-detail-matrix__legend-dot--yellow" />未达成规划,对客户无影响</span>
+									<span class="oe-detail-matrix__legend-item"><span class="oe-detail-matrix__legend-dot oe-detail-matrix__legend-dot--red" />未达成规划,对客户有影响</span>
+								</span>
+							</header>
+							<table class="oe-detail-matrix-table">
+								<thead>
+									<tr>
+										<th class="oe-detail-matrix-table__metric-head">时间节点</th>
+										<th
+											v-for="col in buildStageColumns(row.raw.lifecycle)"
+											:key="col.key"
+											class="oe-detail-matrix-table__stage-head"
+											:class="`oe-detail-matrix-table__stage-head--${col.status}`"
+										>
+											{{ col.name }}
+										</th>
+									</tr>
+								</thead>
+								<tbody>
+									<tr>
+										<td class="oe-detail-matrix-table__metric">KPI</td>
+										<td v-for="col in buildStageColumns(row.raw.lifecycle)" :key="col.key">
+											{{ col.kpiText }}
+										</td>
+									</tr>
+									<tr>
+										<td class="oe-detail-matrix-table__metric">目标时间</td>
+										<td v-for="col in buildStageColumns(row.raw.lifecycle)" :key="col.key">
+											{{ col.targetDate }}
+										</td>
+									</tr>
+									<tr>
+										<td class="oe-detail-matrix-table__metric">完成时间</td>
+										<td v-for="col in buildStageColumns(row.raw.lifecycle)" :key="col.key">
+											{{ col.actualReachedAt }}
+										</td>
+									</tr>
+									<tr>
+										<td class="oe-detail-matrix-table__metric">节点状态</td>
+										<td v-for="col in buildStageColumns(row.raw.lifecycle)" :key="col.key">
+											<span
+												class="oe-detail-matrix-table__status"
+												:class="`oe-detail-matrix-table__status--${col.status}`"
+											>
+												<span class="oe-detail-matrix-table__status-dot" />
+												{{ col.statusLabel }}
+											</span>
+										</td>
+									</tr>
+									<tr>
+										<td class="oe-detail-matrix-table__metric">当前节点偏差(天)</td>
+										<td
+											v-for="col in buildStageColumns(row.raw.lifecycle)"
+											:key="col.key"
+											:class="`oe-var--${col.nodeVarTone}`"
+										>
+											{{ col.nodeVarText }}
+										</td>
+									</tr>
+									<tr>
+										<td class="oe-detail-matrix-table__metric">累计偏差(天)</td>
+										<td
+											v-for="col in buildStageColumns(row.raw.lifecycle)"
+											:key="col.key"
+											:class="`oe-var--${col.cumVarTone}`"
+										>
+											{{ col.cumVarText }}
+										</td>
+									</tr>
+								</tbody>
+							</table>
+						</div>
 					</section>
 				</transition>
 			</div>
@@ -505,6 +624,116 @@ const rows = computed(() =>
 .oe-detail-status--yellow { color: #ffc107; }
 .oe-detail-status--red { color: #ffb4ab; }
 
+.oe-detail-matrix {
+	display: flex;
+	flex-direction: column;
+	gap: 8px;
+}
+
+.oe-detail-matrix__legend {
+	display: inline-flex;
+	flex-wrap: wrap;
+	gap: 12px;
+	margin-left: auto;
+	font-size: 11px;
+	color: var(--order-text-muted, #909097);
+}
+
+.oe-detail-matrix__legend-item {
+	display: inline-flex;
+	align-items: center;
+	gap: 5px;
+}
+
+.oe-detail-matrix__legend-dot {
+	width: 8px;
+	height: 8px;
+	border-radius: 999px;
+	display: inline-block;
+}
+
+.oe-detail-matrix__legend-dot--green { background: #88fd54; }
+.oe-detail-matrix__legend-dot--yellow { background: #ffc107; }
+.oe-detail-matrix__legend-dot--red { background: #ffb4ab; }
+
+.oe-detail-matrix-table {
+	width: 100%;
+	border-collapse: collapse;
+	background: rgba(25, 28, 34, 0.55);
+	border: 1px solid rgba(69, 70, 77, 0.28);
+	border-radius: 10px;
+	overflow: hidden;
+	font-size: 12px;
+	table-layout: fixed;
+}
+
+.oe-detail-matrix-table th,
+.oe-detail-matrix-table td {
+	padding: 8px 10px;
+	text-align: center;
+	border-bottom: 1px solid rgba(69, 70, 77, 0.18);
+	border-right: 1px solid rgba(69, 70, 77, 0.18);
+	color: var(--order-text-primary, #e1e2eb);
+	font-family: 'JetBrains Mono', 'Cascadia Code', Menlo, monospace;
+}
+
+.oe-detail-matrix-table th:last-child,
+.oe-detail-matrix-table td:last-child {
+	border-right: none;
+}
+
+.oe-detail-matrix-table tr:last-child td {
+	border-bottom: none;
+}
+
+.oe-detail-matrix-table thead th {
+	background: rgba(15, 19, 26, 0.7);
+	color: var(--order-text-muted, #909097);
+	font-weight: 600;
+}
+
+.oe-detail-matrix-table__metric-head {
+	width: 140px;
+	text-align: left;
+}
+
+.oe-detail-matrix-table__metric {
+	text-align: left;
+	color: var(--order-text-muted, #909097);
+	background: rgba(15, 19, 26, 0.45);
+	font-family: 'PingFang SC', sans-serif;
+	font-weight: 600;
+	width: 140px;
+}
+
+.oe-detail-matrix-table__stage-head--green { box-shadow: inset 3px 0 0 #88fd54; }
+.oe-detail-matrix-table__stage-head--yellow { box-shadow: inset 3px 0 0 #ffc107; }
+.oe-detail-matrix-table__stage-head--red { box-shadow: inset 3px 0 0 #ffb4ab; }
+.oe-detail-matrix-table__stage-head--pending { box-shadow: inset 3px 0 0 rgba(144, 144, 151, 0.45); }
+
+.oe-detail-matrix-table__status {
+	display: inline-flex;
+	align-items: center;
+	gap: 5px;
+	padding: 1px 8px;
+	border-radius: 999px;
+	font-size: 11px;
+	font-family: 'PingFang SC', sans-serif;
+	background: rgba(50, 53, 60, 0.6);
+}
+
+.oe-detail-matrix-table__status-dot {
+	width: 6px;
+	height: 6px;
+	border-radius: 999px;
+	background: currentColor;
+}
+
+.oe-detail-matrix-table__status--green { color: #88fd54; }
+.oe-detail-matrix-table__status--yellow { color: #ffc107; }
+.oe-detail-matrix-table__status--red { color: #ffb4ab; }
+.oe-detail-matrix-table__status--pending { color: var(--order-text-muted, #909097); }
+
 .oe-expand-enter-active,
 .oe-expand-leave-active {
 	transition: max-height 200ms ease, opacity 180ms ease;