style(@vben/web-antd): 统一工单中心配色方案为柔和浅底双色风格

- STATUS_COLOR_MAP / ORDER_TYPE_COLOR_MAP 由单色改为 { bg, text } 双色结构
- 新增 FACILITIES / SERVICE 工单类型配色
- 优先级配色改为按数值直接映射(P0~P3),不再依赖字典 colorType
- Tag 组件替换为 span + inline style,精确控制背景色与文字色
- 仪表板饼图 STATUS_COLORS 同步更新
- 同步更新 cleaning/work-order 模块保持一致

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-03-25 11:35:37 +08:00
parent 7d22e4b395
commit d0395ba40a
6 changed files with 149 additions and 97 deletions

View File

@@ -3,17 +3,18 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { OpsOrderCenterApi } from '#/api/ops/order-center';
/** 状态颜色映射 */
export const STATUS_COLOR_MAP: Record<string, string> = {
PENDING: '#8c8c8c', // 灰色 - 待分配
QUEUED: '#faad14', // 黄色 - 排队中
DISPATCHED: '#1677ff', // 蓝色 - 已推送
CONFIRMED: '#13c2c2', // 青色 - 已确认
ARRIVED: '#52c41a', // 绿色 - 已到岗
PAUSED: '#fa8c16', // 橙色 - 已暂停
RESUMED: '#52c41a', // 绿色 - 已恢复
COMPLETED: '#389e0d', // 深绿 - 已完成
CANCELLED: '#ff4d4f', // 红色 - 已取消
/** 状态颜色映射(背景色 + 文字色) */
export const STATUS_COLOR_MAP: Record<string, { bg: string; text: string }> = {
PENDING: { bg: '#FFF3E8', text: '#C2540A' }, // 待分配
QUEUED: { bg: '#F0EDFF', text: '#6D28D9' }, // 排队中
DISPATCHED: { bg: '#E0F2FE', text: '#0284C7' }, // 已派发
CONFIRMED: { bg: '#EEF0FF', text: '#3730A3' }, // 已确认
ARRIVED: { bg: '#E5F5EF', text: '#0D9488' }, // 已到岗
IN_PROGRESS: { bg: '#E8F0FE', text: '#1558C0' }, // 进行中
PAUSED: { bg: '#FFF7E0', text: '#92400E' }, // 已暂停
RESUMED: { bg: '#E5F6FB', text: '#0891B2' }, // 已恢复
COMPLETED: { bg: '#E8FAF2', text: '#0A7A55' }, // 已完成
CANCELLED: { bg: '#F0EDE8', text: '#6B5E52' }, // 已取消
};
/** 状态文本映射 */
@@ -69,11 +70,15 @@ export const ORDER_TYPE_TEXT_MAP: Record<string, string> = {
SECURITY: '安保',
};
/** 工单类型颜色映射 */
export const ORDER_TYPE_COLOR_MAP: Record<string, string> = {
CLEAN: '#52c41a', // 绿色
REPAIR: '#fa8c16', // 橙色
SECURITY: '#1890ff', // 蓝色
/** 工单类型颜色映射(背景色 + 文字色) */
export const ORDER_TYPE_COLOR_MAP: Record<
string,
{ bg: string; text: string }
> = {
CLEAN: { bg: '#E5FAF2', text: '#047857' }, // 保洁
SECURITY: { bg: '#EEF0FF', text: '#3730A3' }, // 安保
FACILITIES: { bg: '#FFF5E0', text: '#92400E' }, // 设施
SERVICE: { bg: '#E0F2FE', text: '#0369A1' }, // 服务
};
/** 工单状态选项 */

View File

@@ -619,13 +619,20 @@ onUnmounted(stopPolling);
<div>
<div class="flex items-center gap-2">
<span class="text-base font-semibold">{{ order.title }}</span>
<Tag :color="STATUS_COLOR_MAP[order.status]" class="status-tag">
<span
v-if="STATUS_COLOR_MAP[order.status]"
class="status-tag inline-flex items-center rounded px-2 py-0.5 text-xs font-medium"
:style="{
backgroundColor: STATUS_COLOR_MAP[order.status]?.bg,
color: STATUS_COLOR_MAP[order.status]?.text,
}"
>
<IconifyIcon
:icon="STATUS_ICON_MAP[order.status] || 'solar:circle-bold'"
class="mr-1"
/>
{{ STATUS_TEXT_MAP[order.status] }}
</Tag>
</span>
<span
v-if="order.priority != null"
class="priority-badge inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium"
@@ -1009,10 +1016,16 @@ onUnmounted(stopPolling);
class="custom-descriptions"
>
<Descriptions.Item label="工单类型">
<Tag :color="ORDER_TYPE_COLOR_MAP[order.orderType]">
<span
class="inline-flex items-center rounded px-2 py-0.5 text-xs font-medium"
:style="{
backgroundColor: ORDER_TYPE_COLOR_MAP[order.orderType]?.bg,
color: ORDER_TYPE_COLOR_MAP[order.orderType]?.text,
}"
>
<IconifyIcon icon="solar:broom-bold-duotone" class="mr-1" />
{{ ORDER_TYPE_TEXT_MAP[order.orderType] }}
</Tag>
</span>
</Descriptions.Item>
<Descriptions.Item label="作业区域">
<span class="flex items-center gap-1">

