功能:摄像头页面增加区域选择器
1. 编辑弹窗新增「所属区域」下拉框,支持搜索,数据来自 IoT 平台 2. 表格新增「区域」列显示已绑定的区域名称 3. API 新增 getAreaList 函数查询 vsp-service 区域列表接口 4. Vite 代理新增 /api/area → vsp-service:8000 5. Camera 类型新增 areaId 字段
This commit is contained in:
@@ -45,6 +45,7 @@ export namespace AiotDeviceApi {
|
||||
mediaServerId?: string;
|
||||
streamKey?: string;
|
||||
createTime?: string;
|
||||
areaId?: number; // 所属区域ID
|
||||
}
|
||||
|
||||
/** ROI 区域 */
|
||||
@@ -299,3 +300,15 @@ export async function getAlertImageUrl(imagePath: string): Promise<string> {
|
||||
`&access-token=${encodeURIComponent(token)}`
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== 区域列表 ====================
|
||||
|
||||
/** 获取区域列表(从 IoT 平台代理查询) */
|
||||
export async function getAreaList(): Promise<
|
||||
{ id: number; areaName: string; parentId?: number }[]
|
||||
> {
|
||||
const resp = await fetch(`${apiURL}/api/area/list`);
|
||||
const json = await resp.json();
|
||||
if (json.code === 0) return json.data || [];
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
|
||||
import {
|
||||
deleteCamera,
|
||||
getAreaList,
|
||||
getCameraList,
|
||||
getMediaServerList,
|
||||
getRoiByCameraId,
|
||||
@@ -59,6 +60,7 @@ const columns = [
|
||||
{ title: '摄像头名称', dataIndex: 'cameraName', width: 150 },
|
||||
{ title: '拉流地址', dataIndex: 'srcUrl', ellipsis: true },
|
||||
{ title: '边缘设备', dataIndex: 'edgeDeviceId', width: 100 },
|
||||
{ title: '区域', key: 'areaName', width: 100 },
|
||||
{ title: '状态', key: 'status', width: 60, align: 'center' as const },
|
||||
{ title: 'ROI', key: 'roiCount', width: 80, align: 'center' as const },
|
||||
{ title: '操作', key: 'actions', width: 240, fixed: 'right' as const },
|
||||
@@ -70,6 +72,7 @@ const editModalOpen = ref(false);
|
||||
const editModalTitle = ref('添加摄像头');
|
||||
const saving = ref(false);
|
||||
const mediaServerOptions = ref<{ label: string; value: string }[]>([]);
|
||||
const areaOptions = ref<{ label: string; value: number }[]>([]);
|
||||
const editForm = reactive<Partial<AiotDeviceApi.Camera>>({
|
||||
id: undefined,
|
||||
type: 'default',
|
||||
@@ -86,6 +89,7 @@ const editForm = reactive<Partial<AiotDeviceApi.Camera>>({
|
||||
enableDisableNoneReader: true,
|
||||
relatesMediaServerId: '',
|
||||
ffmpegCmdKey: '',
|
||||
areaId: undefined as number | undefined,
|
||||
});
|
||||
|
||||
// 从已有摄像头中提取应用名选项
|
||||
@@ -108,6 +112,15 @@ const appOptions = computed(() => {
|
||||
.sort((a, b) => b.count - a.count);
|
||||
});
|
||||
|
||||
// 区域名称映射(area_id → area_name)
|
||||
const areaNameMap = computed(() => {
|
||||
const map: Record<number, string> = {};
|
||||
areaOptions.value.forEach((a) => {
|
||||
map[a.value] = a.label;
|
||||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
// ==================== 数据加载 ====================
|
||||
|
||||
async function loadData() {
|
||||
@@ -173,6 +186,18 @@ async function loadMediaServers() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAreaOptions() {
|
||||
try {
|
||||
const list = await getAreaList();
|
||||
areaOptions.value = list.map((a: any) => ({
|
||||
label: a.areaName || a.name || `区域${a.id}`,
|
||||
value: a.id,
|
||||
}));
|
||||
} catch {
|
||||
areaOptions.value = [];
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 新增 / 编辑 ====================
|
||||
|
||||
function resetForm() {
|
||||
@@ -192,6 +217,7 @@ function resetForm() {
|
||||
enableDisableNoneReader: true,
|
||||
relatesMediaServerId: '',
|
||||
ffmpegCmdKey: '',
|
||||
areaId: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -234,6 +260,7 @@ function handleAdd() {
|
||||
editModalTitle.value = '添加摄像头';
|
||||
editModalOpen.value = true;
|
||||
loadMediaServers();
|
||||
loadAreaOptions();
|
||||
// 自动填充流ID
|
||||
autoFillStreamId();
|
||||
}
|
||||
@@ -256,10 +283,12 @@ function handleEdit(row: AiotDeviceApi.Camera) {
|
||||
enableDisableNoneReader: row.enableDisableNoneReader ?? true,
|
||||
relatesMediaServerId: row.relatesMediaServerId || '',
|
||||
ffmpegCmdKey: row.ffmpegCmdKey || '',
|
||||
areaId: row.areaId || undefined,
|
||||
});
|
||||
editModalTitle.value = '编辑摄像头';
|
||||
editModalOpen.value = true;
|
||||
loadMediaServers();
|
||||
loadAreaOptions();
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
@@ -368,6 +397,7 @@ watch(
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
loadAreaOptions();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -421,7 +451,13 @@ onMounted(() => {
|
||||
size="middle"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<template v-if="column.key === 'areaName'">
|
||||
<Tag v-if="record.areaId && areaNameMap[record.areaId]" color="blue">
|
||||
{{ areaNameMap[record.areaId] }}
|
||||
</Tag>
|
||||
<span v-else style="color: #999">未绑定</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<Badge
|
||||
:status="
|
||||
cameraStatus[record.cameraCode] === null ||
|
||||
@@ -501,6 +537,21 @@ onMounted(() => {
|
||||
placeholder="选择绑定的边缘设备"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="所属区域">
|
||||
<Select
|
||||
v-model:value="editForm.areaId"
|
||||
:options="areaOptions"
|
||||
placeholder="选择所属区域(用于工单派发)"
|
||||
allow-clear
|
||||
show-search
|
||||
:filter-option="
|
||||
(input: string, option: any) =>
|
||||
(option?.label ?? '')
|
||||
.toLowerCase()
|
||||
.includes(input.toLowerCase())
|
||||
"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="拉流地址" required>
|
||||
<Input
|
||||
v-model:value="editForm.srcUrl"
|
||||
|
||||
@@ -33,6 +33,11 @@ export default defineConfig(async () => {
|
||||
changeOrigin: true,
|
||||
target: 'http://127.0.0.1:8000',
|
||||
},
|
||||
// 区域列表 API -> 告警服务 :8000
|
||||
'/api/area': {
|
||||
changeOrigin: true,
|
||||
target: 'http://127.0.0.1:8000',
|
||||
},
|
||||
|
||||
// aiot/device/* -> WVP :18080(按子路径分别 rewrite)
|
||||
// 注意:更具体的路径必须写在通配路径前面
|
||||
|
||||
Reference in New Issue
Block a user