重构: 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 05006cb5cf
commit fd946c132e
18 changed files with 179 additions and 174 deletions

View File

@@ -37,7 +37,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
component: 'ApiSelect',
componentProps: {
api: async () => {
const { getCameraOptions } = await import('#/api/aiot/device');
const { getCameraOptions } = await import('#/api/video/device');
const list = await getCameraOptions();
return list.map((item: { cameraCode: string; cameraName: string }) => ({
label: item.cameraName,

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { AiotAlarmApi } from '#/api/aiot/alarm';
import type { VideoAlarmApi } from '#/api/video/alarm';
import { h, ref } from 'vue';
@@ -13,7 +13,7 @@ import {
getAlert,
getAlertPage,
handleAlert,
} from '#/api/aiot/alarm';
} from '#/api/video/alarm';
import {
ALERT_LEVEL_OPTIONS,
@@ -23,7 +23,7 @@ import {
useGridFormSchema,
} from './data';
defineOptions({ name: 'AiotAlarmList' });
defineOptions({ name: 'VideoAlarmList' });
/** 格式化持续时长(毫秒 → 可读文本) */
function formatDuration(ms: number | null | undefined): string {
@@ -43,7 +43,7 @@ function formatDuration(ms: number | null | undefined): string {
return minutes > 0 ? `${hours} 小时 ${minutes}` : `${hours} 小时`;
}
const currentAlert = ref<AiotAlarmApi.Alert | null>(null);
const currentAlert = ref<VideoAlarmApi.Alert | null>(null);
const detailVisible = ref(false);
/** 刷新表格 */
@@ -108,7 +108,7 @@ function getLevelColor(level?: string) {
}
/** 查看告警详情 */
async function handleView(row: AiotAlarmApi.Alert) {
async function handleView(row: VideoAlarmApi.Alert) {
try {
const alertId = row.alarmId || row.id;
if (!alertId) {
@@ -126,11 +126,11 @@ async function handleView(row: AiotAlarmApi.Alert) {
/** 处理告警 — "处理"弹窗(支持备注) */
const handleModalVisible = ref(false);
const handleModalRow = ref<AiotAlarmApi.Alert | null>(null);
const handleModalRow = ref<VideoAlarmApi.Alert | null>(null);
const handleRemark = ref('');
const handleSubmitting = ref(false);
function openHandleModal(row: AiotAlarmApi.Alert) {
function openHandleModal(row: VideoAlarmApi.Alert) {
handleModalRow.value = row;
handleRemark.value = '';
handleModalVisible.value = true;
@@ -159,7 +159,7 @@ async function submitHandle() {
}
/** 处理告警 — "误报"确认弹窗 */
function handleFalseAlarm(row: AiotAlarmApi.Alert) {
function handleFalseAlarm(row: VideoAlarmApi.Alert) {
Modal.confirm({
title: '标记误报',
content: h('div', [
@@ -225,7 +225,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
refresh: true,
search: true,
},
} as VxeTableGridOptions<AiotAlarmApi.Alert>,
} as VxeTableGridOptions<VideoAlarmApi.Alert>,
});
</script>

View File

@@ -1,4 +1,4 @@
import type { AiotAlarmApi } from '#/api/aiot/alarm';
import type { VideoAlarmApi } from '#/api/video/alarm';
const TYPE_NAMES: Record<string, string> = {
leave_post: '离岗检测',
@@ -29,7 +29,7 @@ const LEVEL_COLORS: Record<number, string> = {
};
/** 告警趋势折线图每种类型独立一条线都从0开始 */
export function getTrendChartOptions(data: AiotAlarmApi.TrendItem[]): any {
export function getTrendChartOptions(data: VideoAlarmApi.TrendItem[]): any {
const dates = data.map((d) => d.date.slice(5)); // MM-DD
const types = Object.keys(TYPE_NAMES);
@@ -106,7 +106,7 @@ export function getTypePieChartOptions(
/** 设备告警 Top10 横向条形图 */
export function getDeviceTopChartOptions(
data: AiotAlarmApi.DeviceTopItem[],
data: VideoAlarmApi.DeviceTopItem[],
): any {
const sorted = [...data].reverse(); // 最多的在上面
return {
@@ -172,7 +172,7 @@ export function getLevelBarChartOptions(
/** 24小时时段分布柱状图 */
export function getHourDistChartOptions(
data: AiotAlarmApi.HourDistItem[],
data: VideoAlarmApi.HourDistItem[],
): any {
const counts = data.map((d) => d.count);
const maxCount = Math.max(...counts, 1);

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { AiotAlarmApi } from '#/api/aiot/alarm';
import type { VideoAlarmApi } from '#/api/video/alarm';
import { nextTick, onMounted, onUnmounted, ref } from 'vue';
import { useRouter } from 'vue-router';
@@ -25,7 +25,7 @@ import {
import {
getAlertDashboard,
getAlertTrend,
} from '#/api/aiot/alarm';
} from '#/api/video/alarm';
import {
getDeviceTopChartOptions,
@@ -35,17 +35,17 @@ import {
getTypePieChartOptions,
} from './chart-options';
defineOptions({ name: 'AiotAlarmDashboard' });
defineOptions({ name: 'VideoAlarmDashboard' });
const router = useRouter();
const loading = ref(true);
//
const stats = ref<AiotAlarmApi.AlertStatistics>({});
const trendData = ref<AiotAlarmApi.TrendItem[]>([]);
const deviceTopData = ref<AiotAlarmApi.DeviceTopItem[]>([]);
const hourDistData = ref<AiotAlarmApi.HourDistItem[]>([]);
const recentAlerts = ref<AiotAlarmApi.Alert[]>([]);
const stats = ref<VideoAlarmApi.AlertStatistics>({});
const trendData = ref<VideoAlarmApi.TrendItem[]>([]);
const deviceTopData = ref<VideoAlarmApi.DeviceTopItem[]>([]);
const hourDistData = ref<VideoAlarmApi.HourDistItem[]>([]);
const recentAlerts = ref<VideoAlarmApi.Alert[]>([]);
//
const trendDays = ref(7);
@@ -162,7 +162,7 @@ function formatTime(time?: string) {
/** 跳转到告警列表 */
function goToAlertList() {
router.push('/aiot/alarm/list');
router.push('/video/alarm/list');
}
onMounted(() => {

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { AiotDeviceApi } from '#/api/aiot/device';
import type { AiotEdgeApi } from '#/api/aiot/edge';
import type { VideoDeviceApi } from '#/api/video/device';
import type { VideoEdgeApi } from '#/api/video/edge';
import { computed, onMounted, ref } from 'vue';
@@ -20,8 +20,8 @@ import {
getAlgorithmList,
saveAlgoGlobalParams,
updateDeviceAlgoParams,
} from '#/api/aiot/device';
import { getDeviceList } from '#/api/aiot/edge';
} from '#/api/video/device';
import { getDeviceList } from '#/api/video/edge';
// AlgorithmParamEditor
const paramNameMap: Record<string, string> = {
@@ -62,7 +62,7 @@ const paramDescMap: Record<string, string> = {
// ==================== ====================
const selectedDeviceId = ref<string>('');
const deviceList = ref<AiotEdgeApi.Device[]>([]);
const deviceList = ref<VideoEdgeApi.Device[]>([]);
const isDeviceMode = computed(() => selectedDeviceId.value !== '');
@@ -76,7 +76,7 @@ const saveButtonText = computed(() => {
});
// ==================== ====================
const algorithms = ref<AiotDeviceApi.Algorithm[]>([]);
const algorithms = ref<VideoDeviceApi.Algorithm[]>([]);
const activeTab = ref<string>('');
const loading = ref(false);
const saving = ref(false);
@@ -189,7 +189,7 @@ async function loadAlgorithms() {
}
}
function initFormData(algo: AiotDeviceApi.Algorithm) {
function initFormData(algo: VideoDeviceApi.Algorithm) {
const code = algo.algoCode || '';
let schema: Record<string, any> = {};
let globalParams: Record<string, any> = {};

View File

@@ -5,7 +5,7 @@
* 功能摄像头列表展示搜索筛选新增/编辑/删除拉流控制ROI 配置跳转配置导出
* 后端WVP 视频平台 StreamProxy API
*/
import type { AiotDeviceApi } from '#/api/aiot/device';
import type { VideoDeviceApi } from '#/api/video/device';
import { computed, onMounted, reactive, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
@@ -36,9 +36,9 @@ import {
getSnapUrl,
pushAllConfig,
saveCamera,
} from '#/api/aiot/device';
} from '#/api/video/device';
defineOptions({ name: 'AiotDeviceCamera' });
defineOptions({ name: 'VideoDeviceCamera' });
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
@@ -47,7 +47,7 @@ const router = useRouter();
// ==================== ====================
const loading = ref(false);
const cameraList = ref<AiotDeviceApi.Camera[]>([]);
const cameraList = ref<VideoDeviceApi.Camera[]>([]);
const roiCounts = ref<Record<string, number>>({});
const cameraStatus = ref<Record<string, boolean | null>>({});
const page = ref(1);
@@ -70,7 +70,7 @@ const editModalOpen = ref(false);
const editModalTitle = ref('添加摄像头');
const saving = ref(false);
const mediaServerOptions = ref<{ label: string; value: string }[]>([]);
const editForm = reactive<Partial<AiotDeviceApi.Camera>>({
const editForm = reactive<Partial<VideoDeviceApi.Camera>>({
id: undefined,
type: 'default',
cameraName: '',
@@ -148,7 +148,7 @@ async function loadCameraStatus() {
if (!cameraCode) continue;
// 使 fetch HEAD /snap/image
try {
const url = `${apiURL}/aiot/device/roi/snap/image?cameraCode=${encodeURIComponent(cameraCode)}`;
const url = `${apiURL}/video/device/roi/snap/image?cameraCode=${encodeURIComponent(cameraCode)}`;
const res = await fetch(url, { method: 'HEAD' });
cameraStatus.value = { ...cameraStatus.value, [cameraCode]: res.ok };
} catch {
@@ -238,7 +238,7 @@ function handleAdd() {
autoFillStreamId();
}
function handleEdit(row: AiotDeviceApi.Camera) {
function handleEdit(row: VideoDeviceApi.Camera) {
Object.assign(editForm, {
id: row.id,
type: row.type || 'default',
@@ -301,7 +301,7 @@ async function handleSave() {
// ==================== ====================
function handleDelete(row: AiotDeviceApi.Camera) {
function handleDelete(row: VideoDeviceApi.Camera) {
Modal.confirm({
title: '删除确认',
content: `确定删除摄像头 ${row.cameraName || row.cameraCode || row.stream} `,
@@ -320,9 +320,9 @@ function handleDelete(row: AiotDeviceApi.Camera) {
// ==================== ROI ====================
function handleRoiConfig(row: AiotDeviceApi.Camera) {
function handleRoiConfig(row: VideoDeviceApi.Camera) {
router.push({
path: '/aiot/device/roi',
path: '/video/device/roi',
query: {
cameraCode: row.cameraCode,
srcUrl: row.srcUrl,

View File

@@ -11,7 +11,7 @@ import {
Tag,
} from 'ant-design-vue';
import { updateAlgoParams } from '#/api/aiot/device';
import { updateAlgoParams } from '#/api/video/device';
import WorkingHoursEditor from './WorkingHoursEditor.vue';

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { AiotDeviceApi } from '#/api/aiot/device';
import type { VideoDeviceApi } from '#/api/video/device';
import { ref, watch } from 'vue';
@@ -18,7 +18,7 @@ import {
getAlgorithmList,
unbindAlgo,
updateAlgoParams,
} from '#/api/aiot/device';
} from '#/api/video/device';
import AlgorithmParamEditor from './AlgorithmParamEditor.vue';
@@ -40,7 +40,7 @@ const DEFAULT_ALARM_LEVELS: Record<string, number> = {
interface Props {
roiId: string;
bindings: AiotDeviceApi.RoiAlgoBinding[];
bindings: VideoDeviceApi.RoiAlgoBinding[];
snapOk: boolean;
}
@@ -56,7 +56,7 @@ const emit = defineEmits<{
const showAddDialog = ref(false);
const selectedAlgoCode = ref<string | undefined>(undefined);
const availableAlgorithms = ref<AiotDeviceApi.Algorithm[]>([]);
const availableAlgorithms = ref<VideoDeviceApi.Algorithm[]>([]);
const paramEditorOpen = ref(false);
const currentParamSchema = ref('{}');
const currentParams = ref('{}');
@@ -123,7 +123,7 @@ function handleUnbind(bindId: string) {
});
}
function openParamEditor(item: AiotDeviceApi.RoiAlgoBinding) {
function openParamEditor(item: VideoDeviceApi.RoiAlgoBinding) {
currentBindId.value = item.bind.bindId || '';
currentParams.value = item.bind.params || '{}';
currentParamSchema.value = item.algorithm?.paramSchema || '{}';
@@ -137,7 +137,7 @@ function openParamEditor(item: AiotDeviceApi.RoiAlgoBinding) {
paramEditorOpen.value = true;
}
async function onToggleEnabled(bind: AiotDeviceApi.AlgoBind, val: string | number | boolean) {
async function onToggleEnabled(bind: VideoDeviceApi.AlgoBind, val: string | number | boolean) {
bind.enabled = val ? 1 : 0;
try {
await updateAlgoParams({
@@ -165,7 +165,7 @@ function getAlgoFrameRate(algoCode: string): string {
}
/** 从 params JSON 中读取告警等级 */
function getAlarmLevel(item: AiotDeviceApi.RoiAlgoBinding): number {
function getAlarmLevel(item: VideoDeviceApi.RoiAlgoBinding): number {
try {
const params = JSON.parse(item.bind.params || '{}');
if (params.alarm_level !== undefined) return params.alarm_level;
@@ -174,7 +174,7 @@ function getAlarmLevel(item: AiotDeviceApi.RoiAlgoBinding): number {
}
/** 修改告警等级 */
async function onAlarmLevelChange(item: AiotDeviceApi.RoiAlgoBinding, level: number) {
async function onAlarmLevelChange(item: VideoDeviceApi.RoiAlgoBinding, level: number) {
try {
let params: Record<string, any> = {};
try {

View File

@@ -1,10 +1,10 @@
<script setup lang="ts">
import type { AiotDeviceApi } from '#/api/aiot/device';
import type { VideoDeviceApi } from '#/api/video/device';
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
interface Props {
rois: AiotDeviceApi.Roi[];
rois: VideoDeviceApi.Roi[];
drawMode: string | null;
selectedRoiId: string | null;
snapUrl: string;

View File

@@ -6,7 +6,7 @@
* 算法绑定管理配置推送到边缘端
* 后端WVP 视频平台 AiRoi / AiConfig API
*/
import type { AiotDeviceApi } from '#/api/aiot/device';
import type { VideoDeviceApi } from '#/api/video/device';
import { computed, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
@@ -34,13 +34,13 @@ import {
getSnapUrl,
pushConfig,
saveRoi,
} from '#/api/aiot/device';
import { wvpRequestClient } from '#/api/aiot/request';
} from '#/api/video/device';
import { wvpRequestClient } from '#/api/video/request';
import RoiAlgorithmBind from './components/RoiAlgorithmBind.vue';
import RoiCanvas from './components/RoiCanvas.vue';
defineOptions({ name: 'AiotDeviceRoi' });
defineOptions({ name: 'VideoDeviceRoi' });
const route = useRoute();
const router = useRouter();
@@ -48,11 +48,11 @@ const router = useRouter();
// ==================== ====================
const cameraCode = ref('');
const currentCamera = ref<AiotDeviceApi.Camera | null>(null);
const currentCamera = ref<VideoDeviceApi.Camera | null>(null);
const showCameraSelector = ref(false);
const cameraOptions = ref<
{ camera: AiotDeviceApi.Camera; label: string; value: string }[]
{ camera: VideoDeviceApi.Camera; label: string; value: string }[]
>([]);
const selectedCamera = ref<string | undefined>(undefined);
const cameraLoading = ref(false);
@@ -60,9 +60,9 @@ const cameraLoading = ref(false);
// ==================== ROI ====================
const drawMode = ref<null | string>(null);
const roiList = ref<AiotDeviceApi.Roi[]>([]);
const roiList = ref<VideoDeviceApi.Roi[]>([]);
const selectedRoiId = ref<null | string>(null);
const selectedRoiBindings = ref<AiotDeviceApi.RoiAlgoBinding[]>([]);
const selectedRoiBindings = ref<VideoDeviceApi.RoiAlgoBinding[]>([]);
const snapUrl = ref('');
const snapOk = ref(false);
const panelVisible = ref(false);
@@ -100,7 +100,7 @@ async function loadCurrentCamera() {
try {
const res = await getCameraList({ page: 1, count: 200 });
const list = res.list || [];
const camera = list.find((c: AiotDeviceApi.Camera) => c.cameraCode === cameraCode.value);
const camera = list.find((c: VideoDeviceApi.Camera) => c.cameraCode === cameraCode.value);
if (camera) {
currentCamera.value = camera;
}
@@ -114,7 +114,7 @@ async function loadCameraOptions() {
try {
const res = await getCameraList({ page: 1, count: 200 });
const list = res.list || [];
cameraOptions.value = list.map((cam: AiotDeviceApi.Camera) => ({
cameraOptions.value = list.map((cam: VideoDeviceApi.Camera) => ({
value: cam.cameraCode || '',
label: `${cam.cameraName || cam.app || cam.stream}${cam.srcUrl ? ` (${cam.srcUrl})` : ''}`,
camera: cam,
@@ -138,18 +138,18 @@ async function onCameraSelected(val: string) {
}
function goBack() {
router.push('/aiot/device/camera');
router.push('/video/device/camera');
}
// ==================== ROI ====================
function getRoiTagColor(roi: AiotDeviceApi.Roi) {
function getRoiTagColor(roi: VideoDeviceApi.Roi) {
if (roi.roiType === 'fullscreen') return 'orange';
if (roi.roiType === 'rectangle') return 'blue';
return 'green';
}
function getRoiTagLabel(roi: AiotDeviceApi.Roi) {
function getRoiTagLabel(roi: VideoDeviceApi.Roi) {
if (roi.roiType === 'fullscreen') return '全图';
if (roi.roiType === 'rectangle') return '矩形';
return '自定义';
@@ -209,7 +209,7 @@ function addFullscreen() {
title: '新建全图选区',
content: '将创建覆盖整张图片的选区',
async onOk() {
const newRoi: Partial<AiotDeviceApi.Roi> = {
const newRoi: Partial<VideoDeviceApi.Roi> = {
cameraId: cameraCode.value,
name: `全图-${roiList.value.length + 1}`,
roiType: 'fullscreen',
@@ -262,7 +262,7 @@ function onDrawCancelled() {
async function onRoiDrawn(data: { coordinates: string; roi_type: string }) {
drawMode.value = null;
const roiName = `ROI-${roiList.value.length + 1}`;
const newRoi: Partial<AiotDeviceApi.Roi> = {
const newRoi: Partial<VideoDeviceApi.Roi> = {
cameraId: cameraCode.value,
name: roiName,
roiType: data.roi_type,
@@ -292,7 +292,7 @@ function onRoiSelected(roiId: null | string) {
}
}
function selectRoi(roi: AiotDeviceApi.Roi) {
function selectRoi(roi: VideoDeviceApi.Roi) {
selectedRoiId.value = roi.roiId || null;
loadRoiDetail();
}
@@ -305,7 +305,7 @@ function closePanel() {
// ==================== ROI ====================
async function updateRoiData(roi: AiotDeviceApi.Roi) {
async function updateRoiData(roi: VideoDeviceApi.Roi) {
try {
await saveRoi(roi);
loadRois();
@@ -326,7 +326,7 @@ function onRoiDeleted(roiId: string) {
});
}
function handleDeleteRoi(roi: AiotDeviceApi.Roi) {
function handleDeleteRoi(roi: VideoDeviceApi.Roi) {
Modal.confirm({
title: '提示',
content: '确定删除该ROI关联的算法绑定也将删除。',

View File

@@ -15,15 +15,15 @@ import {
Tag,
} from 'ant-design-vue';
import { getDeviceList } from '#/api/aiot/edge';
import type { AiotEdgeApi } from '#/api/aiot/edge';
import { getDeviceList } from '#/api/video/edge';
import type { VideoEdgeApi } from '#/api/video/edge';
import { formatUptime, STATUS_CONFIG } from './data';
defineOptions({ name: 'AiotEdgeNode' });
defineOptions({ name: 'VideoEdgeNode' });
const loading = ref(false);
const devices = ref<AiotEdgeApi.Device[]>([]);
const devices = ref<VideoEdgeApi.Device[]>([]);
async function fetchDevices() {
loading.value = true;