feat(@vben/web-antd): 新增安保工单模块,重点展示工单描述和处理结果
新增安保工单详情扩展组件和配置文件,详情页以独立卡片形式突出展示 工单描述、告警截图(支持点击预览)、处理结果描述和处理图片。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { OpsOrderCenterApi } from '#/api/ops/order-center';
|
||||
|
||||
defineOptions({ name: 'SecurityActions' });
|
||||
|
||||
defineProps<{
|
||||
order: OpsOrderCenterApi.OrderDetail;
|
||||
}>();
|
||||
|
||||
// 安保工单暂无专有操作按钮
|
||||
// 通用操作(派单、升级、取消)由详情页统一处理
|
||||
// 后续可扩展:出警、联动设备等
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div><!-- 安保专有操作预留位 --></div>
|
||||
</template>
|
||||
@@ -0,0 +1,288 @@
|
||||
<script setup lang="ts">
|
||||
import type { OpsOrderCenterApi } from '#/api/ops/order-center';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { formatDateTime } from '@vben/utils';
|
||||
|
||||
import { Card, Divider, Image, Tag } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
SECURITY_ALARM_TYPE_COLOR_MAP,
|
||||
SECURITY_ALARM_TYPE_MAP,
|
||||
} from '../config';
|
||||
|
||||
defineOptions({ name: 'SecurityDetailExt' });
|
||||
|
||||
const props = defineProps<{
|
||||
order: OpsOrderCenterApi.OrderDetail;
|
||||
}>();
|
||||
|
||||
/** 安保扩展信息(类型安全) */
|
||||
const extInfo = computed(() => {
|
||||
return props.order.extInfo as OpsOrderCenterApi.SecurityExtInfo | undefined;
|
||||
});
|
||||
|
||||
/** 解析处理结果图片 */
|
||||
const resultImages = computed(() => {
|
||||
const urls = extInfo.value?.resultImgUrls;
|
||||
if (!urls) return [];
|
||||
try {
|
||||
return JSON.parse(urls) as string[];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
/** 告警图片列表(单张也包装成数组供 Image.PreviewGroup 使用) */
|
||||
const alarmImages = computed(() => {
|
||||
const url = extInfo.value?.imageUrl;
|
||||
return url ? [url] : [];
|
||||
});
|
||||
</script>
|
||||
|
||||
<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">
|
||||
<template #title>
|
||||
<div class="flex items-center gap-2">
|
||||
<IconifyIcon
|
||||
icon="solar:shield-warning-bold-duotone"
|
||||
class="text-red-500"
|
||||
/>
|
||||
<span>事件信息</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 基本信息行 -->
|
||||
<div class="event-meta">
|
||||
<div v-if="extInfo.alarmType" class="meta-item">
|
||||
<span class="meta-label">告警类型</span>
|
||||
<Tag
|
||||
:color="
|
||||
SECURITY_ALARM_TYPE_COLOR_MAP[extInfo.alarmType] || '#8c8c8c'
|
||||
"
|
||||
>
|
||||
{{
|
||||
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>
|
||||
<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>
|
||||
</Card>
|
||||
|
||||
<!-- 处理结果 -->
|
||||
<Card v-if="extInfo.result || resultImages.length > 0" class="mb-3">
|
||||
<template #title>
|
||||
<div class="flex items-center gap-2">
|
||||
<IconifyIcon
|
||||
icon="solar:check-read-bold-duotone"
|
||||
class="text-green-500"
|
||||
/>
|
||||
<span>处理结果</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 结果描述 -->
|
||||
<div v-if="extInfo.result" class="result-content">
|
||||
{{ extInfo.result }}
|
||||
</div>
|
||||
|
||||
<!-- 结果图片 -->
|
||||
<div v-if="resultImages.length > 0" class="result-images-section">
|
||||
<Divider v-if="extInfo.result" class="my-3" />
|
||||
<div class="section-label mb-2">
|
||||
<IconifyIcon
|
||||
icon="solar:gallery-bold-duotone"
|
||||
class="mr-1 text-gray-500"
|
||||
/>
|
||||
处理图片({{ resultImages.length }}张)
|
||||
</div>
|
||||
<div class="image-gallery image-gallery--grid">
|
||||
<Image.PreviewGroup>
|
||||
<Image
|
||||
v-for="(url, idx) in resultImages"
|
||||
:key="idx"
|
||||
:src="url"
|
||||
:alt="`处理结果图片 ${idx + 1}`"
|
||||
class="gallery-image-thumb"
|
||||
:width="160"
|
||||
:height="120"
|
||||
:style="{ objectFit: 'cover' }"
|
||||
/>
|
||||
</Image.PreviewGroup>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.security-detail-ext {
|
||||
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;
|
||||
color: #1677ff;
|
||||
background: #f0f5ff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 区块标签 */
|
||||
.section-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: rgb(0 0 0 / 65%);
|
||||
}
|
||||
|
||||
:deep(.dark) .section-label {
|
||||
color: rgb(255 255 255 / 65%);
|
||||
}
|
||||
|
||||
/* 图片画廊 */
|
||||
.image-gallery {
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.image-gallery--grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.gallery-image {
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.gallery-image-thumb {
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.gallery-image-thumb:hover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
/* 处理结果 */
|
||||
.result-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
color: rgb(0 0 0 / 85%);
|
||||
overflow-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
:deep(.dark) .result-content {
|
||||
color: rgb(255 255 255 / 85%);
|
||||
}
|
||||
</style>
|
||||
15
apps/web-antd/src/views/ops/security/work-order/config.ts
Normal file
15
apps/web-antd/src/views/ops/security/work-order/config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/** 安保告警类型文本映射 */
|
||||
export const SECURITY_ALARM_TYPE_MAP: Record<string, string> = {
|
||||
intrusion: '入侵检测',
|
||||
leave_post: '离岗检测',
|
||||
fire: '火焰检测',
|
||||
fence: '电子围栏',
|
||||
};
|
||||
|
||||
/** 安保告警类型颜色映射 */
|
||||
export const SECURITY_ALARM_TYPE_COLOR_MAP: Record<string, string> = {
|
||||
intrusion: '#f5222d',
|
||||
leave_post: '#fa8c16',
|
||||
fire: '#ff4d4f',
|
||||
fence: '#faad14',
|
||||
};
|
||||
Reference in New Issue
Block a user