View File

@@ -463,16 +463,32 @@ onMounted(() => {
<Grid>
<!-- 工单类型列 -->
<template #orderType="{ row }">
<Tag :color="ORDER_TYPE_COLOR_MAP[row.orderType]" size="small">
<span
v-if="ORDER_TYPE_COLOR_MAP[row.orderType]"
class="inline-block rounded px-1.5 py-0.5 text-xs font-medium"
:style="{
backgroundColor: ORDER_TYPE_COLOR_MAP[row.orderType]?.bg,
color: ORDER_TYPE_COLOR_MAP[row.orderType]?.text,
}"
>
{{ ORDER_TYPE_TEXT_MAP[row.orderType] }}
</Tag>
</span>
<span v-else class="text-gray-400">-</span>
</template>
<!-- 工单状态列 -->
<template #status="{ row }">
<Tag :color="STATUS_COLOR_MAP[row.status]" size="small">
<span
v-if="STATUS_COLOR_MAP[row.status]"
class="inline-block rounded px-1.5 py-0.5 text-xs font-medium"
:style="{
backgroundColor: STATUS_COLOR_MAP[row.status]?.bg,
color: STATUS_COLOR_MAP[row.status]?.text,
}"
>
{{ STATUS_TEXT_MAP[row.status] }}
</Tag>
</span>
<span v-else class="text-gray-400">-</span>
</template>
<!-- 优先级列 -->

View File

