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:
2026-02-06 16:40:26 +08:00
parent bc2f1e89c9
commit 159a82aaa9
6 changed files with 421 additions and 0 deletions

View 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 },
);
}

View 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 },
);
}

View 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',
);
}

View 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);

View 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}`,
);
}

View 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;