diff --git a/apps/web-antd/src/api/aiot/alarm/index.ts b/apps/web-antd/src/api/aiot/alarm/index.ts index 0c32d7ddb..a90a19f12 100644 --- a/apps/web-antd/src/api/aiot/alarm/index.ts +++ b/apps/web-antd/src/api/aiot/alarm/index.ts @@ -1,11 +1,12 @@ import type { PageParam, PageResult } from '@vben/request'; -import { requestClient } from '#/api/request'; +import { wvpRequestClient } from '#/api/aiot/request'; export namespace AiotAlarmApi { /** AI 告警 VO */ export interface Alert { id?: number | string; + alertId?: string; alarmId?: string; alertNo?: string; cameraId?: string; @@ -23,6 +24,7 @@ export namespace AiotAlarmApi { triggerTime?: string; message?: string; bbox?: string; + imagePath?: string; snapshotUrl?: string; ossUrl?: string; status?: string; @@ -34,9 +36,11 @@ export namespace AiotAlarmApi { lastFrameTime?: string; workOrderId?: number; aiAnalysis?: Record; + receivedAt?: string; createdAt?: string; updatedAt?: string; logInfo?: AlertLogInfo; + extraData?: string; } /** 告警日志信息 */ @@ -74,20 +78,20 @@ export namespace AiotAlarmApi { } } -// ==================== 告警管理 API ==================== +// ==================== 告警管理 API(WVP 后端)==================== /** 分页查询告警列表 */ export function getAlertPage(params: PageParam) { - return requestClient.get>( - '/aiot/alarm/alert/page', - { params }, + return wvpRequestClient.get>( + '/aiot/device/alert/list', + { params: { page: params.pageNo, count: params.pageSize, ...params } }, ); } /** 获取告警详情 */ export function getAlert(id: number | string) { - return requestClient.get( - `/aiot/alarm/alert/get?id=${id}`, + return wvpRequestClient.get( + `/aiot/device/alert/${id}`, ); } @@ -97,20 +101,22 @@ export function handleAlert( status: string, remark?: string, ) { - return requestClient.put('/aiot/alarm/alert/handle', null, { + return wvpRequestClient.put('/aiot/device/alert/handle', null, { params: { id, status, remark }, }); } /** 删除告警 */ export function deleteAlert(id: number | string) { - return requestClient.delete(`/aiot/alarm/alert/delete?id=${id}`); + return wvpRequestClient.delete('/aiot/device/alert/delete', { + params: { alertId: id }, + }); } /** 获取告警统计 */ export function getAlertStatistics(startTime?: string, endTime?: string) { - return requestClient.get( - '/aiot/alarm/alert/statistics', + return wvpRequestClient.get( + '/aiot/device/alert/statistics', { params: { startTime, endTime } }, ); } @@ -119,8 +125,8 @@ export function getAlertStatistics(startTime?: string, endTime?: string) { /** 以摄像头维度获取告警汇总 */ export function getCameraAlertSummary(params: PageParam) { - return requestClient.get>( - '/aiot/alarm/device-summary/page', + return wvpRequestClient.get>( + '/aiot/device/alert/statistics', { params }, ); } diff --git a/apps/web-antd/src/api/aiot/device/index.ts b/apps/web-antd/src/api/aiot/device/index.ts index b25fcd0a0..10553bbe0 100644 --- a/apps/web-antd/src/api/aiot/device/index.ts +++ b/apps/web-antd/src/api/aiot/device/index.ts @@ -188,16 +188,28 @@ export function deleteRoi(roiId: string) { /** * 获取摄像头截图 URL - * 截图接口需要认证,通过 query param 传递 access-token + * + * 非 force 模式:直接返回 /snap/image 代理 URL(从 DB 读取持久化截图,不触发 Edge) + * force 模式:先触发 Edge 截新图(更新 DB),再返回代理 URL */ export async function getSnapUrl(cameraCode: string, force = false): Promise { const token = await getWvpToken(); + if (force) { + // force 时先触发一次截图请求(确保 Edge 截新图并更新 DB) + try { + await wvpRequestClient.get('/aiot/device/roi/snap', { + params: { cameraCode, force: true }, + }); + } catch { + /* 截图请求可能超时,但 DB 会被更新 */ + } + } + // 返回代理 URL(WVP 从 DB 读 cos_key → 生成 presigned URL → 下载返回) return ( - `${apiURL}/aiot/device/roi/snap` + + `${apiURL}/aiot/device/roi/snap/image` + `?cameraCode=${encodeURIComponent(cameraCode)}` + - `&force=${force}` + `&access-token=${encodeURIComponent(token)}` + - `&t=${Date.now()}` + (force ? `&t=${Date.now()}` : '') ); } @@ -256,3 +268,19 @@ export function exportConfig(cameraId: string) { { params: { cameraId } }, ); } + +// ==================== 告警图片代理 ==================== + +/** + * 构造告警图片代理 URL(通过 WVP 下载 COS 图片后返回字节流) + * @param imagePath COS 对象路径或完整 URL + */ +export async function getAlertImageUrl(imagePath: string): Promise { + if (!imagePath) return ''; + const token = await getWvpToken(); + return ( + `${apiURL}/aiot/device/alert/image` + + `?imagePath=${encodeURIComponent(imagePath)}` + + `&access-token=${encodeURIComponent(token)}` + ); +} diff --git a/apps/web-antd/src/views/aiot/alarm/list/index.vue b/apps/web-antd/src/views/aiot/alarm/list/index.vue index 13dfb88c7..09874d144 100644 --- a/apps/web-antd/src/views/aiot/alarm/list/index.vue +++ b/apps/web-antd/src/views/aiot/alarm/list/index.vue @@ -2,7 +2,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { AiotAlarmApi } from '#/api/aiot/alarm'; -import { h, ref } from 'vue'; +import { h, onMounted, ref } from 'vue'; import { Page } from '@vben/common-ui'; @@ -10,6 +10,7 @@ import { Button, Image, message, Modal, Tag } from 'ant-design-vue'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; import { getAlert, getAlertPage, handleAlert } from '#/api/aiot/alarm'; +import { getAlertImageUrl } from '#/api/aiot/device'; import { ALERT_LEVEL_OPTIONS, @@ -21,6 +22,21 @@ import { defineOptions({ name: 'AiotAlarmList' }); +/** 告警图片 URL 缓存(imagePath → 代理 URL) */ +const imageUrlCache = ref>({}); + +/** 获取告警图片代理 URL(异步构造后缓存) */ +function getImageProxyUrl(row: AiotAlarmApi.Alert): string { + const imagePath = row.imagePath || row.ossUrl || row.snapshotUrl; + if (!imagePath) return ''; + if (imageUrlCache.value[imagePath]) return imageUrlCache.value[imagePath]!; + // 异步构造并缓存 + getAlertImageUrl(imagePath).then((url) => { + imageUrlCache.value[imagePath] = url; + }); + return ''; +} + /** 格式化持续时长(毫秒 → 可读文本) */ function formatDuration(ms: number | null | undefined): string { // 当 duration_ms 为 null 时,说明告警仍在进行中 @@ -247,11 +263,11 @@ const [Grid, gridApi] = useVbenVxeGrid({