重构: aiot 模块重命名为 video,WVP 凭据移至环境变量

路径重命名:
- api/aiot/{alarm,device,edge,request} → api/video/{alarm,device,edge,request}
- views/aiot/{alarm,device,edge} → views/video/{alarm,device,edge}
- vite.config.mts 代理路径 /admin-api/aiot/* → /admin-api/video/*

video/request.ts 改造:
- WVP 用户名/密码 MD5 改读 import.meta.env,不再写死在源码里
- force 截图失败时补一条 console.debug,便于回溯 COS 图片加载异常

video/alarm/index.ts 顺带清理:
- 移除无调用方的重复 API getRecentAlerts(与 getAlertPage 重叠)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-04-22 23:57:44 +08:00
parent 4111c3a34f
commit 5cd86e6cf1
18 changed files with 179 additions and 174 deletions

View File

@@ -2,7 +2,7 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace AiotAlarmApi {
export namespace VideoAlarmApi {
/** AI 告警 VO */
export interface Alert {
id?: number | string;
@@ -113,16 +113,16 @@ export namespace AiotAlarmApi {
/** 分页查询告警列表 */
export function getAlertPage(params: PageParam) {
return requestClient.get<PageResult<AiotAlarmApi.Alert>>(
'/aiot/alarm/alert/page',
return requestClient.get<PageResult<VideoAlarmApi.Alert>>(
'/video/alarm/alert/page',
{ params },
);
}
/** 获取告警详情 */
export function getAlert(id: number | string) {
return requestClient.get<AiotAlarmApi.Alert>(
`/aiot/alarm/alert/get?id=${id}`,
return requestClient.get<VideoAlarmApi.Alert>(
`/video/alarm/alert/get?id=${id}`,
);
}
@@ -132,69 +132,62 @@ export function handleAlert(
status: string,
remark?: string,
) {
return requestClient.put('/aiot/alarm/alert/handle', null, {
return requestClient.put('/video/alarm/alert/handle', null, {
params: { id, status, remark },
});
}
/** 删除告警 */
export function deleteAlert(id: number | string) {
return requestClient.delete(`/aiot/alarm/alert/delete?id=${id}`);
return requestClient.delete(`/video/alarm/alert/delete?id=${id}`);
}
/** 获取看板聚合数据(单次请求) */
export function getAlertDashboard(trendDays: number = 7) {
return requestClient.get<AiotAlarmApi.DashboardData>(
'/aiot/alarm/alert/dashboard',
return requestClient.get<VideoAlarmApi.DashboardData>(
'/video/alarm/alert/dashboard',
{ params: { trendDays } },
);
}
/** 获取告警统计 */
export function getAlertStatistics(startTime?: string, endTime?: string) {
return requestClient.get<AiotAlarmApi.AlertStatistics>(
'/aiot/alarm/alert/statistics',
return requestClient.get<VideoAlarmApi.AlertStatistics>(
'/video/alarm/alert/statistics',
{ params: { startTime, endTime } },
);
}
/** 获取告警趋势 */
export function getAlertTrend(days: number = 7) {
return requestClient.get<AiotAlarmApi.TrendItem[]>(
'/aiot/alarm/alert/trend',
return requestClient.get<VideoAlarmApi.TrendItem[]>(
'/video/alarm/alert/trend',
{ params: { days } },
);
}
/** 获取设备告警排行 */
export function getAlertDeviceTop(limit: number = 10, days: number = 7) {
return requestClient.get<AiotAlarmApi.DeviceTopItem[]>(
'/aiot/alarm/alert/device-top',
return requestClient.get<VideoAlarmApi.DeviceTopItem[]>(
'/video/alarm/alert/device-top',
{ params: { limit, days } },
);
}
/** 获取24小时告警分布 */
export function getAlertHourDistribution(days: number = 7) {
return requestClient.get<AiotAlarmApi.HourDistItem[]>(
'/aiot/alarm/alert/hour-distribution',
return requestClient.get<VideoAlarmApi.HourDistItem[]>(
'/video/alarm/alert/hour-distribution',
{ params: { days } },
);
}
/** 获取最近告警列表 */
export function getRecentAlerts(pageSize: number = 10) {
return requestClient.get<any>('/aiot/alarm/alert/page', {
params: { pageNo: 1, pageSize },
});
}
// ==================== 摄像头告警汇总 API ====================
/** 以摄像头维度获取告警汇总 */
export function getCameraAlertSummary(params: PageParam) {
return requestClient.get<PageResult<AiotAlarmApi.CameraAlertSummary>>(
'/aiot/alarm/device-summary/page',
return requestClient.get<PageResult<VideoAlarmApi.CameraAlertSummary>>(
'/video/alarm/device-summary/page',
{ params },
);
}

View File

@@ -1,22 +1,22 @@
/**
* AIoT API
* Video - API
*
* wvpRequestClient WVP
* Vite rewrite
* /aiot/device/proxy/* WVP /api/proxy/*
* /aiot/device/user/* WVP /api/user/*
* /aiot/device/server/* WVP /api/server/media_server/*
* /aiot/device/* WVP /api/ai/*
* /video/device/proxy/* WVP /api/proxy/*
* /video/device/user/* WVP /api/user/*
* /video/device/server/* WVP /api/server/media_server/*
* /video/device/* WVP /api/ai/*
*/
import { useAppConfig } from '@vben/hooks';
import { getWvpToken, wvpRequestClient } from '#/api/aiot/request';
import { getWvpToken, wvpRequestClient } from '#/api/video/request';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
// ==================== 类型定义 ====================
export namespace AiotDeviceApi {
export namespace VideoDeviceApi {
/** 分页响应结构 */
export interface PageResult<T> {
list: T[];
@@ -105,48 +105,48 @@ export function getCameraList(params: {
query?: string;
pulling?: boolean;
}) {
return wvpRequestClient.get<AiotDeviceApi.PageResult<AiotDeviceApi.Camera>>(
'/aiot/device/proxy/list',
return wvpRequestClient.get<VideoDeviceApi.PageResult<VideoDeviceApi.Camera>>(
'/video/device/proxy/list',
{ params },
);
}
/** 新增摄像头 */
export function addCamera(data: Partial<AiotDeviceApi.Camera>) {
return wvpRequestClient.post('/aiot/device/proxy/add', data);
export function addCamera(data: Partial<VideoDeviceApi.Camera>) {
return wvpRequestClient.post('/video/device/proxy/add', data);
}
/** 编辑摄像头 */
export function updateCamera(data: Partial<AiotDeviceApi.Camera>) {
return wvpRequestClient.post('/aiot/device/proxy/update', data);
export function updateCamera(data: Partial<VideoDeviceApi.Camera>) {
return wvpRequestClient.post('/video/device/proxy/update', data);
}
/** 保存摄像头(新增/编辑,有 id 为编辑) */
export function saveCamera(data: Partial<AiotDeviceApi.Camera>) {
export function saveCamera(data: Partial<VideoDeviceApi.Camera>) {
return data.id ? updateCamera(data) : addCamera(data);
}
/** 删除摄像头 */
export function deleteCamera(id: number) {
return wvpRequestClient.delete('/aiot/device/proxy/delete', {
return wvpRequestClient.delete('/video/device/proxy/delete', {
params: { id },
});
}
/** 开始拉流 */
export function startCamera(id: number) {
return wvpRequestClient.get('/aiot/device/proxy/start', { params: { id } });
return wvpRequestClient.get('/video/device/proxy/start', { params: { id } });
}
/** 停止拉流 */
export function stopCamera(id: number) {
return wvpRequestClient.get('/aiot/device/proxy/stop', { params: { id } });
return wvpRequestClient.get('/video/device/proxy/stop', { params: { id } });
}
/** 在线媒体服务器列表 */
export function getMediaServerList() {
return wvpRequestClient.get<AiotDeviceApi.MediaServer[]>(
'/aiot/device/server/online/list',
return wvpRequestClient.get<VideoDeviceApi.MediaServer[]>(
'/video/device/server/online/list',
);
}
@@ -154,7 +154,7 @@ export function getMediaServerList() {
export function getCameraOptions() {
return wvpRequestClient.get<
{ cameraCode: string; cameraName: string }[]
>('/aiot/device/camera/options');
>('/video/device/camera/options');
}
// ==================== ROI 区域管理 ====================
@@ -167,33 +167,33 @@ export function getRoiList(params: {
deviceId?: string;
query?: string;
}) {
return wvpRequestClient.get<AiotDeviceApi.PageResult<AiotDeviceApi.Roi>>(
'/aiot/device/roi/list',
return wvpRequestClient.get<VideoDeviceApi.PageResult<VideoDeviceApi.Roi>>(
'/video/device/roi/list',
{ params },
);
}
/** ROI 详情(含算法绑定) */
export function getRoiDetail(id: number) {
return wvpRequestClient.get<AiotDeviceApi.Roi>(`/aiot/device/roi/${id}`);
return wvpRequestClient.get<VideoDeviceApi.Roi>(`/video/device/roi/${id}`);
}
/** 某摄像头的所有 ROI */
export function getRoiByCameraId(cameraId: string) {
return wvpRequestClient.get<AiotDeviceApi.Roi[]>(
'/aiot/device/roi/channel',
return wvpRequestClient.get<VideoDeviceApi.Roi[]>(
'/video/device/roi/channel',
{ params: { cameraId } },
);
}
/** 保存 ROI新增/编辑) */
export function saveRoi(data: Partial<AiotDeviceApi.Roi>) {
return wvpRequestClient.post('/aiot/device/roi/save', data);
export function saveRoi(data: Partial<VideoDeviceApi.Roi>) {
return wvpRequestClient.post('/video/device/roi/save', data);
}
/** 删除 ROI */
export function deleteRoi(roiId: string) {
return wvpRequestClient.delete(`/aiot/device/roi/delete/${roiId}`);
return wvpRequestClient.delete(`/video/device/roi/delete/${roiId}`);
}
/**
@@ -207,15 +207,19 @@ export async function getSnapUrl(cameraCode: string, force = false): Promise<str
if (force) {
// 强制刷新:先触发边缘端截图(等待完成)
try {
await wvpRequestClient.get('/aiot/device/roi/snap', {
await wvpRequestClient.get('/video/device/roi/snap', {
params: { cameraCode, force: true },
});
} catch {
} catch (err) {
// 截图请求可能超时,但 COS 上可能已有图片,继续返回代理 URL
// 留一条日志便于图片加载 401/404 时回溯根因
if (import.meta.env.DEV) {
console.debug('[WVP] force snap 触发失败,继续返回已有代理 URL', err);
}
}
// 加时间戳破浏览器缓存
return (
`${apiURL}/aiot/device/roi/snap/image` +
`${apiURL}/video/device/roi/snap/image` +
`?cameraCode=${encodeURIComponent(cameraCode)}` +
`&access-token=${encodeURIComponent(token)}` +
`&t=${Date.now()}`
@@ -223,7 +227,7 @@ export async function getSnapUrl(cameraCode: string, force = false): Promise<str
}
// 非 force使用代理端点不加时间戳浏览器自动缓存
return (
`${apiURL}/aiot/device/roi/snap/image` +
`${apiURL}/video/device/roi/snap/image` +
`?cameraCode=${encodeURIComponent(cameraCode)}` +
`&access-token=${encodeURIComponent(token)}`
);
@@ -233,8 +237,8 @@ export async function getSnapUrl(cameraCode: string, force = false): Promise<str
/** 算法列表 */
export function getAlgorithmList(deviceId?: string) {
return wvpRequestClient.get<AiotDeviceApi.Algorithm[]>(
'/aiot/device/algorithm/list',
return wvpRequestClient.get<VideoDeviceApi.Algorithm[]>(
'/video/device/algorithm/list',
{ params: deviceId ? { deviceId } : {} },
);
}
@@ -242,7 +246,7 @@ export function getAlgorithmList(deviceId?: string) {
/** 保存算法全局参数 */
export function saveAlgoGlobalParams(algoCode: string, globalParams: string) {
return wvpRequestClient.post(
`/aiot/device/algorithm/global-params/${algoCode}`,
`/video/device/algorithm/global-params/${algoCode}`,
{ globalParams },
);
}
@@ -250,7 +254,7 @@ export function saveAlgoGlobalParams(algoCode: string, globalParams: string) {
/** 批量更新设备算法参数 */
export function updateDeviceAlgoParams(deviceId: string, algoCode: string, params: string) {
return wvpRequestClient.post(
`/aiot/device/algorithm/device-binds/${deviceId}/${algoCode}`,
`/video/device/algorithm/device-binds/${deviceId}/${algoCode}`,
{ params },
);
}
@@ -259,12 +263,12 @@ export function updateDeviceAlgoParams(deviceId: string, algoCode: string, param
/** 绑定算法到 ROI */
export function bindAlgo(data: { roiId: string; algoCode: string }) {
return wvpRequestClient.post('/aiot/device/roi/bindAlgo', data);
return wvpRequestClient.post('/video/device/roi/bindAlgo', data);
}
/** 解绑算法 */
export function unbindAlgo(bindId: string) {
return wvpRequestClient.delete('/aiot/device/roi/unbindAlgo', {
return wvpRequestClient.delete('/video/device/roi/unbindAlgo', {
params: { bindId },
});
}
@@ -275,14 +279,14 @@ export function updateAlgoParams(data: {
params?: string;
enabled?: number;
}) {
return wvpRequestClient.post('/aiot/device/roi/updateAlgoParams', data);
return wvpRequestClient.post('/video/device/roi/updateAlgoParams', data);
}
// ==================== 配置推送 ====================
/** 推送配置到边缘端 */
export function pushConfig(cameraId: string) {
return wvpRequestClient.post('/aiot/device/config/push', null, {
return wvpRequestClient.post('/video/device/config/push', null, {
params: { cameraId },
});
}
@@ -290,14 +294,14 @@ export function pushConfig(cameraId: string) {
/** 一次性推送全部配置到本地Edge */
export function pushAllConfig() {
return wvpRequestClient.post<Record<string, any>>(
'/aiot/device/config/push-all',
'/video/device/config/push-all',
);
}
/** 导出摄像头配置 JSON */
export function exportConfig(cameraId: string) {
return wvpRequestClient.get<Record<string, any>>(
'/aiot/device/config/export',
'/video/device/config/export',
{ params: { cameraId } },
);
}
@@ -312,7 +316,7 @@ export async function getAlertImageUrl(imagePath: string): Promise<string> {
if (!imagePath) return '';
const token = await getWvpToken();
return (
`${apiURL}/aiot/device/alert/image` +
`${apiURL}/video/device/alert/image` +
`?imagePath=${encodeURIComponent(imagePath)}` +
`&access-token=${encodeURIComponent(token)}`
);

View File

@@ -1,8 +1,8 @@
import type { PageParam, PageResult } from '@vben/request';
import { wvpRequestClient } from '#/api/aiot/request';
import { wvpRequestClient } from '#/api/video/request';
export namespace AiotEdgeApi {
export namespace VideoEdgeApi {
/** 边缘设备 VO */
export interface Device {
id?: number;
@@ -32,33 +32,33 @@ export namespace AiotEdgeApi {
// ==================== 边缘设备 API ====================
// 数据源WVP 数据库 wvp_ai_edge_device 表
// 路径经 vite proxy: /admin-api/aiot/device/* → rewrite → /api/ai/* → WVP:18080
// 路径经 vite proxy: /admin-api/video/device/* → rewrite → /api/ai/* → WVP:18080
/** 获取全部边缘设备列表 */
export function getDeviceList() {
return wvpRequestClient.get<AiotEdgeApi.Device[]>(
'/aiot/device/device/list',
return wvpRequestClient.get<VideoEdgeApi.Device[]>(
'/video/device/device/list',
);
}
/** 分页查询边缘设备列表 */
export function getDevicePage(params: PageParam) {
return wvpRequestClient.get<PageResult<AiotEdgeApi.Device>>(
'/aiot/device/device/page',
return wvpRequestClient.get<PageResult<VideoEdgeApi.Device>>(
'/video/device/device/page',
{ params },
);
}
/** 获取设备详情 */
export function getDevice(deviceId: string) {
return wvpRequestClient.get<AiotEdgeApi.Device>('/aiot/device/device/get', {
return wvpRequestClient.get<VideoEdgeApi.Device>('/video/device/device/get', {
params: { deviceId },
});
}
/** 获取设备统计 */
export function getDeviceStatistics() {
return wvpRequestClient.get<AiotEdgeApi.DeviceStatistics>(
'/aiot/device/device/statistics',
return wvpRequestClient.get<VideoEdgeApi.DeviceStatistics>(
'/video/device/device/statistics',
);
}

View File

@@ -13,9 +13,17 @@ const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
// ==================== WVP Token 管理 ====================
/** WVP 默认账号(开发环境使用,生产环境应通过环境变量配置) */
const WVP_USERNAME = 'admin';
const WVP_PASSWORD_MD5 = '21232f297a57a5a743894a0e4a801fc3'; // admin 的 MD5
/**
* WVP
*
* WVP / bundle
* WVP
*
*/
const WVP_USERNAME = import.meta.env.VITE_WVP_USERNAME || 'admin';
const WVP_PASSWORD_MD5 =
import.meta.env.VITE_WVP_PASSWORD_MD5 ||
'21232f297a57a5a743894a0e4a801fc3'; // 'admin' 的 MD5仅作兜底
let wvpAccessToken: null | string = null;
let tokenPromise: null | Promise<string> = null;
@@ -23,7 +31,7 @@ let tokenPromise: null | Promise<string> = null;
/** 登录 WVP 获取 access-token */
async function loginToWvp(): Promise<string> {
const url =
`${apiURL}/aiot/device/user/login` +
`${apiURL}/video/device/user/login` +
`?username=${encodeURIComponent(WVP_USERNAME)}` +
`&password=${encodeURIComponent(WVP_PASSWORD_MD5)}`;