refactor(ops): 代码格式化与 lint 修复
清理未使用的导入、修复格式化问题、优化 requiredSteps 为 Set 提升查找性能 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,14 +7,8 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { AuthenticationLoginExpiredModal, useVbenModal } from '@vben/common-ui';
|
||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||
import { isTenantEnable, useTabs, useWatermark } from '@vben/hooks';
|
||||
import {
|
||||
AntdProfileOutlined,
|
||||
BookOpenText,
|
||||
CircleHelp,
|
||||
SvgGithubIcon,
|
||||
} from '@vben/icons';
|
||||
import { AntdProfileOutlined, CircleHelp } from '@vben/icons';
|
||||
import {
|
||||
BasicLayout,
|
||||
Help,
|
||||
@@ -25,7 +19,7 @@ import {
|
||||
} from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { formatDateTime, openWindow } from '@vben/utils';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
|
||||
@@ -13,10 +13,7 @@ import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { Button, message, Modal, Tag } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
getTrafficRealtime,
|
||||
getWorkspaceStats,
|
||||
} from '#/api/ops/order-center';
|
||||
import { getTrafficRealtime, getWorkspaceStats } from '#/api/ops/order-center';
|
||||
|
||||
import { BackgroundChart } from '../../../components/background-chart';
|
||||
import { createBackgroundChartOptions } from './utils/chart-options';
|
||||
@@ -325,12 +322,24 @@ onUnmounted(stopPolling);
|
||||
<div :class="cardContentClasses.container">
|
||||
<h3 :class="cardContentClasses.title">实时客流监测</h3>
|
||||
<div :class="cardContentClasses.numberContainer">
|
||||
<span :class="cardContentClasses.largeNumber">{{ formatNumber(trafficTotalIn) }}</span>
|
||||
<Tag color="success" :class="`text-xs font-bold ${textShadowClasses.tag}`">+12%</Tag>
|
||||
<span :class="cardContentClasses.largeNumber">{{
|
||||
formatNumber(trafficTotalIn)
|
||||
}}</span>
|
||||
<Tag
|
||||
color="success"
|
||||
:class="`text-xs font-bold ${textShadowClasses.tag}`"
|
||||
>
|
||||
+12%
|
||||
</Tag>
|
||||
</div>
|
||||
<p :class="cardContentClasses.description">预计高峰时间: 14:00</p>
|
||||
</div>
|
||||
<Button type="text" shape="circle" :class="buttonClasses.refresh" @click="handleRefreshStats">
|
||||
<Button
|
||||
type="text"
|
||||
shape="circle"
|
||||
:class="buttonClasses.refresh"
|
||||
@click="handleRefreshStats"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ant-design:more-outlined" class="text-base" />
|
||||
</template>
|
||||
@@ -351,9 +360,15 @@ onUnmounted(stopPolling);
|
||||
</div>
|
||||
|
||||
<div :class="taskListClasses.listContainer">
|
||||
<div v-if="filteredTasks.length === 0" :class="taskListClasses.emptyState">
|
||||
<div
|
||||
v-if="filteredTasks.length === 0"
|
||||
:class="taskListClasses.emptyState"
|
||||
>
|
||||
<div :class="taskListClasses.emptyContent">
|
||||
<IconifyIcon icon="ant-design:check-circle-outlined" :class="taskListClasses.emptyIcon" />
|
||||
<IconifyIcon
|
||||
icon="ant-design:check-circle-outlined"
|
||||
:class="taskListClasses.emptyIcon"
|
||||
/>
|
||||
<p>暂无待办任务</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -367,7 +382,10 @@ onUnmounted(stopPolling);
|
||||
<div :class="taskListClasses.taskContent">
|
||||
<div :class="taskListClasses.taskLeft">
|
||||
<div
|
||||
:class="[taskListClasses.taskIcon, getPriorityConfig(task.priority).bg]"
|
||||
:class="[
|
||||
taskListClasses.taskIcon,
|
||||
getPriorityConfig(task.priority).bg,
|
||||
]"
|
||||
>
|
||||
<IconifyIcon
|
||||
icon="ant-design:clock-circle-outlined"
|
||||
@@ -376,7 +394,9 @@ onUnmounted(stopPolling);
|
||||
/>
|
||||
</div>
|
||||
<div :class="taskListClasses.taskInfo">
|
||||
<div :class="taskListClasses.taskTitle">{{ task.title }}</div>
|
||||
<div :class="taskListClasses.taskTitle">
|
||||
{{ task.title }}
|
||||
</div>
|
||||
<div :class="taskListClasses.taskMeta">
|
||||
<span>{{ task.location }}</span>
|
||||
<span v-if="task.createTime">• {{ task.createTime }}</span>
|
||||
@@ -385,7 +405,10 @@ onUnmounted(stopPolling);
|
||||
</div>
|
||||
<Tag
|
||||
:color="getPriorityConfig(task.priority).color"
|
||||
:class="[taskListClasses.taskPriorityTag, getPriorityConfig(task.priority).border]"
|
||||
:class="[
|
||||
taskListClasses.taskPriorityTag,
|
||||
getPriorityConfig(task.priority).border,
|
||||
]"
|
||||
>
|
||||
{{ task.priority }}
|
||||
</Tag>
|
||||
@@ -399,7 +422,10 @@ onUnmounted(stopPolling);
|
||||
@click.stop="handleTaskComplete(task.id)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ant-design:check-circle-outlined" class="text-xs" />
|
||||
<IconifyIcon
|
||||
icon="ant-design:check-circle-outlined"
|
||||
class="text-xs"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
@@ -410,7 +436,10 @@ onUnmounted(stopPolling);
|
||||
@click.stop="handleTaskDetail(task)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ant-design:eye-outlined" class="text-xs" />
|
||||
<IconifyIcon
|
||||
icon="ant-design:eye-outlined"
|
||||
class="text-xs"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
<Button
|
||||
@@ -421,7 +450,10 @@ onUnmounted(stopPolling);
|
||||
@click.stop="handleTaskDelete(task.id)"
|
||||
>
|
||||
<template #icon>
|
||||
<IconifyIcon icon="ant-design:delete-outlined" class="text-xs" />
|
||||
<IconifyIcon
|
||||
icon="ant-design:delete-outlined"
|
||||
class="text-xs"
|
||||
/>
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -433,18 +465,33 @@ onUnmounted(stopPolling);
|
||||
</div>
|
||||
|
||||
<!-- --- 右侧列 (40%) --- -->
|
||||
<div class="flex h-full w-[40%] flex-none flex-col gap-5" style="padding-right: 1.25rem;">
|
||||
<div
|
||||
class="flex h-full w-[40%] flex-none flex-col gap-5"
|
||||
style="padding-right: 1.25rem"
|
||||
>
|
||||
<!-- 卡片3: 工单趋势分析 -->
|
||||
<GlassCard class="relative flex h-[35%] flex-col overflow-hidden p-6">
|
||||
<div class="relative z-10 mb-2 flex items-start justify-between">
|
||||
<div :class="cardContentClasses.container">
|
||||
<h3 :class="cardContentClasses.title">工单趋势分析</h3>
|
||||
<div :class="cardContentClasses.numberContainer">
|
||||
<span :class="cardContentClasses.largeNumber">{{ todayOrderCount }}</span>
|
||||
<Tag color="processing" :class="`text-xs font-bold ${textShadowClasses.tag}`">+{{ newOrderCount }} 新增</Tag>
|
||||
<span :class="cardContentClasses.largeNumber">{{
|
||||
todayOrderCount
|
||||
}}</span>
|
||||
<Tag
|
||||
color="processing"
|
||||
:class="`text-xs font-bold ${textShadowClasses.tag}`"
|
||||
>
|
||||
+{{ newOrderCount }} 新增
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="text" size="small" :class="buttonClasses.viewAll" @click="message.info('查看全部工单')">
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
:class="buttonClasses.viewAll"
|
||||
@click="message.info('查看全部工单')"
|
||||
>
|
||||
查看全部
|
||||
</Button>
|
||||
</div>
|
||||
@@ -484,7 +531,14 @@ onUnmounted(stopPolling);
|
||||
<!-- 神经网络背景图案 -->
|
||||
<div :class="aiCardClasses.background">
|
||||
<svg width="100%" height="100%">
|
||||
<pattern id="grid" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
|
||||
<pattern
|
||||
id="grid"
|
||||
x="0"
|
||||
y="0"
|
||||
width="20"
|
||||
height="20"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<circle cx="1" cy="1" r="1" fill="white" />
|
||||
</pattern>
|
||||
<rect width="100%" height="100%" fill="url(#grid)" />
|
||||
@@ -502,20 +556,34 @@ onUnmounted(stopPolling);
|
||||
<div :class="aiCardClasses.leftContent">
|
||||
<div class="mb-2 flex items-center gap-2">
|
||||
<span :class="aiCardClasses.iconContainer">
|
||||
<IconifyIcon icon="ant-design:robot-outlined" class="text-xs text-white" />
|
||||
<IconifyIcon
|
||||
icon="ant-design:robot-outlined"
|
||||
class="text-xs text-white"
|
||||
/>
|
||||
</span>
|
||||
<span :class="aiCardClasses.label">AI Agent</span>
|
||||
</div>
|
||||
<h3 :class="aiCardClasses.title">需要我帮你做什么吗?</h3>
|
||||
<Button type="primary" :class="aiCardClasses.button" @click="message.info('打开AI助手')">
|
||||
<Button
|
||||
type="primary"
|
||||
:class="aiCardClasses.button"
|
||||
@click="message.info('打开AI助手')"
|
||||
>
|
||||
使用AI助手
|
||||
<IconifyIcon icon="ant-design:robot-outlined" :class="aiCardClasses.buttonIcon" />
|
||||
<IconifyIcon
|
||||
icon="ant-design:robot-outlined"
|
||||
:class="aiCardClasses.buttonIcon"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 右侧图片 -->
|
||||
<div :class="aiCardClasses.imageContainer">
|
||||
<img :src="robotImage" alt="AI Robot" :class="aiCardClasses.image" />
|
||||
<img
|
||||
:src="robotImage"
|
||||
alt="AI Robot"
|
||||
:class="aiCardClasses.image"
|
||||
/>
|
||||
</div>
|
||||
</GlassCard>
|
||||
</div>
|
||||
@@ -539,7 +607,10 @@ onUnmounted(stopPolling);
|
||||
</div>
|
||||
<div>
|
||||
<div :class="modalClasses.label">优先级</div>
|
||||
<Tag :color="getPriorityConfig(selectedTask.priority).color" class="px-2 py-1 text-xs font-bold uppercase">
|
||||
<Tag
|
||||
:color="getPriorityConfig(selectedTask.priority).color"
|
||||
class="px-2 py-1 text-xs font-bold uppercase"
|
||||
>
|
||||
{{ selectedTask.priority }}
|
||||
</Tag>
|
||||
</div>
|
||||
|
||||
@@ -20,9 +20,10 @@ import { getDashboardStats } from '#/api/ops/order-center';
|
||||
defineOptions({ name: 'CleaningWorkOrderDashboard' });
|
||||
|
||||
// ========== 保洁类型映射 ==========
|
||||
const CLEANING_TYPE_MAP: Record<
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const _CLEANING_TYPE_MAP: Record<
|
||||
string,
|
||||
{ type: string; icon: string; color: string }
|
||||
{ color: string; icon: string; type: string }
|
||||
> = {
|
||||
ROUTINE: { type: '日常保洁', icon: 'solar:broom-bold', color: '#1677ff' },
|
||||
DEEP: { type: '深度保洁', icon: 'solar:multiply-bold', color: '#52c41a' },
|
||||
@@ -74,9 +75,9 @@ interface DashboardStats {
|
||||
|
||||
// 功能类型工单排行
|
||||
functionTypeRanking: Array<{
|
||||
functionType: string;
|
||||
completed: number;
|
||||
count: number;
|
||||
functionType: string;
|
||||
rate: number;
|
||||
}>;
|
||||
}
|
||||
@@ -98,8 +99,9 @@ const { renderEcharts: renderHourlyChart } = useEcharts(hourlyChartRef);
|
||||
const { renderEcharts: renderTimeTrendChart } = useEcharts(timeTrendChartRef);
|
||||
const { renderEcharts: renderFunnelChart } = useEcharts(funnelChartRef);
|
||||
const { renderEcharts: renderHeatmapChart } = useEcharts(heatmapChartRef);
|
||||
const { renderEcharts: renderFunctionTypeRankingChart } =
|
||||
useEcharts(functionTypeRankingChartRef);
|
||||
const { renderEcharts: renderFunctionTypeRankingChart } = useEcharts(
|
||||
functionTypeRankingChartRef,
|
||||
);
|
||||
|
||||
const statsData = ref<DashboardStats>({
|
||||
pendingCount: 0,
|
||||
|
||||
@@ -346,18 +346,24 @@ const showLeaveWarning = computed(() => {
|
||||
/** 动态生成应该显示的状态步骤(根据 timeline 过滤) */
|
||||
const visibleSteps = computed(() => {
|
||||
// 必须显示的节点(主流程)
|
||||
const requiredSteps = ['PENDING', 'DISPATCHED', 'CONFIRMED', 'ARRIVED', 'COMPLETED'];
|
||||
const requiredSteps = new Set([
|
||||
'ARRIVED',
|
||||
'COMPLETED',
|
||||
'CONFIRMED',
|
||||
'DISPATCHED',
|
||||
'PENDING',
|
||||
]);
|
||||
|
||||
// timeline 中存在的状态
|
||||
const timelineStatuses = new Set(timeline.value.map(t => t.status));
|
||||
const timelineStatuses = new Set(timeline.value.map((t) => t.status));
|
||||
|
||||
// 当前状态
|
||||
const currentStatus = order.value.status;
|
||||
|
||||
// 过滤出要显示的节点
|
||||
return STATUS_STEPS.filter(step => {
|
||||
return STATUS_STEPS.filter((step) => {
|
||||
// 必须显示的节点
|
||||
if (requiredSteps.includes(step.key)) return true;
|
||||
if (requiredSteps.has(step.key)) return true;
|
||||
|
||||
// QUEUED 节点:只有在 timeline 中明确存在时才显示
|
||||
if (step.key === 'QUEUED') {
|
||||
@@ -376,7 +382,9 @@ const currentStepIndex = computed(() => {
|
||||
// 暂停状态显示在到岗后
|
||||
return visibleSteps.value.findIndex((s) => s.key === 'ARRIVED');
|
||||
}
|
||||
const index = visibleSteps.value.findIndex((s) => s.key === order.value.status);
|
||||
const index = visibleSteps.value.findIndex(
|
||||
(s) => s.key === order.value.status,
|
||||
);
|
||||
return Math.max(index, 0);
|
||||
});
|
||||
|
||||
|
||||
@@ -153,9 +153,7 @@ defineExpose({ refresh: loadStats });
|
||||
<div class="stats-content">
|
||||
<div
|
||||
class="stats-icon"
|
||||
style="
|
||||
|
||||
--icon-color: #ff4d4f; --icon-bg: #fff1f0"
|
||||
style="--icon-color: #ff4d4f; --icon-bg: #fff1f0"
|
||||
>
|
||||
<IconifyIcon icon="solar:clock-circle-bold-duotone" />
|
||||
</div>
|
||||
@@ -177,9 +175,7 @@ defineExpose({ refresh: loadStats });
|
||||
<div class="stats-content">
|
||||
<div
|
||||
class="stats-icon"
|
||||
style="
|
||||
|
||||
--icon-color: #1677ff; --icon-bg: #e6f4ff"
|
||||
style="--icon-color: #1677ff; --icon-bg: #e6f4ff"
|
||||
>
|
||||
<IconifyIcon icon="solar:play-circle-bold-duotone" />
|
||||
</div>
|
||||
@@ -201,9 +197,7 @@ defineExpose({ refresh: loadStats });
|
||||
<div class="stats-content">
|
||||
<div
|
||||
class="stats-icon"
|
||||
style="
|
||||
|
||||
--icon-color: #52c41a; --icon-bg: #f6ffed"
|
||||
style="--icon-color: #52c41a; --icon-bg: #f6ffed"
|
||||
>
|
||||
<IconifyIcon icon="solar:check-circle-bold-duotone" />
|
||||
</div>
|
||||
@@ -223,9 +217,7 @@ defineExpose({ refresh: loadStats });
|
||||
<div class="stats-content">
|
||||
<div
|
||||
class="stats-icon"
|
||||
style="
|
||||
|
||||
--icon-color: #722ed1; --icon-bg: #f9f0ff"
|
||||
style="--icon-color: #722ed1; --icon-bg: #f9f0ff"
|
||||
>
|
||||
<IconifyIcon
|
||||
icon="solar:users-group-two-rounded-bold-duotone"
|
||||
|
||||
Reference in New Issue
Block a user