style(@vben/web-antd): 保洁/安保工单详情组件重构及样式优化
- cleaning-detail-ext: 精简为 Descriptions 表格展示,移除冗余的工牌状态面板和作业进度 - cleaning-actions: 按钮改为 size=small 行内样式,移除 block 布局 - security-detail-ext: 事件信息改为 Descriptions 表格,统一告警截图展示 - AreaTree: 支持 title 插槽透传 - 保洁工单列表移除 PAUSED Tab Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -48,8 +48,7 @@ async function handleManualComplete() {
|
||||
<Button
|
||||
v-if="order.status === 'DISPATCHED'"
|
||||
type="primary"
|
||||
block
|
||||
class="action-btn"
|
||||
size="small"
|
||||
@click="handleVoiceNotify"
|
||||
>
|
||||
<IconifyIcon icon="solar:volume-loud-bold-duotone" class="mr-1" />
|
||||
@@ -60,14 +59,14 @@ async function handleManualComplete() {
|
||||
<template v-if="order.status === 'ARRIVED'">
|
||||
<Button
|
||||
type="primary"
|
||||
block
|
||||
class="action-btn action-btn-success"
|
||||
size="small"
|
||||
class="action-btn-success"
|
||||
@click="handleManualComplete"
|
||||
>
|
||||
<IconifyIcon icon="solar:check-circle-bold-duotone" class="mr-1" />
|
||||
手动完成
|
||||
完成
|
||||
</Button>
|
||||
<Button block class="action-btn" @click="handleVoiceNotify">
|
||||
<Button size="small" @click="handleVoiceNotify">
|
||||
<IconifyIcon icon="solar:volume-loud-bold-duotone" class="mr-1" />
|
||||
语音提醒
|
||||
</Button>
|
||||
|
||||
@@ -5,20 +5,8 @@ import type { OpsOrderCenterApi } from '#/api/ops/order-center';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Descriptions,
|
||||
Progress,
|
||||
Row,
|
||||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { CLEANING_TYPE_TEXT_MAP } from '../config';
|
||||
import { Alert, Button, Card, Descriptions } from 'ant-design-vue';
|
||||
|
||||
defineOptions({ name: 'CleaningDetailExt' });
|
||||
|
||||
@@ -31,39 +19,6 @@ const emit = defineEmits<{
|
||||
voiceNotify: [];
|
||||
}>();
|
||||
|
||||
/** 保洁扩展信息(类型安全) */
|
||||
const extInfo = computed(() => {
|
||||
return props.order.extInfo as OpsOrderCenterApi.CleaningExtInfo | undefined;
|
||||
});
|
||||
|
||||
/** 计算作业时长 */
|
||||
const workDuration = computed(() => {
|
||||
if (!extInfo.value?.arrivedTime) return 0;
|
||||
const arrived = new Date(extInfo.value.arrivedTime).getTime();
|
||||
const endTime = props.order.endTime
|
||||
? new Date(props.order.endTime).getTime()
|
||||
: Date.now();
|
||||
return Math.floor((endTime - arrived) / 60_000);
|
||||
});
|
||||
|
||||
/** 计算作业进度 */
|
||||
const workProgress = computed(() => {
|
||||
if (!extInfo.value?.expectedDuration) return 0;
|
||||
return Math.min(
|
||||
Math.round((workDuration.value / extInfo.value.expectedDuration) * 100),
|
||||
100,
|
||||
);
|
||||
});
|
||||
|
||||
/** 是否超时 */
|
||||
const isOvertime = computed(() => {
|
||||
if (props.order.status === 'COMPLETED') return false;
|
||||
return (
|
||||
extInfo.value?.expectedDuration &&
|
||||
workDuration.value > extInfo.value.expectedDuration
|
||||
);
|
||||
});
|
||||
|
||||
/** 是否显示离岗警告 */
|
||||
const showLeaveWarning = computed(() => {
|
||||
return (
|
||||
@@ -73,48 +28,20 @@ const showLeaveWarning = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
/** 获取电池颜色 */
|
||||
function getBatteryColor(level: null | number) {
|
||||
if (level === null) return '#d9d9d9';
|
||||
if (level <= 20) return '#f5222d';
|
||||
if (level <= 50) return '#fa8c16';
|
||||
return '#52c41a';
|
||||
}
|
||||
/** 保洁扩展信息 */
|
||||
const extInfo = computed(() => {
|
||||
return props.order.extInfo as OpsOrderCenterApi.CleaningExtInfo | undefined;
|
||||
});
|
||||
|
||||
/** 获取工牌状态文本 */
|
||||
function getBadgeStatusText(status: string) {
|
||||
const s = status?.toUpperCase();
|
||||
switch (s) {
|
||||
case 'BUSY': {
|
||||
return '作业中';
|
||||
}
|
||||
case 'IDLE': {
|
||||
return '空闲';
|
||||
}
|
||||
case 'OFFLINE': {
|
||||
return '离线';
|
||||
}
|
||||
case 'PAUSED': {
|
||||
return '暂停';
|
||||
}
|
||||
default: {
|
||||
return status || '未知';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isBusyStatus(status: string) {
|
||||
return status?.toUpperCase() === 'BUSY';
|
||||
}
|
||||
|
||||
function formatRelativeTime(time: string) {
|
||||
const now = Date.now();
|
||||
const target = new Date(time).getTime();
|
||||
const diff = now - target;
|
||||
if (diff < 60_000) return '刚刚';
|
||||
if (diff < 3_600_000) return `${Math.floor(diff / 60_000)} 分钟前`;
|
||||
if (diff < 86_400_000) return `${Math.floor(diff / 3_600_000)} 小时前`;
|
||||
return formatDateTime(time);
|
||||
/** 格式化秒数为可读时长 */
|
||||
function formatDuration(seconds?: number) {
|
||||
if (!seconds || seconds <= 0) return null;
|
||||
if (seconds < 60) return `${seconds} 秒`;
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainSeconds = seconds % 60;
|
||||
return remainSeconds > 0
|
||||
? `${minutes} 分 ${remainSeconds} 秒`
|
||||
: `${minutes} 分钟`;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -139,254 +66,96 @@ function formatRelativeTime(time: string) {
|
||||
</template>
|
||||
</Alert>
|
||||
|
||||
<!-- 作业进度卡片 -->
|
||||
<Card
|
||||
v-if="extInfo && ['ARRIVED', 'COMPLETED'].includes(order.status)"
|
||||
class="work-progress-card mb-3"
|
||||
>
|
||||
<!-- 保洁扩展信息卡片 -->
|
||||
<Card v-if="extInfo" class="info-card mb-3">
|
||||
<template #title>
|
||||
<div class="flex items-center gap-2">
|
||||
<IconifyIcon icon="solar:chart-2-bold-duotone" class="text-blue-500" />
|
||||
<span>作业进度</span>
|
||||
<IconifyIcon icon="solar:broom-bold-duotone" class="text-green-500" />
|
||||
<span>保洁信息</span>
|
||||
</div>
|
||||
</template>
|
||||
<Row :gutter="16" align="middle">
|
||||
<Col :span="8">
|
||||
<div class="progress-circle-wrapper">
|
||||
<Progress
|
||||
type="circle"
|
||||
:percent="workProgress"
|
||||
:stroke-color="
|
||||
isOvertime
|
||||
? '#ff4d4f'
|
||||
: workProgress >= 100
|
||||
? '#52c41a'
|
||||
: { '0%': '#1677ff', '100%': '#52c41a' }
|
||||
"
|
||||
:stroke-width="6"
|
||||
:size="100"
|
||||
>
|
||||
<template #format>
|
||||
<div class="progress-inner">
|
||||
<div
|
||||
class="progress-value"
|
||||
:style="{
|
||||
color: isOvertime
|
||||
? '#ff4d4f'
|
||||
: workProgress >= 100
|
||||
? '#52c41a'
|
||||
: '#1677ff',
|
||||
}"
|
||||
>
|
||||
{{ workProgress }}%
|
||||
</div>
|
||||
<div class="progress-label">完成度</div>
|
||||
</div>
|
||||
</template>
|
||||
</Progress>
|
||||
</div>
|
||||
</Col>
|
||||
<Col :span="16">
|
||||
<div class="work-stats-grid">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon bg-blue-50 dark:bg-blue-900/30">
|
||||
<IconifyIcon
|
||||
icon="solar:clipboard-list-bold-duotone"
|
||||
class="text-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">作业类型</div>
|
||||
<div class="stat-value">
|
||||
{{ CLEANING_TYPE_TEXT_MAP[extInfo.cleaningType!] || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon bg-green-50 dark:bg-green-900/30">
|
||||
<IconifyIcon
|
||||
icon="solar:clock-circle-bold-duotone"
|
||||
class="text-green-500"
|
||||
/>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">预计时长</div>
|
||||
<div class="stat-value">{{ extInfo.expectedDuration }} 分钟</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div
|
||||
class="stat-icon"
|
||||
:class="
|
||||
isOvertime
|
||||
? 'bg-red-50 dark:bg-red-900/30'
|
||||
: 'bg-orange-50 dark:bg-orange-900/30'
|
||||
"
|
||||
>
|
||||
<IconifyIcon
|
||||
icon="solar:stopwatch-bold-duotone"
|
||||
:class="isOvertime ? 'text-red-500' : 'text-orange-500'"
|
||||
/>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">已用时长</div>
|
||||
<div class="stat-value" :class="{ 'text-red-500': isOvertime }">
|
||||
{{ workDuration }} 分钟
|
||||
<Tag v-if="isOvertime" color="error" size="small" class="ml-1">
|
||||
超时
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon bg-purple-50 dark:bg-purple-900/30">
|
||||
<IconifyIcon
|
||||
icon="solar:hourglass-line-bold-duotone"
|
||||
class="text-purple-500"
|
||||
/>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">剩余时间</div>
|
||||
<div class="stat-value" :class="{ 'text-red-500': isOvertime }">
|
||||
{{
|
||||
isOvertime
|
||||
? `已超时 ${workDuration - (extInfo.expectedDuration || 0)} 分钟`
|
||||
: `${(extInfo.expectedDuration || 0) - workDuration} 分钟`
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Descriptions :column="3" bordered size="small" class="custom-descriptions">
|
||||
<Descriptions.Item v-if="extInfo.isAuto !== undefined" label="创建方式">
|
||||
<span class="flex items-center gap-1">
|
||||
<IconifyIcon
|
||||
icon="solar:rocket-2-bold-duotone"
|
||||
:class="extInfo.isAuto ? 'text-cyan-400' : 'text-gray-400'"
|
||||
/>
|
||||
{{ extInfo.isAuto ? '自动创建' : '手动创建' }}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item v-if="extInfo.expectedDuration" label="预计时长">
|
||||
<span class="flex items-center gap-1">
|
||||
<IconifyIcon
|
||||
icon="solar:clock-circle-bold-duotone"
|
||||
class="text-green-400"
|
||||
/>
|
||||
{{ extInfo.expectedDuration }} 分钟
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
v-if="formatDuration(extInfo.totalPauseSeconds)"
|
||||
label="累计暂停"
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
<IconifyIcon
|
||||
icon="solar:pause-circle-bold-duotone"
|
||||
class="text-yellow-500"
|
||||
/>
|
||||
{{ formatDuration(extInfo.totalPauseSeconds) }}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
v-if="formatDuration(order.responseSeconds)"
|
||||
label="响应时长"
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
<IconifyIcon
|
||||
icon="solar:stopwatch-bold-duotone"
|
||||
class="text-blue-400"
|
||||
/>
|
||||
{{ formatDuration(order.responseSeconds) }}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
v-if="formatDuration(order.completionSeconds)"
|
||||
label="完成耗时"
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
<IconifyIcon
|
||||
icon="solar:hourglass-line-bold-duotone"
|
||||
class="text-purple-400"
|
||||
/>
|
||||
{{ formatDuration(order.completionSeconds) }}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
|
||||
<!-- 保洁专有基础信息字段 -->
|
||||
<template v-if="extInfo?.arrivedTime">
|
||||
<Descriptions.Item label="到岗时间">
|
||||
<span class="flex items-center gap-1">
|
||||
<IconifyIcon
|
||||
icon="solar:user-rounded-bold-duotone"
|
||||
class="text-green-400"
|
||||
/>
|
||||
{{ formatDateTime(extInfo.arrivedTime) }}
|
||||
</span>
|
||||
</Descriptions.Item>
|
||||
</template>
|
||||
<template v-if="extInfo?.cleaningType">
|
||||
<Descriptions.Item label="作业类型">
|
||||
<span>{{ CLEANING_TYPE_TEXT_MAP[extInfo.cleaningType] }}</span>
|
||||
</Descriptions.Item>
|
||||
</template>
|
||||
|
||||
<!-- 工牌状态面板(右侧面板内容) -->
|
||||
<template v-if="badgeStatus">
|
||||
<div class="badge-status-panel">
|
||||
<div class="panel-header">
|
||||
<IconifyIcon
|
||||
icon="solar:bluetooth-wave-bold-duotone"
|
||||
class="text-blue-500"
|
||||
/>
|
||||
<span>工牌实时状态</span>
|
||||
<div class="pulse-dot"></div>
|
||||
</div>
|
||||
<div class="badge-stats">
|
||||
<div class="badge-stat-item">
|
||||
<div class="badge-stat-icon">
|
||||
<IconifyIcon
|
||||
icon="solar:map-point-wave-bold-duotone"
|
||||
:class="
|
||||
badgeStatus.isInArea ? 'text-green-500' : 'text-orange-500'
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="badge-stat-content">
|
||||
<div class="badge-stat-label">位置状态</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Tag
|
||||
:color="badgeStatus.isInArea ? 'success' : 'warning'"
|
||||
size="small"
|
||||
>
|
||||
{{ badgeStatus.isInArea ? '在区域内' : '已离开' }}
|
||||
</Tag>
|
||||
<span v-if="badgeStatus.areaName" class="text-xs text-gray-500">
|
||||
{{ badgeStatus.areaName }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="badge-stat-item">
|
||||
<div class="badge-stat-icon">
|
||||
<IconifyIcon
|
||||
icon="solar:battery-charge-bold-duotone"
|
||||
:style="{ color: getBatteryColor(badgeStatus.batteryLevel) }"
|
||||
/>
|
||||
</div>
|
||||
<div class="badge-stat-content">
|
||||
<div class="badge-stat-label">电池电量</div>
|
||||
<div v-if="badgeStatus.batteryLevel != null">
|
||||
<div class="battery-bar">
|
||||
<div
|
||||
class="battery-fill"
|
||||
:style="{
|
||||
width: `${badgeStatus.batteryLevel}%`,
|
||||
backgroundColor: getBatteryColor(badgeStatus.batteryLevel),
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<span
|
||||
class="battery-text"
|
||||
:style="{ color: getBatteryColor(badgeStatus.batteryLevel) }"
|
||||
>
|
||||
{{ badgeStatus.batteryLevel }}%
|
||||
</span>
|
||||
</div>
|
||||
<span v-else class="text-xs text-gray-400">未知</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="badge-stat-item">
|
||||
<div class="badge-stat-icon">
|
||||
<IconifyIcon
|
||||
icon="solar:wifi-router-bold-duotone"
|
||||
class="text-blue-500"
|
||||
/>
|
||||
</div>
|
||||
<div class="badge-stat-content">
|
||||
<div class="badge-stat-label">信号强度</div>
|
||||
<div v-if="badgeStatus.rssi != null" class="signal-strength">
|
||||
<div
|
||||
v-for="i in 4"
|
||||
:key="i"
|
||||
class="signal-bar"
|
||||
:class="{ active: badgeStatus.rssi > -70 + (i - 1) * 15 }"
|
||||
></div>
|
||||
<span class="signal-value">{{ badgeStatus.rssi }} dBm</span>
|
||||
</div>
|
||||
<span v-else class="text-xs text-gray-400">未知</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="badge-stat-item">
|
||||
<div class="badge-stat-icon">
|
||||
<IconifyIcon
|
||||
icon="solar:pulse-2-bold-duotone"
|
||||
class="text-green-500"
|
||||
/>
|
||||
</div>
|
||||
<div class="badge-stat-content">
|
||||
<div class="badge-stat-label">最后心跳</div>
|
||||
<div class="heartbeat-time">
|
||||
{{ formatRelativeTime(badgeStatus.lastHeartbeatTime) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 执行人卡片中工牌状态Tag -->
|
||||
<Tag
|
||||
:color="isBusyStatus(badgeStatus.status) ? 'processing' : 'default'"
|
||||
class="status-badge"
|
||||
>
|
||||
{{ getBadgeStatusText(badgeStatus.status) }}
|
||||
</Tag>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 离岗警告 */
|
||||
.leave-warning {
|
||||
border-left: 3px solid #faad14;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.leave-warning :deep(.ant-alert-message) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.leave-warning :deep(.ant-alert-description) {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* 表格样式与基础信息统一 */
|
||||
.info-card :deep(.ant-descriptions-item-label) {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.info-card :deep(.ant-descriptions-item-content) {
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -55,7 +55,6 @@ const tabCounts = ref<Record<string, number>>({
|
||||
ALL: 0,
|
||||
PENDING: 0,
|
||||
IN_PROGRESS: 0,
|
||||
PAUSED: 0,
|
||||
COMPLETED: 0,
|
||||
CANCELLED: 0,
|
||||
});
|
||||
|
||||
@@ -194,7 +194,11 @@ onMounted(loadTree);
|
||||
}"
|
||||
@select="handleSelect"
|
||||
@check="handleCheck"
|
||||
/>
|
||||
>
|
||||
<template v-if="$slots.title" #title="nodeData">
|
||||
<slot name="title" v-bind="nodeData" />
|
||||
</template>
|
||||
</Tree>
|
||||
<div v-else-if="!loading" class="py-4 text-center text-gray-500">
|
||||
暂无区域数据
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { computed } from 'vue';
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { Card, Divider, Image, Tag } from 'ant-design-vue';
|
||||
import { Card, Descriptions, Image, Tag } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
SECURITY_ALARM_TYPE_COLOR_MAP,
|
||||
@@ -44,22 +44,8 @@ const alarmImages = computed(() => {
|
||||
|
||||
<template>
|
||||
<div v-if="extInfo" class="security-detail-ext">
|
||||
<!-- 工单描述 -->
|
||||
<Card v-if="order.description" class="mb-3">
|
||||
<template #title>
|
||||
<div class="flex items-center gap-2">
|
||||
<IconifyIcon
|
||||
icon="solar:document-text-bold-duotone"
|
||||
class="text-blue-500"
|
||||
/>
|
||||
<span>工单描述</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="desc-content">{{ order.description }}</div>
|
||||
</Card>
|
||||
|
||||
<!-- 事件信息 + 告警图片 -->
|
||||
<Card class="mb-3">
|
||||
<Card class="info-card mb-3">
|
||||
<template #title>
|
||||
<div class="flex items-center gap-2">
|
||||
<IconifyIcon
|
||||
@@ -70,10 +56,8 @@ const alarmImages = computed(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 基本信息行 -->
|
||||
<div class="event-meta">
|
||||
<div v-if="extInfo.alarmType" class="meta-item">
|
||||
<span class="meta-label">告警类型</span>
|
||||
<Descriptions :column="3" bordered size="small" class="custom-descriptions">
|
||||
<Descriptions.Item v-if="extInfo.alarmType" label="告警类型">
|
||||
<Tag
|
||||
:color="
|
||||
SECURITY_ALARM_TYPE_COLOR_MAP[extInfo.alarmType] || '#8c8c8c'
|
||||
@@ -83,57 +67,47 @@ const alarmImages = computed(() => {
|
||||
SECURITY_ALARM_TYPE_MAP[extInfo.alarmType] || extInfo.alarmType
|
||||
}}
|
||||
</Tag>
|
||||
</div>
|
||||
<div v-if="extInfo.cameraId" class="meta-item">
|
||||
<span class="meta-label">摄像头</span>
|
||||
<code class="meta-code">{{ extInfo.cameraId }}</code>
|
||||
</div>
|
||||
<div v-if="extInfo.alarmId" class="meta-item">
|
||||
<span class="meta-label">告警ID</span>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item v-if="extInfo.alarmId" label="告警ID">
|
||||
<code class="meta-code">{{ extInfo.alarmId }}</code>
|
||||
</div>
|
||||
<div v-if="extInfo.assignedUserName" class="meta-item">
|
||||
<span class="meta-label">处理人</span>
|
||||
<span>{{ extInfo.assignedUserName }}</span>
|
||||
</div>
|
||||
<div v-if="extInfo.dispatchedTime" class="meta-item">
|
||||
<span class="meta-label">派单时间</span>
|
||||
<span>{{ formatDateTime(extInfo.dispatchedTime) }}</span>
|
||||
</div>
|
||||
<div v-if="extInfo.confirmedTime" class="meta-item">
|
||||
<span class="meta-label">确认时间</span>
|
||||
<span>{{ formatDateTime(extInfo.confirmedTime) }}</span>
|
||||
</div>
|
||||
<div v-if="extInfo.completedTime" class="meta-item">
|
||||
<span class="meta-label">完成时间</span>
|
||||
<span>{{ formatDateTime(extInfo.completedTime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 告警截图 -->
|
||||
<div v-if="alarmImages.length > 0" class="alarm-images-section">
|
||||
<Divider class="my-3" />
|
||||
<div class="section-label mb-2">
|
||||
<IconifyIcon
|
||||
icon="solar:camera-bold-duotone"
|
||||
class="mr-1 text-gray-500"
|
||||
/>
|
||||
告警截图
|
||||
</div>
|
||||
<div class="image-gallery">
|
||||
<Image.PreviewGroup>
|
||||
<Image
|
||||
v-for="(url, idx) in alarmImages"
|
||||
:key="idx"
|
||||
:src="url"
|
||||
:alt="`告警截图 ${idx + 1}`"
|
||||
class="gallery-image"
|
||||
width="100%"
|
||||
:style="{ maxHeight: '360px', objectFit: 'contain' }"
|
||||
/>
|
||||
</Image.PreviewGroup>
|
||||
</div>
|
||||
</div>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item v-if="extInfo.cameraId" label="摄像头">
|
||||
<code class="meta-code">{{ extInfo.cameraId }}</code>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item v-if="extInfo.roiId" label="ROI区域">
|
||||
<code class="meta-code">{{ extInfo.roiId }}</code>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item v-if="extInfo.assignedUserName" label="处理人">
|
||||
{{ extInfo.assignedUserName }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item v-if="extInfo.dispatchedTime" label="派单时间">
|
||||
{{ formatDateTime(extInfo.dispatchedTime) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item v-if="extInfo.confirmedTime" label="确认时间">
|
||||
{{ formatDateTime(extInfo.confirmedTime) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item v-if="extInfo.completedTime" label="完成时间">
|
||||
{{ formatDateTime(extInfo.completedTime) }}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item
|
||||
v-if="alarmImages.length > 0"
|
||||
label="告警截图"
|
||||
:span="3"
|
||||
>
|
||||
<div class="image-gallery">
|
||||
<Image.PreviewGroup>
|
||||
<Image
|
||||
v-for="(url, idx) in alarmImages"
|
||||
:key="idx"
|
||||
:src="url"
|
||||
:alt="`告警截图 ${idx + 1}`"
|
||||
class="gallery-image"
|
||||
:style="{ maxHeight: '360px', objectFit: 'contain' }"
|
||||
/>
|
||||
</Image.PreviewGroup>
|
||||
</div>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
</Card>
|
||||
|
||||
<!-- 处理结果 -->
|
||||
@@ -187,42 +161,7 @@ const alarmImages = computed(() => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 工单描述 */
|
||||
.desc-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
color: rgb(0 0 0 / 85%);
|
||||
overflow-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
:deep(.dark) .desc-content {
|
||||
color: rgb(255 255 255 / 85%);
|
||||
}
|
||||
|
||||
/* 事件信息元数据 */
|
||||
.event-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px 32px;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
color: rgb(0 0 0 / 45%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:deep(.dark) .meta-label {
|
||||
color: rgb(255 255 255 / 45%);
|
||||
}
|
||||
|
||||
/* 代码标签 */
|
||||
.meta-code {
|
||||
padding: 1px 6px;
|
||||
font-size: 12px;
|
||||
@@ -285,4 +224,15 @@ const alarmImages = computed(() => {
|
||||
:deep(.dark) .result-content {
|
||||
color: rgb(255 255 255 / 85%);
|
||||
}
|
||||
|
||||
/* 表格样式与基础信息统一 */
|
||||
.info-card :deep(.ant-descriptions-item-label) {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.info-card :deep(.ant-descriptions-item-content) {
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user