feat(aiot): 告警截图展示 + 全局配置同步 + API兼容修复
- 告警列表新增截图缩略图列,支持预览大图 - 告警详情显示截图 URL 链接 - 摄像头管理页新增「同步全局配置」按钮 - 告警 API 路径修正: camera-summary → device-summary - 告警 ID 兼容 alarmId 字符串格式 - Vite 代理新增 /uploads、/captures、/aiot/storage 路由 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,8 @@ import { requestClient } from '#/api/request';
|
||||
export namespace AiotAlarmApi {
|
||||
/** AI 告警 VO */
|
||||
export interface Alert {
|
||||
id?: number;
|
||||
id?: number | string;
|
||||
alarmId?: string;
|
||||
alertNo?: string;
|
||||
cameraId?: string;
|
||||
cameraName?: string;
|
||||
@@ -81,21 +82,25 @@ export function getAlertPage(params: PageParam) {
|
||||
}
|
||||
|
||||
/** 获取告警详情 */
|
||||
export function getAlert(id: number) {
|
||||
export function getAlert(id: number | string) {
|
||||
return requestClient.get<AiotAlarmApi.Alert>(
|
||||
`/aiot/alarm/alert/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 处理告警 */
|
||||
export function handleAlert(id: number, status: string, remark?: string) {
|
||||
export function handleAlert(
|
||||
id: number | string,
|
||||
status: string,
|
||||
remark?: string,
|
||||
) {
|
||||
return requestClient.put('/aiot/alarm/alert/handle', null, {
|
||||
params: { id, status, remark },
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除告警 */
|
||||
export function deleteAlert(id: number) {
|
||||
export function deleteAlert(id: number | string) {
|
||||
return requestClient.delete(`/aiot/alarm/alert/delete?id=${id}`);
|
||||
}
|
||||
|
||||
@@ -112,7 +117,7 @@ export function getAlertStatistics(startTime?: string, endTime?: string) {
|
||||
/** 以摄像头维度获取告警汇总 */
|
||||
export function getCameraAlertSummary(params: PageParam) {
|
||||
return requestClient.get<PageResult<AiotAlarmApi.CameraAlertSummary>>(
|
||||
'/aiot/alarm/camera-summary/page',
|
||||
'/aiot/alarm/device-summary/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -244,6 +244,13 @@ export function pushConfig(cameraId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
/** 一次性推送全部配置到本地Edge */
|
||||
export function pushAllConfig() {
|
||||
return wvpRequestClient.post<Record<string, any>>(
|
||||
'/aiot/device/config/push-all',
|
||||
);
|
||||
}
|
||||
|
||||
/** 导出摄像头配置 JSON */
|
||||
export function exportConfig(cameraId: string) {
|
||||
return wvpRequestClient.get<Record<string, any>>(
|
||||
|
||||
@@ -119,6 +119,13 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
minWidth: 170,
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'snapshotUrl',
|
||||
title: '截图',
|
||||
width: 80,
|
||||
align: 'center' as const,
|
||||
slots: { default: 'snapshot' },
|
||||
},
|
||||
{
|
||||
field: 'createdAt',
|
||||
title: '创建时间',
|
||||
|
||||
@@ -84,7 +84,12 @@ function getLevelColor(level?: string) {
|
||||
/** 查看告警详情 */
|
||||
async function handleView(row: AiotAlarmApi.Alert) {
|
||||
try {
|
||||
const alert = await getAlert(row.id as number);
|
||||
const alertId = row.alarmId || row.id;
|
||||
if (!alertId) {
|
||||
message.error('告警ID为空');
|
||||
return;
|
||||
}
|
||||
const alert = await getAlert(alertId);
|
||||
currentAlert.value = alert;
|
||||
detailVisible.value = true;
|
||||
} catch (error) {
|
||||
@@ -119,7 +124,7 @@ async function handleProcess(row: AiotAlarmApi.Alert, status: string) {
|
||||
duration: 0,
|
||||
});
|
||||
try {
|
||||
await handleAlert(row.id as number, status, remark);
|
||||
await handleAlert(row.alarmId || row.id!, status, remark);
|
||||
message.success(`${statusText}成功`);
|
||||
handleRefresh();
|
||||
} catch (error) {
|
||||
@@ -217,6 +222,19 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
</Tag>
|
||||
</template>
|
||||
|
||||
<!-- 截图缩略图列 -->
|
||||
<template #snapshot="{ row }">
|
||||
<Image
|
||||
v-if="row.snapshotUrl || row.ossUrl"
|
||||
:src="row.ossUrl || row.snapshotUrl"
|
||||
:width="40"
|
||||
:height="40"
|
||||
:preview="{ src: row.ossUrl || row.snapshotUrl }"
|
||||
style="object-fit: cover; border-radius: 4px; cursor: pointer"
|
||||
/>
|
||||
<span v-else class="text-gray-400">-</span>
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
@@ -322,6 +340,16 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||
:preview="{ src: currentAlert.ossUrl || currentAlert.snapshotUrl }"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
<a
|
||||
:href="currentAlert.ossUrl || currentAlert.snapshotUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-blue-500 text-xs hover:underline"
|
||||
>
|
||||
{{ currentAlert.ossUrl || currentAlert.snapshotUrl }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 检测区域 -->
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
getCameraList,
|
||||
getMediaServerList,
|
||||
getRoiByCameraId,
|
||||
pushAllConfig,
|
||||
saveCamera,
|
||||
startCamera,
|
||||
stopCamera,
|
||||
@@ -278,6 +279,24 @@ async function handleExport(row: AiotDeviceApi.Camera) {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 同步全局配置 ====================
|
||||
|
||||
const syncing = ref(false);
|
||||
|
||||
async function handleSyncAll() {
|
||||
syncing.value = true;
|
||||
try {
|
||||
const res = await pushAllConfig();
|
||||
message.success(
|
||||
`同步完成:ROI ${res.rois ?? 0} 条,算法绑定 ${res.binds ?? 0} 条`,
|
||||
);
|
||||
} catch {
|
||||
message.error('同步全局配置失败');
|
||||
} finally {
|
||||
syncing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 分页 ====================
|
||||
|
||||
function handlePageChange(p: number, size: number) {
|
||||
@@ -326,7 +345,12 @@ onMounted(() => {
|
||||
/>
|
||||
<Button type="primary" @click="loadData">查询</Button>
|
||||
</div>
|
||||
<Button type="primary" @click="handleAdd">添加摄像头</Button>
|
||||
<Space>
|
||||
<Button type="primary" :loading="syncing" @click="handleSyncAll">
|
||||
同步全局配置
|
||||
</Button>
|
||||
<Button type="primary" @click="handleAdd">添加摄像头</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
|
||||
@@ -18,6 +18,21 @@ export default defineConfig(async () => {
|
||||
changeOrigin: true,
|
||||
target: 'http://127.0.0.1:8000',
|
||||
},
|
||||
// 告警截图静态文件 -> 告警服务 :8000
|
||||
'/uploads': {
|
||||
changeOrigin: true,
|
||||
target: 'http://127.0.0.1:8000',
|
||||
},
|
||||
// Edge 本地截图(COS 未配置时回退)-> 告警服务 :8000
|
||||
'/captures': {
|
||||
changeOrigin: true,
|
||||
target: 'http://127.0.0.1:8000',
|
||||
},
|
||||
// COS 存储相关接口 -> 告警服务 :8000
|
||||
'/admin-api/aiot/storage': {
|
||||
changeOrigin: true,
|
||||
target: 'http://127.0.0.1:8000',
|
||||
},
|
||||
|
||||
// aiot/device/* -> WVP :18080(按子路径分别 rewrite)
|
||||
// 注意:更具体的路径必须写在通配路径前面
|
||||
|
||||
Reference in New Issue
Block a user