Compare commits

...

4 Commits

Author SHA1 Message Date
2e0efc7c8c fix(aiot): 修改应用名时自动重新分配流ID,从001开始按序分配 2026-02-25 14:14:02 +08:00
6ac978a2ac fix(aiot): 修复应用名多选问题,使用AutoComplete替代Select tags模式 2026-02-25 14:08:21 +08:00
286f4a1a03 feat(aiot): 添加应用名下拉选择功能,支持选择已有或输入新名称 2026-02-25 13:55:29 +08:00
bf3ca08d7b feat(aiot): 允许编辑摄像头应用名,保留流ID不变
修改内容:
- 移除应用名的 disabled 限制,允许在编辑模式下修改
- 流ID保持不可编辑(避免复杂的唯一性冲突)
- 更新提示文字:应用名可修改,流ID不可修改

用户场景:
- 用户创建摄像头时可能输错应用名
- 或者后期需要调整摄像头的场景分类
- 修改应用名后,流ID保持不变(如:大堂/001 → 停车场/001)
- 后端会验证新的 app+stream 组合是否冲突

注意事项:
- 修改应用名可能导致 app+stream 组合冲突
- 后端已添加唯一性验证,会提示用户冲突
2026-02-25 13:49:27 +08:00

View File

@@ -7,12 +7,13 @@
*/ */
import type { AiotDeviceApi } from '#/api/aiot/device'; import type { AiotDeviceApi } from '#/api/aiot/device';
import { onMounted, reactive, ref, watch } from 'vue'; import { computed, onMounted, reactive, ref, watch } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { Page } from '@vben/common-ui'; import { Page } from '@vben/common-ui';
import { import {
AutoComplete,
Badge, Badge,
Button, Button,
Checkbox, Checkbox,
@@ -84,6 +85,26 @@ const editForm = reactive<Partial<AiotDeviceApi.Camera>>({
ffmpegCmdKey: '', ffmpegCmdKey: '',
}); });
// 从已有摄像头中提取应用名选项
const appOptions = computed(() => {
// 统计每个应用名下的摄像头数量
const appCounts = new Map<string, number>();
cameraList.value.forEach((cam) => {
if (cam.app) {
appCounts.set(cam.app, (appCounts.get(cam.app) || 0) + 1);
}
});
// 转换为选项数组,按摄像头数量降序排序
return Array.from(appCounts.entries())
.map(([app, count]) => ({
label: `${app} (${count}个)`,
value: app,
count,
}))
.sort((a, b) => b.count - a.count);
});
// ==================== 数据加载 ==================== // ==================== 数据加载 ====================
async function loadData() { async function loadData() {
@@ -163,10 +184,12 @@ function resetForm() {
*/ */
function autoFillStreamId() { function autoFillStreamId() {
const app = editForm.app; const app = editForm.app;
if (!app || editForm.id) return; // 编辑模式不自动填充 if (!app) return;
// 过滤出同一应用下的摄像头 // 过滤出同一应用下的摄像头(排除当前编辑的摄像头)
const sameAppCameras = cameraList.value.filter((c) => c.app === app); const sameAppCameras = cameraList.value.filter(
(c) => c.app === app && c.id !== editForm.id,
);
// 获取已用的纯数字编号 // 获取已用的纯数字编号
const usedNumbers = sameAppCameras const usedNumbers = sameAppCameras
@@ -486,23 +509,24 @@ onMounted(() => {
<Input :value="editForm.cameraCode" disabled /> <Input :value="editForm.cameraCode" disabled />
</Form.Item> </Form.Item>
<Form.Item label="应用名" required> <Form.Item label="应用名" required>
<Input <AutoComplete
v-model:value="editForm.app" v-model:value="editForm.app"
placeholder="场景分类,如:大堂、停车场、入口" :options="appOptions"
:disabled="!!editForm.id" placeholder="选择已有场景或输入新场景名"
allow-clear
@change="autoFillStreamId"
/> />
<div style="margin-top: 4px; font-size: 12px; color: #999"> <div style="margin-top: 4px; font-size: 12px; color: #999">
场景名称同一场景下可有多个摄像头 场景名称同一场景下可有多个摄像头可选择已有或输入新名称
</div> </div>
</Form.Item> </Form.Item>
<Form.Item label="流ID" required> <Form.Item label="流ID" required>
<Input <Input
v-model:value="editForm.stream" v-model:value="editForm.stream"
placeholder="在当前场景下的唯一标识001系统会自动编号" placeholder="在当前场景下的唯一标识001系统会自动编号"
:disabled="!!editForm.id"
/> />
<div style="margin-top: 4px; font-size: 12px; color: #999"> <div style="margin-top: 4px; font-size: 12px; color: #999">
在当前场景下唯一系统会根据应用名自动编号 在当前场景下唯一系统会根据应用名自动编号可手动修改
</div> </div>
</Form.Item> </Form.Item>
<Form.Item label="拉流地址" required> <Form.Item label="拉流地址" required>