From 0772a12074e4a8b072ddd328858cffbebe590105 Mon Sep 17 00:00:00 2001 From: lzh Date: Wed, 25 Mar 2026 11:36:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(@vben/web-antd):=20=E5=B7=A5=E5=8D=95?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=8F=96=E6=B6=88=E7=8A=B6=E6=80=81=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=E6=9D=A1=E6=94=B9=E9=80=A0=E5=8F=8A=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E8=B4=A8=E9=87=8F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 取消状态下进度条展示 timeline 实际节点,末尾追加"已取消"终态 - 已走过节点灰色实心,取消终态节点深灰,进度线灰色 - 移除底部红色 cancelled-banner,改为进度条内联展示 - 进行中节点蓝色标签改为显示到达时间 - 提取 isCurrentActive / getStepTime 简化模板逻辑 - timeline 去重防御异常数据,取消终态改用索引判断 - 移除 progress-steps-wrapper 的 overflow:hidden 避免裁切光晕 - 清理无引用的 cancelled-banner CSS Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/views/ops/work-order/detail/index.vue | 188 ++++++++++++------ 1 file changed, 124 insertions(+), 64 deletions(-) diff --git a/apps/web-antd/src/views/ops/work-order/detail/index.vue b/apps/web-antd/src/views/ops/work-order/detail/index.vue index f5f5e0d82..b798edd46 100644 --- a/apps/web-antd/src/views/ops/work-order/detail/index.vue +++ b/apps/web-antd/src/views/ops/work-order/detail/index.vue @@ -343,8 +343,51 @@ const orderImages = computed(() => { return images; }); +/** 是否已取消 */ +const isCancelled = computed(() => order.value.status === 'CANCELLED'); + +/** 是否为当前进行中(非终态) */ +const isCurrentActive = computed( + () => + !isCancelled.value && + !['COMPLETED', 'CANCELLED'].includes(order.value.status), +); + +/** 根据 step.key 查找 timeline 中对应的时间 */ +function getStepTime(key: string): string { + const t = timeline.value.find((item) => item.status === key); + if (t?.time) return formatRelativeTime(t.time); + if (key === 'PENDING') return formatRelativeTime(order.value.createTime); + if (key === 'COMPLETED' && order.value.endTime) + return formatRelativeTime(order.value.endTime); + return ''; +} + /** 动态生成应该显示的状态步骤(根据 timeline 过滤) */ const visibleSteps = computed(() => { + // 取消状态:只展示 timeline 中已发生的节点(按时间顺序,去重) + if (isCancelled.value) { + const seen = new Set(); + return timeline.value + .filter((t) => { + if (seen.has(t.status)) return false; + seen.add(t.status); + return true; + }) + .map((t) => { + const step = STATUS_STEPS.find((s) => s.key === t.status); + return { + key: t.status, + title: + t.status === 'CANCELLED' + ? '已取消' + : (step?.title || t.statusName || t.status), + icon: step?.icon || 'solar:close-circle-bold-duotone', + desc: t.description || step?.desc || '', + }; + }); + } + // 必须显示的节点(主流程) const requiredSteps = new Set([ 'ARRIVED', @@ -377,7 +420,10 @@ const visibleSteps = computed(() => { /** 计算当前状态步骤索引 */ const currentStepIndex = computed(() => { - if (order.value.status === 'CANCELLED') return -1; + // 取消状态:所有节点都视为"已走过",索引指向最后一个 + if (isCancelled.value) { + return visibleSteps.value.length - 1; + } if (order.value.status === 'PAUSED') { // 暂停状态显示在到岗后 return visibleSteps.value.findIndex((s) => s.key === 'ARRIVED'); @@ -625,10 +671,15 @@ onUnmounted(stopPolling);
{{ order.title }} - {{ ORDER_TYPE_TEXT_MAP[order.orderType] }} - + - + {{ STATUS_TEXT_MAP[order.status] }} - +
@@ -816,46 +883,28 @@ onUnmounted(stopPolling);
{{ step.title }}
+
- {{ - timeline.find((t) => t.status === step.key)?.time - ? formatRelativeTime( - timeline.find((t) => t.status === step.key)?.time || '', - ) - : step.key === 'PENDING' - ? formatRelativeTime(order.createTime) - : step.key === 'COMPLETED' && order.endTime - ? formatRelativeTime(order.endTime) - : '' - }} + {{ getStepTime(step.key) }}
+
- 进行中 + {{ getStepTime(step.key) }}
- -
- - 工单已取消 -
-
@@ -1318,7 +1367,6 @@ onUnmounted(stopPolling); .progress-steps-wrapper { position: relative; padding: 0 72px; - overflow: hidden; } .progress-nodes { @@ -1347,6 +1395,10 @@ onUnmounted(stopPolling); &.line-all-completed { background: #52c41a; } + + &.line-cancelled { + background: #d9d9d9; + } } .progress-node { @@ -1403,10 +1455,37 @@ onUnmounted(stopPolling); background: #fafafa; } -.node-cancelled .node-icon { +/* 取消流程 - 已走过的节点(灰色实心) */ +.node-cancelled-done .node-icon { color: #fff; - background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%); - border-color: #ff4d4f; + background: #bfbfbf; + border-color: #bfbfbf; + box-shadow: none; +} + +.node-cancelled-done .node-label { + color: #8c8c8c; +} + +.node-cancelled-done .node-time { + color: #bfbfbf; +} + +/* 取消流程 - 最终取消节点(红灰色调) */ +.node-cancelled-end .node-icon { + color: #fff; + background: #8c8c8c; + border-color: #8c8c8c; + box-shadow: none; +} + +.node-cancelled-end .node-label { + font-weight: 600; + color: #8c8c8c; +} + +.node-cancelled-end .node-time { + color: #bfbfbf; } .node-label { @@ -1461,21 +1540,6 @@ onUnmounted(stopPolling); animation: pulse 1.5s infinite; } -.cancelled-banner { - display: flex; - gap: 8px; - align-items: center; - justify-content: center; - padding: 10px; - margin-top: 16px; - font-size: 13px; - font-weight: 500; - color: #ff4d4f; - background: #fff2f0; - border: 1px solid #ffccc7; - border-radius: 6px; -} - /* ========== 基础信息行:左右等高 ========== */ .info-row :deep(> .ant-col) { display: flex; @@ -1811,10 +1875,6 @@ onUnmounted(stopPolling); color: rgb(255 255 255 / 75%); } - .cancelled-banner { - background: rgb(255 77 79 / 10%); - border-color: rgb(255 77 79 / 30%); - } } /* ========== 通用卡片样式 ========== */