@@ -469,13 +469,13 @@ function getTimeTrendChartOptions(): ECOption {
function getStatusDistributionChartOptions(): ECOption {
const { statusDistribution } = statsData.value;
const STATUS_COLORS: Record<string, string> = {
待处理: '#faad14',
排队中: '#722ed1',
已派单: '#1677ff',
已到岗: '#13c2c2',
已完成: '#52c41a',
已取消: '#ff4d4f',
已暂停: '#8c8c8c',
待处理: '#C2540A', // PENDING
排队中: '#6D28D9', // QUEUED
已派单: '#0284C7', // DISPATCHED
已到岗: '#0D9488', // ARRIVED
已完成: '#0A7A55', // COMPLETED
已取消: '#6B5E52', // CANCELLED
已暂停: '#92400E', // PAUSED
};
const total = statusDistribution.reduce((sum, item) => sum + item.value, 0);
return {

View File

@@ -8,17 +8,18 @@ import { useDictStore } from '@vben/stores';
import { OpsOrderCenterApi } from '#/api/ops/order-center';
/** 状态颜色映射 */
export const STATUS_COLOR_MAP: Record<string, string> = {
PENDING: '#8c8c8c', // 灰色 - 待分配
QUEUED: '#faad14', // 黄色 - 排队中
DISPATCHED: '#1677ff', // 蓝色 - 已推送
CONFIRMED: '#13c2c2', // 青色 - 已确认
ARRIVED: '#52c41a', // 绿色 - 已到岗
PAUSED: '#fa8c16', // 橙色 - 已暂停
RESUMED: '#52c41a', // 绿色 - 已恢复
COMPLETED: '#389e0d', // 深绿 - 已完成
CANCELLED: '#ff4d4f', // 红色 - 已取消
/** 状态颜色映射(背景色 + 文字色) */
export const STATUS_COLOR_MAP: Record<string, { bg: string; text: string }> = {
PENDING: { bg: '#FFF3E8', text: '#C2540A' }, // 待分配
QUEUED: { bg: '#F0EDFF', text: '#6D28D9' }, // 排队中
DISPATCHED: { bg: '#E0F2FE', text: '#0284C7' }, // 已派发
CONFIRMED: { bg: '#EEF0FF', text: '#3730A3' }, // 已确认
ARRIVED: { bg: '#E5F5EF', text: '#0D9488' }, // 已到岗
IN_PROGRESS: { bg: '#E8F0FE', text: '#1558C0' }, // 进行中
PAUSED: { bg: '#FFF7E0', text: '#92400E' }, // 已暂停
RESUMED: { bg: '#E5F6FB', text: '#0891B2' }, // 已恢复
COMPLETED: { bg: '#E8FAF2', text: '#0A7A55' }, // 已完成
CANCELLED: { bg: '#F0EDE8', text: '#6B5E52' }, // 已取消
};
/** 状态文本映射 */
@@ -60,7 +61,36 @@ export const STATUS_TAB_OPTIONS = [
{ key: 'CANCELLED', label: '已取消', statuses: ['CANCELLED'] },
];
/** 获取优先级信息(基于字典),返回 getPriorityInfo 函数 */
/** 优先级颜色映射(按数值直接映射,不依赖字典 colorType */
const PRIORITY_STYLE_MAP: Record<
number,
{ style: { backgroundColor: string; color: string }; icon: string }
> = {
0: {
style: { backgroundColor: '#FFF0F0', color: '#C01D1D' },
icon: 'solar:bolt-bold',
}, // P0 紧急
1: {
style: { backgroundColor: '#FFF4E6', color: '#C2410C' },
icon: 'lucide:alert-triangle',
}, // P1 重要
2: {
style: { backgroundColor: '#F0EDE8', color: '#6B5E52' },
icon: 'lucide:info',
}, // P2 一般
3: {
style: { backgroundColor: '#E8FAF2', color: '#0A7A55' },
icon: 'lucide:info',
}, // P3 低优
};
/** 默认优先级样式(未匹配时回退) */
const DEFAULT_PRIORITY = {
style: { backgroundColor: '#F0EDE8', color: '#6B5E52' },
icon: 'lucide:info',
};
/** 获取优先级信息(基于字典标签 + 数值精确配色) */
export function usePriorityInfo() {
const dictStore = useDictStore();
const priorityOptions = computed(() =>
@@ -68,40 +98,13 @@ export function usePriorityInfo() {
);
function getPriorityInfo(priority: number | string | undefined) {
const p = Number(priority);
const dictItem = priorityOptions.value.find(
(item) => item.value === String(priority),
);
if (!dictItem) {
return {
label: `P${priority}`,
style: { color: '#8c8c8c', backgroundColor: '#f5f5f5' },
icon: 'lucide:info',
};
}
let style = { color: '#8c8c8c', backgroundColor: '#f5f5f5' };
switch (dictItem.colorType) {
case 'danger': {
style = { color: '#ff4d4f', backgroundColor: '#fff1f0' };
break;
}
case 'info': {
style = { color: '#1677ff', backgroundColor: '#e6f4ff' };
break;
}
case 'success': {
style = { color: '#52c41a', backgroundColor: '#f6ffed' };
break;
}
case 'warning': {
style = { color: '#fa8c16', backgroundColor: '#fff7e6' };
break;
}
// No default
}
let icon = 'lucide:info';
if (Number(priority) === 0) icon = 'solar:bolt-bold';
else if (Number(priority) === 1) icon = 'lucide:alert-triangle';
return { label: dictItem.label, style, icon };
const label = dictItem?.label ?? `P${priority}`;
const mapped = PRIORITY_STYLE_MAP[p] ?? DEFAULT_PRIORITY;
return { label, ...mapped };
}
return { getPriorityInfo };
@@ -121,11 +124,15 @@ export const ORDER_TYPE_TEXT_MAP: Record<string, string> = {
SECURITY: '安保',
};
/** 工单类型颜色映射 */
export const ORDER_TYPE_COLOR_MAP: Record<string, string> = {
CLEAN: '#52c41a', // 绿色
REPAIR: '#fa8c16', // 橙色
SECURITY: '#1890ff', // 蓝色
/** 工单类型颜色映射(背景色 + 文字色) */
export const ORDER_TYPE_COLOR_MAP: Record<
string,
{ bg: string; text: string }
> = {
CLEAN: { bg: '#E5FAF2', text: '#047857' }, // 保洁
SECURITY: { bg: '#EEF0FF', text: '#3730A3' }, // 安保
FACILITIES: { bg: '#FFF5E0', text: '#92400E' }, // 设施
SERVICE: { bg: '#E0F2FE', text: '#0369A1' }, // 服务
};
/** 工单状态选项 */

View File

@@ -8,15 +8,7 @@ import { useRouter } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import {
Button,
Card,
Input,
message,
Select,
Tabs,
Tag,
} from 'ant-design-vue';
import { Button, Card, Input, message, Select, Tabs } from 'ant-design-vue';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { OpsCleaningApi, sendDeviceNotify } from '#/api/ops/cleaning';
@@ -32,6 +24,7 @@ import {
STATUS_TAB_OPTIONS,
STATUS_TEXT_MAP,
useGridColumns,
usePriorityInfo,
} from './data';
import AssignForm from './modules/assign-form.vue';
import CancelForm from './modules/cancel-form.vue';
@@ -42,6 +35,7 @@ import UpgradePriorityForm from './modules/upgrade-priority-form.vue';
defineOptions({ name: 'WorkOrderCenter' });
const { getPriorityInfo } = usePriorityInfo();
const router = useRouter();
const viewMode = ref<'card' | 'list'>('card');
const activeTab = ref('ALL');
@@ -490,25 +484,42 @@ onActivated(() => {
<Grid>
<!-- 工单类型列 -->
<template #orderType="{ row }">
<Tag :color="ORDER_TYPE_COLOR_MAP[row.orderType]" size="small">
<span
v-if="ORDER_TYPE_COLOR_MAP[row.orderType]"
class="inline-block rounded px-1.5 py-0.5 text-xs font-medium"
:style="{
backgroundColor: ORDER_TYPE_COLOR_MAP[row.orderType]?.bg,
color: ORDER_TYPE_COLOR_MAP[row.orderType]?.text,
}"
>
{{ ORDER_TYPE_TEXT_MAP[row.orderType] }}
</Tag>
</span>
<span v-else class="text-gray-400">-</span>
</template>
<!-- 工单状态列 -->
<template #status="{ row }">
<Tag :color="STATUS_COLOR_MAP[row.status]" size="small">
<span
v-if="STATUS_COLOR_MAP[row.status]"
class="inline-block rounded px-1.5 py-0.5 text-xs font-medium"
:style="{
backgroundColor: STATUS_COLOR_MAP[row.status]?.bg,
color: STATUS_COLOR_MAP[row.status]?.text,
}"
>
{{ STATUS_TEXT_MAP[row.status] }}
</Tag>
</span>
<span v-else class="text-gray-400">-</span>
</template>
<!-- 优先级列 -->
<template #priority="{ row }">
<Tag v-if="row.priority === 0" color="error" size="small"> P0 </Tag>
<Tag v-else-if="row.priority === 1" color="warning" size="small">
P1
</Tag>
<span v-else class="text-gray-400">P2</span>
<span
class="inline-block rounded px-1.5 py-0.5 text-xs font-medium"
:style="getPriorityInfo(row.priority).style"
>
{{ getPriorityInfo(row.priority).label }}
</span>
</template>
<!-- 执行人列 -->