2026-02-09 10:25:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* AIoT 设备管理 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/*
|
|
|
|
|
|
*/
|
2026-02-08 23:24:12 +08:00
|
|
|
|
import { useAppConfig } from '@vben/hooks';
|
2026-02-06 16:40:26 +08:00
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
import { getWvpToken, wvpRequestClient } from '#/api/aiot/request';
|
2026-02-06 16:40:26 +08:00
|
|
|
|
|
2026-02-08 23:24:12 +08:00
|
|
|
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
// ==================== 类型定义 ====================
|
|
|
|
|
|
|
2026-02-06 16:40:26 +08:00
|
|
|
|
export namespace AiotDeviceApi {
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** 分页响应结构 */
|
|
|
|
|
|
export interface PageResult<T> {
|
|
|
|
|
|
list: T[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 摄像头(拉流代理) */
|
|
|
|
|
|
export interface Camera {
|
|
|
|
|
|
id?: number;
|
|
|
|
|
|
type?: string; // 'default' | 'ffmpeg'
|
|
|
|
|
|
app?: string;
|
|
|
|
|
|
stream?: string;
|
2026-02-13 11:24:31 +08:00
|
|
|
|
cameraCode?: string; // 摄像头唯一编码
|
2026-03-19 11:42:17 +08:00
|
|
|
|
cameraName?: string; // 摄像头名称(用户自定义)
|
2026-03-22 01:08:03 +08:00
|
|
|
|
edgeDeviceId?: string; // 绑定的边缘设备ID
|
2026-02-09 10:25:03 +08:00
|
|
|
|
srcUrl?: string;
|
|
|
|
|
|
timeout?: number;
|
|
|
|
|
|
rtspType?: string; // '0'=TCP, '1'=UDP, '2'=Multicast
|
|
|
|
|
|
enable?: boolean;
|
|
|
|
|
|
enableAudio?: boolean;
|
|
|
|
|
|
enableMp4?: boolean;
|
|
|
|
|
|
enableDisableNoneReader?: boolean;
|
|
|
|
|
|
relatesMediaServerId?: string;
|
|
|
|
|
|
ffmpegCmdKey?: string;
|
|
|
|
|
|
pulling?: boolean;
|
|
|
|
|
|
mediaServerId?: string;
|
|
|
|
|
|
streamKey?: string;
|
|
|
|
|
|
createTime?: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 16:40:26 +08:00
|
|
|
|
/** ROI 区域 */
|
|
|
|
|
|
export interface Roi {
|
|
|
|
|
|
id?: number;
|
2026-02-08 23:24:12 +08:00
|
|
|
|
roiId?: string;
|
|
|
|
|
|
cameraId?: string;
|
|
|
|
|
|
deviceId?: string;
|
2026-02-06 16:40:26 +08:00
|
|
|
|
name?: string;
|
2026-02-09 10:25:03 +08:00
|
|
|
|
roiType?: string; // 'rectangle' | 'polygon'
|
2026-02-08 23:24:12 +08:00
|
|
|
|
coordinates?: string;
|
|
|
|
|
|
color?: string;
|
|
|
|
|
|
priority?: number;
|
|
|
|
|
|
enabled?: number; // 0 | 1
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
algorithms?: RoiAlgoBinding[];
|
2026-02-06 16:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** ROI 算法绑定(详情嵌套结构) */
|
2026-02-08 23:24:12 +08:00
|
|
|
|
export interface RoiAlgoBinding {
|
|
|
|
|
|
bind: AlgoBind;
|
|
|
|
|
|
algorithm?: Algorithm;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 算法绑定记录 */
|
|
|
|
|
|
export interface AlgoBind {
|
|
|
|
|
|
bindId?: string;
|
|
|
|
|
|
roiId?: string;
|
|
|
|
|
|
algoCode?: string;
|
2026-02-09 10:25:03 +08:00
|
|
|
|
enabled?: number; // 0 | 1
|
2026-02-08 23:24:12 +08:00
|
|
|
|
params?: string;
|
2026-02-06 16:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** 算法定义 */
|
2026-02-06 16:40:26 +08:00
|
|
|
|
export interface Algorithm {
|
|
|
|
|
|
id?: number;
|
2026-02-08 23:24:12 +08:00
|
|
|
|
algoCode?: string;
|
|
|
|
|
|
algoName?: string;
|
2026-02-06 16:40:26 +08:00
|
|
|
|
description?: string;
|
2026-02-08 23:24:12 +08:00
|
|
|
|
isActive?: boolean;
|
|
|
|
|
|
paramSchema?: string;
|
2026-02-06 16:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** 媒体服务器 */
|
|
|
|
|
|
export interface MediaServer {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
ip: string;
|
2026-02-06 16:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
// ==================== 摄像头管理 ====================
|
2026-02-08 23:24:12 +08:00
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** 摄像头列表(分页) */
|
2026-02-08 23:24:12 +08:00
|
|
|
|
export function getCameraList(params: {
|
|
|
|
|
|
page: number;
|
|
|
|
|
|
count: number;
|
|
|
|
|
|
query?: string;
|
|
|
|
|
|
pulling?: boolean;
|
|
|
|
|
|
}) {
|
2026-02-09 10:25:03 +08:00
|
|
|
|
return wvpRequestClient.get<AiotDeviceApi.PageResult<AiotDeviceApi.Camera>>(
|
|
|
|
|
|
'/aiot/device/proxy/list',
|
|
|
|
|
|
{ params },
|
|
|
|
|
|
);
|
2026-02-08 23:24:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-10 15:22:31 +08:00
|
|
|
|
/** 新增摄像头 */
|
|
|
|
|
|
export function addCamera(data: Partial<AiotDeviceApi.Camera>) {
|
|
|
|
|
|
return wvpRequestClient.post('/aiot/device/proxy/add', data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 编辑摄像头 */
|
|
|
|
|
|
export function updateCamera(data: Partial<AiotDeviceApi.Camera>) {
|
|
|
|
|
|
return wvpRequestClient.post('/aiot/device/proxy/update', data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** 保存摄像头(新增/编辑,有 id 为编辑) */
|
2026-02-09 09:51:37 +08:00
|
|
|
|
export function saveCamera(data: Partial<AiotDeviceApi.Camera>) {
|
2026-02-10 15:22:31 +08:00
|
|
|
|
return data.id ? updateCamera(data) : addCamera(data);
|
2026-02-09 09:51:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 删除摄像头 */
|
|
|
|
|
|
export function deleteCamera(id: number) {
|
|
|
|
|
|
return wvpRequestClient.delete('/aiot/device/proxy/delete', {
|
|
|
|
|
|
params: { id },
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** 开始拉流 */
|
|
|
|
|
|
export function startCamera(id: number) {
|
|
|
|
|
|
return wvpRequestClient.get('/aiot/device/proxy/start', { params: { id } });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 停止拉流 */
|
|
|
|
|
|
export function stopCamera(id: number) {
|
|
|
|
|
|
return wvpRequestClient.get('/aiot/device/proxy/stop', { params: { id } });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 在线媒体服务器列表 */
|
2026-02-09 09:51:37 +08:00
|
|
|
|
export function getMediaServerList() {
|
2026-02-09 10:25:03 +08:00
|
|
|
|
return wvpRequestClient.get<AiotDeviceApi.MediaServer[]>(
|
|
|
|
|
|
'/aiot/device/server/online/list',
|
|
|
|
|
|
);
|
2026-02-09 09:51:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 11:36:56 +08:00
|
|
|
|
/** 摄像头选项列表(用于下拉搜索选择) */
|
|
|
|
|
|
export function getCameraOptions() {
|
|
|
|
|
|
return wvpRequestClient.get<
|
|
|
|
|
|
{ cameraCode: string; cameraName: string }[]
|
|
|
|
|
|
>('/aiot/device/camera/options');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
// ==================== ROI 区域管理 ====================
|
2026-02-06 16:40:26 +08:00
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** ROI 列表(分页) */
|
2026-02-08 23:24:12 +08:00
|
|
|
|
export function getRoiList(params: {
|
|
|
|
|
|
page: number;
|
|
|
|
|
|
count: number;
|
|
|
|
|
|
cameraId?: string;
|
|
|
|
|
|
deviceId?: string;
|
|
|
|
|
|
query?: string;
|
|
|
|
|
|
}) {
|
2026-02-09 10:25:03 +08:00
|
|
|
|
return wvpRequestClient.get<AiotDeviceApi.PageResult<AiotDeviceApi.Roi>>(
|
|
|
|
|
|
'/aiot/device/roi/list',
|
|
|
|
|
|
{ params },
|
|
|
|
|
|
);
|
2026-02-08 23:24:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** ROI 详情(含算法绑定) */
|
2026-02-08 23:24:12 +08:00
|
|
|
|
export function getRoiDetail(id: number) {
|
2026-02-09 10:25:03 +08:00
|
|
|
|
return wvpRequestClient.get<AiotDeviceApi.Roi>(`/aiot/device/roi/${id}`);
|
2026-02-08 23:24:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** 某摄像头的所有 ROI */
|
2026-02-08 23:24:12 +08:00
|
|
|
|
export function getRoiByCameraId(cameraId: string) {
|
2026-02-09 10:25:03 +08:00
|
|
|
|
return wvpRequestClient.get<AiotDeviceApi.Roi[]>(
|
|
|
|
|
|
'/aiot/device/roi/channel',
|
|
|
|
|
|
{ params: { cameraId } },
|
|
|
|
|
|
);
|
2026-02-06 16:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** 保存 ROI(新增/编辑) */
|
2026-02-08 23:24:12 +08:00
|
|
|
|
export function saveRoi(data: Partial<AiotDeviceApi.Roi>) {
|
2026-02-06 16:40:26 +08:00
|
|
|
|
return wvpRequestClient.post('/aiot/device/roi/save', data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 删除 ROI */
|
2026-02-08 23:24:12 +08:00
|
|
|
|
export function deleteRoi(roiId: string) {
|
|
|
|
|
|
return wvpRequestClient.delete(`/aiot/device/roi/delete/${roiId}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 获取摄像头截图 URL
|
2026-03-04 09:21:48 +08:00
|
|
|
|
*
|
2026-03-10 16:02:41 +08:00
|
|
|
|
* 非 force 模式:直接返回 /snap/image 代理 URL(无时间戳,浏览器自动缓存)
|
|
|
|
|
|
* force 模式:先触发边缘端截图,再返回带时间戳的代理 URL 破缓存
|
2026-02-09 10:25:03 +08:00
|
|
|
|
*/
|
2026-02-28 16:27:30 +08:00
|
|
|
|
export async function getSnapUrl(cameraCode: string, force = false): Promise<string> {
|
2026-02-09 10:25:03 +08:00
|
|
|
|
const token = await getWvpToken();
|
2026-03-10 16:02:41 +08:00
|
|
|
|
if (force) {
|
|
|
|
|
|
// 强制刷新:先触发边缘端截图(等待完成)
|
|
|
|
|
|
try {
|
|
|
|
|
|
await wvpRequestClient.get('/aiot/device/roi/snap', {
|
|
|
|
|
|
params: { cameraCode, force: true },
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
// 截图请求可能超时,但 COS 上可能已有图片,继续返回代理 URL
|
|
|
|
|
|
}
|
|
|
|
|
|
// 加时间戳破浏览器缓存
|
|
|
|
|
|
return (
|
|
|
|
|
|
`${apiURL}/aiot/device/roi/snap/image` +
|
|
|
|
|
|
`?cameraCode=${encodeURIComponent(cameraCode)}` +
|
|
|
|
|
|
`&access-token=${encodeURIComponent(token)}` +
|
|
|
|
|
|
`&t=${Date.now()}`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 非 force:使用代理端点,不加时间戳,浏览器自动缓存
|
2026-02-09 10:25:03 +08:00
|
|
|
|
return (
|
2026-03-10 16:02:41 +08:00
|
|
|
|
`${apiURL}/aiot/device/roi/snap/image` +
|
2026-02-13 11:24:31 +08:00
|
|
|
|
`?cameraCode=${encodeURIComponent(cameraCode)}` +
|
2026-03-10 16:02:41 +08:00
|
|
|
|
`&access-token=${encodeURIComponent(token)}`
|
2026-02-09 10:25:03 +08:00
|
|
|
|
);
|
2026-02-06 16:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
// ==================== 算法管理 ====================
|
|
|
|
|
|
|
|
|
|
|
|
/** 算法列表 */
|
|
|
|
|
|
export function getAlgorithmList() {
|
|
|
|
|
|
return wvpRequestClient.get<AiotDeviceApi.Algorithm[]>(
|
|
|
|
|
|
'/aiot/device/algorithm/list',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 算法绑定 ====================
|
2026-02-06 16:40:26 +08:00
|
|
|
|
|
|
|
|
|
|
/** 绑定算法到 ROI */
|
2026-02-08 23:24:12 +08:00
|
|
|
|
export function bindAlgo(data: { roiId: string; algoCode: string }) {
|
2026-02-06 16:40:26 +08:00
|
|
|
|
return wvpRequestClient.post('/aiot/device/roi/bindAlgo', data);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** 解绑算法 */
|
2026-02-08 23:24:12 +08:00
|
|
|
|
export function unbindAlgo(bindId: string) {
|
|
|
|
|
|
return wvpRequestClient.delete('/aiot/device/roi/unbindAlgo', {
|
|
|
|
|
|
params: { bindId },
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** 更新算法绑定参数 */
|
2026-02-08 23:24:12 +08:00
|
|
|
|
export function updateAlgoParams(data: {
|
|
|
|
|
|
bindId: string;
|
|
|
|
|
|
params?: string;
|
|
|
|
|
|
enabled?: number;
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return wvpRequestClient.post('/aiot/device/roi/updateAlgoParams', data);
|
2026-02-06 16:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
// ==================== 配置推送 ====================
|
2026-02-08 23:24:12 +08:00
|
|
|
|
|
|
|
|
|
|
/** 推送配置到边缘端 */
|
|
|
|
|
|
export function pushConfig(cameraId: string) {
|
|
|
|
|
|
return wvpRequestClient.post('/aiot/device/config/push', null, {
|
|
|
|
|
|
params: { cameraId },
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2026-02-06 16:40:26 +08:00
|
|
|
|
|
2026-02-11 09:57:29 +08:00
|
|
|
|
/** 一次性推送全部配置到本地Edge */
|
|
|
|
|
|
export function pushAllConfig() {
|
|
|
|
|
|
return wvpRequestClient.post<Record<string, any>>(
|
|
|
|
|
|
'/aiot/device/config/push-all',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-09 10:25:03 +08:00
|
|
|
|
/** 导出摄像头配置 JSON */
|
2026-02-08 23:24:12 +08:00
|
|
|
|
export function exportConfig(cameraId: string) {
|
2026-02-09 10:25:03 +08:00
|
|
|
|
return wvpRequestClient.get<Record<string, any>>(
|
|
|
|
|
|
'/aiot/device/config/export',
|
|
|
|
|
|
{ params: { cameraId } },
|
|
|
|
|
|
);
|
2026-02-06 16:40:26 +08:00
|
|
|
|
}
|
2026-03-04 09:21:48 +08:00
|
|
|
|
|
|
|
|
|
|
// ==================== 告警图片代理 ====================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 构造告警图片代理 URL(通过 WVP 下载 COS 图片后返回字节流)
|
|
|
|
|
|
* @param imagePath COS 对象路径或完整 URL
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function getAlertImageUrl(imagePath: string): Promise<string> {
|
|
|
|
|
|
if (!imagePath) return '';
|
|
|
|
|
|
const token = await getWvpToken();
|
|
|
|
|
|
return (
|
|
|
|
|
|
`${apiURL}/aiot/device/alert/image` +
|
|
|
|
|
|
`?imagePath=${encodeURIComponent(imagePath)}` +
|
|
|
|
|
|
`&access-token=${encodeURIComponent(token)}`
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|