feat(aiot): 搭建 aiot 前端模块路由和 API 层
- 新增 router/routes/modules/aiot.ts:6 个页面路由 告警列表、摄像头汇总、摄像头管理、ROI配置、实时视频、边缘节点 - 新增 api/aiot/alarm/:告警 API(分页、详情、处理、删除、统计、汇总) - 新增 api/aiot/edge/:边缘设备 API(分页、详情、统计) - 新增 api/aiot/device/:摄像头和 ROI API(调用 WVP 后端) - 新增 api/aiot/video/:视频播放 API(playStart/playStop) - 新增 api/aiot/request.ts:WVP 专用请求客户端(跳过芋道响应拦截器) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
118
apps/web-antd/src/api/aiot/alarm/index.ts
Normal file
118
apps/web-antd/src/api/aiot/alarm/index.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace AiotAlarmApi {
|
||||
/** AI 告警 VO */
|
||||
export interface Alert {
|
||||
id?: number;
|
||||
alertNo?: string;
|
||||
cameraId?: string;
|
||||
cameraName?: string;
|
||||
roiId?: string;
|
||||
bindId?: string;
|
||||
deviceId?: string;
|
||||
alertType?: string;
|
||||
alertTypeName?: string;
|
||||
algorithm?: string;
|
||||
confidence?: number;
|
||||
durationMinutes?: number;
|
||||
triggerTime?: string;
|
||||
message?: string;
|
||||
bbox?: string;
|
||||
snapshotUrl?: string;
|
||||
ossUrl?: string;
|
||||
status?: string;
|
||||
statusName?: string;
|
||||
level?: string;
|
||||
handleRemark?: string;
|
||||
handledBy?: string;
|
||||
handledAt?: string;
|
||||
workOrderId?: number;
|
||||
aiAnalysis?: Record<string, any>;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
logInfo?: AlertLogInfo;
|
||||
}
|
||||
|
||||
/** 告警日志信息 */
|
||||
export interface AlertLogInfo {
|
||||
receiveTime?: string;
|
||||
handleTime?: string;
|
||||
handledBy?: string;
|
||||
handleRemark?: string;
|
||||
confidence?: number;
|
||||
durationMinutes?: number;
|
||||
bbox?: string;
|
||||
aiAnalysis?: Record<string, any>;
|
||||
}
|
||||
|
||||
/** 摄像头告警汇总 */
|
||||
export interface CameraAlertSummary {
|
||||
cameraId?: string;
|
||||
cameraName?: string;
|
||||
totalCount?: number;
|
||||
pendingCount?: number;
|
||||
lastAlertTime?: string;
|
||||
lastAlertType?: string;
|
||||
lastAlertTypeName?: string;
|
||||
}
|
||||
|
||||
/** 告警统计 */
|
||||
export interface AlertStatistics {
|
||||
total?: number;
|
||||
todayCount?: number;
|
||||
pendingCount?: number;
|
||||
handledCount?: number;
|
||||
byType?: Record<string, number>;
|
||||
byStatus?: Record<string, number>;
|
||||
byLevel?: Record<string, number>;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 告警管理 API ====================
|
||||
|
||||
/** 分页查询告警列表 */
|
||||
export function getAlertPage(params: PageParam) {
|
||||
return requestClient.get<PageResult<AiotAlarmApi.Alert>>(
|
||||
'/aiot/alarm/alert/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 获取告警详情 */
|
||||
export function getAlert(id: number) {
|
||||
return requestClient.get<AiotAlarmApi.Alert>(
|
||||
`/aiot/alarm/alert/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 处理告警 */
|
||||
export function handleAlert(id: number, status: string, remark?: string) {
|
||||
return requestClient.put('/aiot/alarm/alert/handle', null, {
|
||||
params: { id, status, remark },
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除告警 */
|
||||
export function deleteAlert(id: number) {
|
||||
return requestClient.delete(`/aiot/alarm/alert/delete?id=${id}`);
|
||||
}
|
||||
|
||||
/** 获取告警统计 */
|
||||
export function getAlertStatistics(startTime?: string, endTime?: string) {
|
||||
return requestClient.get<AiotAlarmApi.AlertStatistics>(
|
||||
'/aiot/alarm/alert/statistics',
|
||||
{ params: { startTime, endTime } },
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== 摄像头告警汇总 API ====================
|
||||
|
||||
/** 以摄像头维度获取告警汇总 */
|
||||
export function getCameraAlertSummary(params: PageParam) {
|
||||
return requestClient.get<PageResult<AiotAlarmApi.CameraAlertSummary>>(
|
||||
'/aiot/alarm/camera-summary/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
99
apps/web-antd/src/api/aiot/device/index.ts
Normal file
99
apps/web-antd/src/api/aiot/device/index.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { wvpRequestClient } from '#/api/aiot/request';
|
||||
|
||||
export namespace AiotDeviceApi {
|
||||
/** ROI 区域 */
|
||||
export interface Roi {
|
||||
id?: number;
|
||||
channelId?: string;
|
||||
channelName?: string;
|
||||
name?: string;
|
||||
type?: string;
|
||||
points?: string;
|
||||
enabled?: boolean;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
/** ROI 算法绑定 */
|
||||
export interface RoiAlgoBind {
|
||||
id?: number;
|
||||
roiId?: number;
|
||||
algorithmId?: number;
|
||||
algorithmName?: string;
|
||||
enabled?: boolean;
|
||||
config?: Record<string, any>;
|
||||
}
|
||||
|
||||
/** 算法 */
|
||||
export interface Algorithm {
|
||||
id?: number;
|
||||
name?: string;
|
||||
code?: string;
|
||||
description?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
/** 摄像头通道 */
|
||||
export interface Channel {
|
||||
channelId?: string;
|
||||
name?: string;
|
||||
manufacturer?: string;
|
||||
status?: string;
|
||||
ptztypeText?: string;
|
||||
longitudeWgs84?: number;
|
||||
latitudeWgs84?: number;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== ROI 区域管理 API ====================
|
||||
|
||||
/** 获取 ROI 列表 */
|
||||
export function getRoiPage(params: PageParam) {
|
||||
return wvpRequestClient.get<PageResult<AiotDeviceApi.Roi>>(
|
||||
'/aiot/device/roi/list',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 保存 ROI */
|
||||
export function saveRoi(data: AiotDeviceApi.Roi) {
|
||||
return wvpRequestClient.post('/aiot/device/roi/save', data);
|
||||
}
|
||||
|
||||
/** 删除 ROI */
|
||||
export function deleteRoi(id: number) {
|
||||
return wvpRequestClient.delete(`/aiot/device/roi/delete?id=${id}`);
|
||||
}
|
||||
|
||||
// ==================== 算法绑定 API ====================
|
||||
|
||||
/** 绑定算法到 ROI */
|
||||
export function bindAlgo(data: { roiId: number; algorithmId: number }) {
|
||||
return wvpRequestClient.post('/aiot/device/roi/bindAlgo', data);
|
||||
}
|
||||
|
||||
/** 解绑算法 */
|
||||
export function unbindAlgo(bindId: number) {
|
||||
return wvpRequestClient.delete(
|
||||
`/aiot/device/roi/unbindAlgo?bindId=${bindId}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 获取算法列表 */
|
||||
export function getAlgorithmList() {
|
||||
return wvpRequestClient.get<AiotDeviceApi.Algorithm[]>(
|
||||
'/aiot/device/algorithm/list',
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== 摄像头通道 API ====================
|
||||
|
||||
/** 获取摄像头通道列表 */
|
||||
export function getChannelPage(params: PageParam) {
|
||||
return wvpRequestClient.get<PageResult<AiotDeviceApi.Channel>>(
|
||||
'/aiot/device/channel/list',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
55
apps/web-antd/src/api/aiot/edge/index.ts
Normal file
55
apps/web-antd/src/api/aiot/edge/index.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { PageParam, PageResult } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace AiotEdgeApi {
|
||||
/** 边缘设备 VO */
|
||||
export interface Device {
|
||||
id?: number;
|
||||
deviceId?: string;
|
||||
deviceName?: string;
|
||||
status?: string;
|
||||
statusName?: string;
|
||||
lastHeartbeat?: string;
|
||||
uptimeSeconds?: number;
|
||||
framesProcessed?: number;
|
||||
alertsGenerated?: number;
|
||||
ipAddress?: string;
|
||||
streamCount?: number;
|
||||
configVersion?: string;
|
||||
extraInfo?: Record<string, any>;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
/** 设备统计 */
|
||||
export interface DeviceStatistics {
|
||||
total?: number;
|
||||
online?: number;
|
||||
offline?: number;
|
||||
error?: number;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 边缘设备 API ====================
|
||||
|
||||
/** 分页查询边缘设备列表 */
|
||||
export function getDevicePage(params: PageParam) {
|
||||
return requestClient.get<PageResult<AiotEdgeApi.Device>>(
|
||||
'/aiot/edge/device/page',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/** 获取设备详情 */
|
||||
export function getDevice(id: string) {
|
||||
return requestClient.get<AiotEdgeApi.Device>(
|
||||
`/aiot/edge/device/get?id=${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 获取设备统计 */
|
||||
export function getDeviceStatistics() {
|
||||
return requestClient.get<AiotEdgeApi.DeviceStatistics>(
|
||||
'/aiot/edge/device/statistics',
|
||||
);
|
||||
}
|
||||
35
apps/web-antd/src/api/aiot/request.ts
Normal file
35
apps/web-antd/src/api/aiot/request.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* WVP 视频平台专用请求客户端
|
||||
*
|
||||
* WVP 返回原始 JSON(非芋道的 {code:0, data:...} 格式),
|
||||
* 需要跳过芋道默认的响应拦截器。
|
||||
*/
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { RequestClient } from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
|
||||
function createWvpRequestClient(baseURL: string) {
|
||||
const client = new RequestClient({
|
||||
baseURL,
|
||||
});
|
||||
|
||||
// 请求头:携带 token 用于 Vite 代理鉴权透传
|
||||
client.addRequestInterceptor({
|
||||
fulfilled: async (config) => {
|
||||
const accessStore = useAccessStore();
|
||||
const token = accessStore.accessToken;
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
config.headers['Accept-Language'] = preferences.app.locale;
|
||||
return config;
|
||||
},
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export const wvpRequestClient = createWvpRequestClient(apiURL);
|
||||
43
apps/web-antd/src/api/aiot/video/index.ts
Normal file
43
apps/web-antd/src/api/aiot/video/index.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { wvpRequestClient } from '#/api/aiot/request';
|
||||
|
||||
export namespace AiotVideoApi {
|
||||
/** 流信息 */
|
||||
export interface StreamInfo {
|
||||
app?: string;
|
||||
stream?: string;
|
||||
ip?: string;
|
||||
flv?: string;
|
||||
ws_flv?: string;
|
||||
rtmp?: string;
|
||||
hls?: string;
|
||||
rtsp?: string;
|
||||
mediaServerId?: string;
|
||||
tracks?: StreamTrack[];
|
||||
}
|
||||
|
||||
export interface StreamTrack {
|
||||
codec_id?: number;
|
||||
codec_id_name?: string;
|
||||
ready?: boolean;
|
||||
type?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
fps?: number;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 视频播放 API ====================
|
||||
|
||||
/** 开始播放 */
|
||||
export function playStart(deviceId: string, channelId: string) {
|
||||
return wvpRequestClient.get<AiotVideoApi.StreamInfo>(
|
||||
`/aiot/video/play/start/${deviceId}/${channelId}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** 停止播放 */
|
||||
export function playStop(deviceId: string, channelId: string) {
|
||||
return wvpRequestClient.get(
|
||||
`/aiot/video/play/stop/${deviceId}/${channelId}`,
|
||||
);
|
||||
}
|
||||
71
apps/web-antd/src/router/routes/modules/aiot.ts
Normal file
71
apps/web-antd/src/router/routes/modules/aiot.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/aiot',
|
||||
name: 'AIoT',
|
||||
meta: {
|
||||
title: 'AIoT 智能平台',
|
||||
icon: 'ant-design:robot-outlined',
|
||||
keepAlive: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'alarm/list',
|
||||
name: 'AiotAlarmList',
|
||||
component: () => import('#/views/aiot/alarm/list/index.vue'),
|
||||
meta: {
|
||||
title: '告警列表',
|
||||
icon: 'ant-design:alert-outlined',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'alarm/summary',
|
||||
name: 'AiotAlarmSummary',
|
||||
component: () => import('#/views/aiot/alarm/summary/index.vue'),
|
||||
meta: {
|
||||
title: '摄像头告警汇总',
|
||||
icon: 'ant-design:video-camera-outlined',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'device/camera',
|
||||
name: 'AiotDeviceCamera',
|
||||
component: () => import('#/views/aiot/device/camera/index.vue'),
|
||||
meta: {
|
||||
title: '摄像头管理',
|
||||
icon: 'ant-design:camera-outlined',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'device/roi',
|
||||
name: 'AiotDeviceRoi',
|
||||
component: () => import('#/views/aiot/device/roi/index.vue'),
|
||||
meta: {
|
||||
title: 'ROI 区域配置',
|
||||
icon: 'ant-design:aim-outlined',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'video/live',
|
||||
name: 'AiotVideoLive',
|
||||
component: () => import('#/views/aiot/video/live/index.vue'),
|
||||
meta: {
|
||||
title: '实时视频',
|
||||
icon: 'ant-design:play-circle-outlined',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'edge/node',
|
||||
name: 'AiotEdgeNode',
|
||||
component: () => import('#/views/aiot/edge/node/index.vue'),
|
||||
meta: {
|
||||
title: '边缘节点管理',
|
||||
icon: 'ant-design:cluster-outlined',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
Reference in New Issue
Block a user