refactor(aiot-device): 规范整理摄像头管理和 ROI 配置页面代码

- 摄像头管理:代码按功能分区(列表状态/编辑弹窗/数据加载/增删改/拉流控制/配置导出)
- ROI 配置:代码按功能分区(摄像头选择/截图/数据加载/绘制/选择/编辑/删除/推送)
- getSnapUrl 适配 async 调用,截图 URL 携带 access-token 认证参数
- 统一中文注释风格

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 10:25:19 +08:00
parent eb11e7ed1f
commit f78eaa2ae1
2 changed files with 144 additions and 92 deletions

View File

@@ -1,4 +1,11 @@
<script setup lang="ts">
/**
* ROI 区域配置页面
*
* 功能摄像头截图展示、ROI 区域绘制(矩形/多边形、ROI 属性编辑、
* 算法绑定管理、配置推送到边缘端
* 后端WVP 视频平台 AiRoi / AiConfig API
*/
import type { AiotDeviceApi } from '#/api/aiot/device';
import { computed, onMounted, ref } from 'vue';
@@ -37,23 +44,24 @@ defineOptions({ name: 'AiotDeviceRoi' });
const route = useRoute();
const router = useRouter();
// Route params
// ==================== 摄像头选择 ====================
const cameraId = ref('');
const app = ref('');
const stream = ref('');
// Camera selector (when no cameraId in route)
const showCameraSelector = ref(false);
const cameraOptions = ref<
{ value: string; label: string; camera: AiotDeviceApi.Camera }[]
{ camera: AiotDeviceApi.Camera; label: string; value: string }[]
>([]);
const selectedCamera = ref<string | undefined>(undefined);
const cameraLoading = ref(false);
// ROI state
const drawMode = ref<string | null>(null);
// ==================== ROI 状态 ====================
const drawMode = ref<null | string>(null);
const roiList = ref<AiotDeviceApi.Roi[]>([]);
const selectedRoiId = ref<string | null>(null);
const selectedRoiId = ref<null | string>(null);
const selectedRoiBindings = ref<AiotDeviceApi.RoiAlgoBinding[]>([]);
const snapUrl = ref('');
@@ -62,13 +70,15 @@ const selectedRoi = computed(() => {
return roiList.value.find((r) => r.roiId === selectedRoiId.value) || null;
});
onMounted(() => {
// ==================== 初始化 ====================
onMounted(async () => {
const q = route.query;
if (q.cameraId) {
cameraId.value = String(q.cameraId);
app.value = String(q.app || '');
stream.value = String(q.stream || '');
buildSnapUrl();
await buildSnapUrl();
loadRois();
} else {
showCameraSelector.value = true;
@@ -76,6 +86,8 @@ onMounted(() => {
}
});
// ==================== 摄像头加载与选择 ====================
async function loadCameraOptions() {
cameraLoading.value = true;
try {
@@ -93,14 +105,14 @@ async function loadCameraOptions() {
}
}
function onCameraSelected(val: string) {
async function onCameraSelected(val: string) {
const opt = cameraOptions.value.find((o) => o.value === val);
if (opt) {
cameraId.value = val;
app.value = opt.camera.app || '';
stream.value = opt.camera.stream || '';
showCameraSelector.value = false;
buildSnapUrl();
await buildSnapUrl();
loadRois();
}
}
@@ -109,16 +121,20 @@ function goBack() {
router.push('/aiot/device/camera');
}
function buildSnapUrl() {
// ==================== 截图 ====================
async function buildSnapUrl() {
if (app.value && stream.value) {
snapUrl.value = getSnapUrl(app.value, stream.value);
snapUrl.value = await getSnapUrl(app.value, stream.value);
}
}
function refreshSnap() {
buildSnapUrl();
async function refreshSnap() {
await buildSnapUrl();
}
// ==================== ROI 数据加载 ====================
async function loadRois() {
try {
const data = await getRoiByCameraId(cameraId.value);
@@ -143,11 +159,13 @@ async function loadRoiDetail() {
}
}
// ==================== ROI 绘制 ====================
function startDraw(mode: string) {
drawMode.value = mode;
}
async function onRoiDrawn(data: { roi_type: string; coordinates: string }) {
async function onRoiDrawn(data: { coordinates: string; roi_type: string }) {
drawMode.value = null;
const newRoi: Partial<AiotDeviceApi.Roi> = {
cameraId: cameraId.value,
@@ -168,7 +186,9 @@ async function onRoiDrawn(data: { roi_type: string; coordinates: string }) {
}
}
function onRoiSelected(roiId: string | null) {
// ==================== ROI 选择 ====================
function onRoiSelected(roiId: null | string) {
selectedRoiId.value = roiId;
if (roiId) {
loadRoiDetail();
@@ -182,6 +202,19 @@ function selectRoi(roi: AiotDeviceApi.Roi) {
loadRoiDetail();
}
// ==================== ROI 编辑 ====================
async function updateRoiData(roi: AiotDeviceApi.Roi) {
try {
await saveRoi(roi);
loadRois();
} catch {
message.error('更新失败');
}
}
// ==================== ROI 删除 ====================
function onRoiDeleted(roiId: string) {
doDeleteRoi(roiId);
}
@@ -210,14 +243,7 @@ async function doDeleteRoi(roiId: string) {
}
}
async function updateRoiData(roi: AiotDeviceApi.Roi) {
try {
await saveRoi(roi);
loadRois();
} catch {
message.error('更新失败');
}
}
// ==================== 配置推送 ====================
function handlePush() {
Modal.confirm({
@@ -237,7 +263,7 @@ function handlePush() {
<template>
<Page auto-content-height>
<!-- Camera selector when no cameraId -->
<!-- 摄像头选择器 cameraId 参数时显示 -->
<div v-if="showCameraSelector" style="padding: 60px; text-align: center">
<h3 style="margin-bottom: 20px">请选择要配置ROI的摄像头</h3>
<Select
@@ -254,9 +280,9 @@ function handlePush() {
</div>
</div>
<!-- ROI Config Page -->
<!-- ROI 配置主界面 -->
<div v-else class="roi-config-page">
<!-- Header -->
<!-- 顶部操作栏 -->
<div class="page-header">
<div class="header-left">
<Button size="small" @click="goBack">返回</Button>
@@ -279,7 +305,7 @@ function handlePush() {
>
画多边形
</Button>
<Button size="small" @click="drawMode = null" :disabled="!drawMode">
<Button size="small" :disabled="!drawMode" @click="drawMode = null">
取消绘制
</Button>
<Button size="small" @click="refreshSnap">刷新截图</Button>
@@ -289,9 +315,9 @@ function handlePush() {
</div>
</div>
<!-- Main Content -->
<!-- 主内容区左侧画布 + 右侧面板 -->
<div class="main-content">
<!-- Canvas Panel -->
<!-- 画布区域 -->
<div class="canvas-panel">
<RoiCanvas
:rois="roiList"
@@ -304,9 +330,9 @@ function handlePush() {
/>
</div>
<!-- Side Panel -->
<!-- 右侧面板 -->
<div class="side-panel">
<!-- ROI List -->
<!-- ROI 列表 -->
<div class="section-header">
<span>ROI列表 ({{ roiList.length }})</span>
</div>
@@ -357,7 +383,7 @@ function handlePush() {
<Divider />
<!-- ROI Detail -->
<!-- ROI 属性编辑 -->
<div v-if="selectedRoi" class="roi-detail-section">
<h4>ROI属性</h4>
<Form
@@ -398,6 +424,7 @@ function handlePush() {
<Divider />
<!-- 算法绑定管理 -->
<RoiAlgorithmBind
:roi-id="selectedRoi.roiId || ''"
:bindings="selectedRoiBindings"