Compare commits
15 Commits
bf93a1afec
...
8375d3c8a1
| Author | SHA1 | Date | |
|---|---|---|---|
| 8375d3c8a1 | |||
| b0ea479493 | |||
| e90ffec8c8 | |||
| 0a3658dc57 | |||
| bddae7367a | |||
| 37af7ea117 | |||
| 6b096862b2 | |||
| 5f04a3c401 | |||
| e163bf5152 | |||
| fd6ac90b67 | |||
| d70ce2fa79 | |||
| 10701468c8 | |||
| fd4673d661 | |||
| 5d43b46155 | |||
| 792424717a |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -50,3 +50,9 @@ vite.config.ts.*
|
||||
*.sw?
|
||||
.history
|
||||
.cursor
|
||||
|
||||
# Diagnostic documents - DO NOT COMMIT
|
||||
docs/问题诊断/*.md
|
||||
!docs/问题诊断/README.md
|
||||
CLAUDE.md
|
||||
AGENTS.md
|
||||
|
||||
@@ -20,6 +20,8 @@ interface Props {
|
||||
paramSchema: string;
|
||||
currentParams: string;
|
||||
bindId: string;
|
||||
priority?: number;
|
||||
enabled?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -27,6 +29,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
paramSchema: '{}',
|
||||
currentParams: '{}',
|
||||
bindId: '',
|
||||
priority: 0,
|
||||
enabled: 1,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -37,6 +41,39 @@ const emit = defineEmits<{
|
||||
const formData = ref<Record<string, any>>({});
|
||||
const newListItem = ref('');
|
||||
|
||||
// 参数名中英文映射
|
||||
const paramNameMap: Record<string, string> = {
|
||||
leave_countdown_sec: '离岗倒计时(秒)',
|
||||
working_hours: '工作时间段',
|
||||
// 其他算法参数
|
||||
cooldown_seconds: '告警冷却期(秒)',
|
||||
confirm_seconds: '触发确认时间(秒)',
|
||||
min_confidence: '最小置信度',
|
||||
max_targets: '最大目标数',
|
||||
detection_interval: '检测间隔(秒)',
|
||||
enable_tracking: '启用跟踪',
|
||||
tracking_timeout: '跟踪超时(秒)',
|
||||
};
|
||||
|
||||
// 参数说明映射
|
||||
const paramDescMap: Record<string, string> = {
|
||||
leave_countdown_sec: '人员离开后,倒计时多少秒才触发离岗告警',
|
||||
working_hours: '仅在指定时间段内进行监控,留空表示24小时监控',
|
||||
// 其他算法参数说明
|
||||
cooldown_seconds: '触发告警后,多少秒内不再重复告警(用于周界入侵等算法)',
|
||||
confirm_seconds: '检测到目标后,持续多少秒才触发告警(避免瞬间误报)',
|
||||
};
|
||||
|
||||
// 获取参数的中文名称
|
||||
function getParamLabel(key: string): string {
|
||||
return paramNameMap[key] || key;
|
||||
}
|
||||
|
||||
// 获取参数说明
|
||||
function getParamDesc(key: string): string | undefined {
|
||||
return paramDescMap[key];
|
||||
}
|
||||
|
||||
const parsedSchema = computed(() => {
|
||||
try {
|
||||
return JSON.parse(props.paramSchema);
|
||||
@@ -94,22 +131,36 @@ function isWorkingHoursField(key: string): boolean {
|
||||
|
||||
async function handleSave() {
|
||||
try {
|
||||
console.log('[算法参数保存] 开始保存,原始数据:', formData.value);
|
||||
|
||||
// 保存前验证参数
|
||||
const validation = validateParams(formData.value);
|
||||
console.log('[算法参数保存] 验证结果:', validation);
|
||||
|
||||
if (!validation.valid) {
|
||||
message.error(validation.error || '参数格式错误');
|
||||
return;
|
||||
}
|
||||
|
||||
await updateAlgoParams({
|
||||
const paramsJson = JSON.stringify(formData.value);
|
||||
console.log('[算法参数保存] JSON序列化后:', paramsJson);
|
||||
|
||||
const payload = {
|
||||
bindId: props.bindId,
|
||||
params: JSON.stringify(formData.value),
|
||||
});
|
||||
params: paramsJson,
|
||||
priority: props.priority ?? 0, // 保留原有priority
|
||||
enabled: props.enabled ?? 1, // 保留原有enabled
|
||||
};
|
||||
console.log('[算法参数保存] 发送请求:', payload);
|
||||
|
||||
await updateAlgoParams(payload);
|
||||
message.success('参数保存成功');
|
||||
emit('saved', formData.value);
|
||||
emit('update:open', false);
|
||||
} catch (error: any) {
|
||||
console.error('保存失败:', error);
|
||||
console.error('[算法参数保存] 保存失败,完整错误:', error);
|
||||
console.error('[算法参数保存] 错误响应:', error?.response);
|
||||
|
||||
// 改进错误提示:显示后端返回的具体错误信息
|
||||
const errorMsg = error?.response?.data?.msg ||
|
||||
error?.response?.data?.message ||
|
||||
@@ -151,18 +202,30 @@ function validateParams(params: Record<string, any>): {
|
||||
|
||||
// 特殊校验 working_hours 格式
|
||||
if (key === 'working_hours' && value.length > 0) {
|
||||
console.log('[working_hours 验证] 当前值:', value);
|
||||
console.log('[working_hours 验证] 值类型:', typeof value);
|
||||
console.log('[working_hours 验证] 是否数组:', Array.isArray(value));
|
||||
|
||||
const isValid = value.every(
|
||||
(item: any) =>
|
||||
typeof item === 'object' &&
|
||||
typeof item.start === 'string' &&
|
||||
typeof item.end === 'string',
|
||||
(item: any, index: number) => {
|
||||
const valid = typeof item === 'object' &&
|
||||
typeof item.start === 'string' &&
|
||||
typeof item.end === 'string';
|
||||
if (!valid) {
|
||||
console.error(`[working_hours 验证] 第 ${index} 项格式错误:`, item);
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
return {
|
||||
valid: false,
|
||||
error: '工作时间段格式错误,每项需包含 start 和 end',
|
||||
error: '工作时间段格式错误,每项需包含 start 和 end 字符串',
|
||||
};
|
||||
}
|
||||
|
||||
console.log('[working_hours 验证] 格式正确 ✓');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,22 +250,28 @@ function validateParams(params: Record<string, any>): {
|
||||
<Form.Item
|
||||
v-for="(schema, key) in parsedSchema"
|
||||
:key="key"
|
||||
:label="String(key)"
|
||||
:label="getParamLabel(String(key))"
|
||||
>
|
||||
<!-- 整数类型 -->
|
||||
<InputNumber
|
||||
v-if="schema.type === 'int'"
|
||||
v-model:value="formData[String(key)]"
|
||||
:min="schema.min"
|
||||
:placeholder="`默认: ${schema.default}`"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<template v-if="schema.type === 'int'">
|
||||
<InputNumber
|
||||
v-model:value="formData[String(key)]"
|
||||
:min="schema.min"
|
||||
:placeholder="`默认: ${schema.default}`"
|
||||
style="width: 100%"
|
||||
/>
|
||||
<div v-if="getParamDesc(String(key))" class="param-desc">
|
||||
{{ getParamDesc(String(key)) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 工作时间段(特殊处理) -->
|
||||
<WorkingHoursEditor
|
||||
v-else-if="isWorkingHoursField(String(key))"
|
||||
v-model="formData[String(key)]"
|
||||
/>
|
||||
<template v-else-if="isWorkingHoursField(String(key))">
|
||||
<WorkingHoursEditor v-model="formData[String(key)]" />
|
||||
<div v-if="getParamDesc(String(key))" class="param-desc">
|
||||
{{ getParamDesc(String(key)) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 普通列表类型 -->
|
||||
<div v-else-if="schema.type === 'list'">
|
||||
@@ -275,4 +344,11 @@ function validateParams(params: Record<string, any>): {
|
||||
.help-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.param-desc {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -43,6 +43,8 @@ const paramEditorOpen = ref(false);
|
||||
const currentParamSchema = ref('{}');
|
||||
const currentParams = ref('{}');
|
||||
const currentBindId = ref('');
|
||||
const currentPriority = ref(0);
|
||||
const currentEnabled = ref(1);
|
||||
|
||||
watch(
|
||||
() => props.roiId,
|
||||
@@ -102,6 +104,8 @@ function openParamEditor(item: AiotDeviceApi.RoiAlgoBinding) {
|
||||
currentBindId.value = item.bind.bindId || '';
|
||||
currentParams.value = item.bind.params || '{}';
|
||||
currentParamSchema.value = item.algorithm?.paramSchema || '{}';
|
||||
currentPriority.value = item.bind.priority ?? 0;
|
||||
currentEnabled.value = item.bind.enabled ?? 1;
|
||||
paramEditorOpen.value = true;
|
||||
}
|
||||
|
||||
@@ -120,6 +124,15 @@ async function onToggleEnabled(bind: AiotDeviceApi.AlgoBind, val: string | numbe
|
||||
function onParamSaved() {
|
||||
emit('changed');
|
||||
}
|
||||
|
||||
// 获取算法的固定帧率
|
||||
function getAlgoFrameRate(algoCode: string): string {
|
||||
const frameRates: Record<string, string> = {
|
||||
leave_post: '3帧/秒',
|
||||
intrusion: '1帧/秒',
|
||||
};
|
||||
return frameRates[algoCode] || '5帧/秒';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -170,6 +183,9 @@ function onParamSaved() {
|
||||
<Tag color="blue">
|
||||
{{ item.algorithm?.algoName || item.bind.algoCode }}
|
||||
</Tag>
|
||||
<Tag color="green" style="font-size: 11px">
|
||||
{{ getAlgoFrameRate(item.bind.algoCode) }}
|
||||
</Tag>
|
||||
<span style="color: #999; font-size: 12px">
|
||||
{{ item.bind.algoCode }}
|
||||
</span>
|
||||
@@ -223,6 +239,8 @@ function onParamSaved() {
|
||||
:param-schema="currentParamSchema"
|
||||
:current-params="currentParams"
|
||||
:bind-id="currentBindId"
|
||||
:priority="currentPriority"
|
||||
:enabled="currentEnabled"
|
||||
@saved="onParamSaved"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -7,10 +7,7 @@ import {
|
||||
message,
|
||||
Space,
|
||||
Tag,
|
||||
TimePicker,
|
||||
} from 'ant-design-vue';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
interface WorkingHourPeriod {
|
||||
start: string; // "HH:mm" 格式
|
||||
@@ -30,7 +27,11 @@ const emit = defineEmits<{
|
||||
}>();
|
||||
|
||||
// 当前编辑的时间段
|
||||
const currentRange = ref<[Dayjs, Dayjs] | null>(null);
|
||||
const startTime = ref('');
|
||||
const endTime = ref('');
|
||||
|
||||
// 正在编辑的时间段索引
|
||||
const editingIndex = ref<number | null>(null);
|
||||
|
||||
// 内部工作时间段列表
|
||||
const periods = computed<WorkingHourPeriod[]>({
|
||||
@@ -57,33 +58,22 @@ const templates = [
|
||||
icon: '⏰',
|
||||
description: '上午+下午',
|
||||
periods: [
|
||||
{ start: '09:00', end: '12:00' },
|
||||
{ start: '14:00', end: '18:00' },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '三班制',
|
||||
icon: '🔄',
|
||||
description: '早中晚班',
|
||||
periods: [
|
||||
{ start: '08:00', end: '16:00' },
|
||||
{ start: '16:00', end: '00:00' },
|
||||
{ start: '00:00', end: '08:00' },
|
||||
{ start: '08:30', end: '11:00' },
|
||||
{ start: '12:00', end: '17:30' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// 添加时间段
|
||||
// 添加或更新时间段
|
||||
function addPeriod() {
|
||||
if (!currentRange.value) {
|
||||
message.warning('请先选择时间段');
|
||||
if (!startTime.value || !endTime.value) {
|
||||
message.warning('请选择开始时间和结束时间');
|
||||
return;
|
||||
}
|
||||
|
||||
const [start, end] = currentRange.value;
|
||||
const newPeriod: WorkingHourPeriod = {
|
||||
start: start.format('HH:mm'),
|
||||
end: end.format('HH:mm'),
|
||||
start: startTime.value,
|
||||
end: endTime.value,
|
||||
};
|
||||
|
||||
// 校验:结束时间必须晚于开始时间
|
||||
@@ -92,37 +82,85 @@ function addPeriod() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 校验:时间段不能重叠
|
||||
const hasOverlap = periods.value.some((p) => {
|
||||
return !(newPeriod.end <= p.start || newPeriod.start >= p.end);
|
||||
});
|
||||
// 如果是编辑模式
|
||||
if (editingIndex.value !== null) {
|
||||
// 校验:时间段不能与其他时间段重叠(排除自己)
|
||||
const hasOverlap = periods.value.some((p, i) => {
|
||||
if (i === editingIndex.value) return false;
|
||||
return !(newPeriod.end <= p.start || newPeriod.start >= p.end);
|
||||
});
|
||||
|
||||
if (hasOverlap) {
|
||||
message.error('时间段不能重叠');
|
||||
return;
|
||||
if (hasOverlap) {
|
||||
message.error('时间段不能重叠');
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新现有时间段
|
||||
const newPeriods = [...periods.value];
|
||||
newPeriods[editingIndex.value] = newPeriod;
|
||||
periods.value = newPeriods.sort((a, b) => a.start.localeCompare(b.start));
|
||||
message.success('时间段已更新');
|
||||
editingIndex.value = null;
|
||||
} else {
|
||||
// 校验:时间段不能重叠
|
||||
const hasOverlap = periods.value.some((p) => {
|
||||
return !(newPeriod.end <= p.start || newPeriod.start >= p.end);
|
||||
});
|
||||
|
||||
if (hasOverlap) {
|
||||
message.error('时间段不能重叠');
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加新时间段
|
||||
periods.value = [...periods.value, newPeriod].sort((a, b) =>
|
||||
a.start.localeCompare(b.start),
|
||||
);
|
||||
message.success('时间段已添加');
|
||||
}
|
||||
|
||||
periods.value = [...periods.value, newPeriod].sort((a, b) =>
|
||||
a.start.localeCompare(b.start),
|
||||
);
|
||||
currentRange.value = null;
|
||||
message.success('时间段已添加');
|
||||
startTime.value = '';
|
||||
endTime.value = '';
|
||||
}
|
||||
|
||||
// 编辑时间段
|
||||
function editPeriod(index: number) {
|
||||
const period = periods.value[index];
|
||||
startTime.value = period.start;
|
||||
endTime.value = period.end;
|
||||
editingIndex.value = index;
|
||||
}
|
||||
|
||||
// 取消编辑
|
||||
function cancelEdit() {
|
||||
startTime.value = '';
|
||||
endTime.value = '';
|
||||
editingIndex.value = null;
|
||||
}
|
||||
|
||||
// 删除时间段
|
||||
function removePeriod(index: number) {
|
||||
periods.value = periods.value.filter((_, i) => i !== index);
|
||||
// 如果删除的是正在编辑的时间段,取消编辑状态
|
||||
if (editingIndex.value === index) {
|
||||
cancelEdit();
|
||||
} else if (editingIndex.value !== null && editingIndex.value > index) {
|
||||
// 如果删除的时间段在编辑时间段之前,需要调整编辑索引
|
||||
editingIndex.value--;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用模板
|
||||
function useTemplate(template: (typeof templates)[0]) {
|
||||
periods.value = template.periods;
|
||||
cancelEdit(); // 取消编辑状态
|
||||
message.success(`已应用模板:${template.label}`);
|
||||
}
|
||||
|
||||
// 清空所有时间段
|
||||
function clearAll() {
|
||||
periods.value = [];
|
||||
cancelEdit(); // 取消编辑状态
|
||||
}
|
||||
|
||||
// 时间段描述文本
|
||||
@@ -178,18 +216,27 @@ function getDuration(period: WorkingHourPeriod): string {
|
||||
</div>
|
||||
|
||||
<div class="time-picker-row">
|
||||
<TimePicker.RangePicker
|
||||
v-model:value="currentRange"
|
||||
format="HH:mm"
|
||||
:minute-step="30"
|
||||
placeholder="选择开始和结束时间"
|
||||
style="flex: 1"
|
||||
<input
|
||||
v-model="startTime"
|
||||
type="time"
|
||||
class="time-input"
|
||||
placeholder="开始时间"
|
||||
/>
|
||||
<span class="time-separator">至</span>
|
||||
<input
|
||||
v-model="endTime"
|
||||
type="time"
|
||||
class="time-input"
|
||||
placeholder="结束时间"
|
||||
/>
|
||||
<Button v-if="editingIndex !== null" @click="cancelEdit">
|
||||
取消
|
||||
</Button>
|
||||
<Button type="primary" @click="addPeriod">
|
||||
<template #icon>
|
||||
<span>➕</span>
|
||||
<span>{{ editingIndex !== null ? '💾' : '➕' }}</span>
|
||||
</template>
|
||||
添加
|
||||
{{ editingIndex !== null ? '保存' : '添加' }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -207,6 +254,7 @@ function getDuration(period: WorkingHourPeriod): string {
|
||||
v-for="(period, index) in periods"
|
||||
:key="index"
|
||||
class="period-card"
|
||||
:class="{ editing: editingIndex === index }"
|
||||
size="small"
|
||||
>
|
||||
<div class="period-content">
|
||||
@@ -219,14 +267,23 @@ function getDuration(period: WorkingHourPeriod): string {
|
||||
{{ getDuration(period) }}
|
||||
</Tag>
|
||||
</div>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
danger
|
||||
@click="removePeriod(index)"
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
<Space>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
@click="editPeriod(index)"
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
danger
|
||||
@click="removePeriod(index)"
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -247,6 +304,7 @@ function getDuration(period: WorkingHourPeriod): string {
|
||||
<div class="help-title">💡 使用说明</div>
|
||||
<ul class="help-list">
|
||||
<li>工作时间段为空表示24小时监控</li>
|
||||
<li>点击快捷模板后可继续编辑调整时间</li>
|
||||
<li>时间段不能重叠,系统会自动校验</li>
|
||||
<li>支持跨天时间段(如 16:00-08:00)</li>
|
||||
<li>可添加多个不连续的时间段</li>
|
||||
@@ -327,9 +385,44 @@ function getDuration(period: WorkingHourPeriod): string {
|
||||
.time-picker-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.time-input {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
padding: 4px 11px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5715;
|
||||
color: rgba(0, 0, 0, 0.88);
|
||||
background-color: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.time-input:hover {
|
||||
border-color: #4096ff;
|
||||
}
|
||||
|
||||
.time-input:focus {
|
||||
border-color: #4096ff;
|
||||
box-shadow: 0 0 0 2px rgba(5, 145, 255, 0.1);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.time-input:disabled {
|
||||
color: rgba(0, 0, 0, 0.25);
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.time-separator {
|
||||
color: rgba(0, 0, 0, 0.45);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 时间段列表 */
|
||||
.periods-list {
|
||||
background: #fafafa;
|
||||
@@ -357,6 +450,12 @@ function getDuration(period: WorkingHourPeriod): string {
|
||||
border: 1px solid #d9d9d9;
|
||||
}
|
||||
|
||||
.period-card.editing {
|
||||
border-color: #1890ff;
|
||||
background-color: #e6f4ff;
|
||||
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
|
||||
.period-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -47,6 +47,7 @@ const router = useRouter();
|
||||
// ==================== 摄像头选择 ====================
|
||||
|
||||
const cameraCode = ref('');
|
||||
const currentCamera = ref<AiotDeviceApi.Camera | null>(null);
|
||||
|
||||
const showCameraSelector = ref(false);
|
||||
const cameraOptions = ref<
|
||||
@@ -74,6 +75,8 @@ onMounted(async () => {
|
||||
const q = route.query;
|
||||
if (q.cameraCode) {
|
||||
cameraCode.value = String(q.cameraCode);
|
||||
// 加载摄像头信息以获取应用名
|
||||
await loadCurrentCamera();
|
||||
await buildSnapUrl();
|
||||
loadRois();
|
||||
} else {
|
||||
@@ -84,6 +87,19 @@ onMounted(async () => {
|
||||
|
||||
// ==================== 摄像头加载与选择 ====================
|
||||
|
||||
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);
|
||||
if (camera) {
|
||||
currentCamera.value = camera;
|
||||
}
|
||||
} catch {
|
||||
console.error('加载摄像头信息失败');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCameraOptions() {
|
||||
cameraLoading.value = true;
|
||||
try {
|
||||
@@ -91,7 +107,7 @@ async function loadCameraOptions() {
|
||||
const list = res.list || [];
|
||||
cameraOptions.value = list.map((cam: AiotDeviceApi.Camera) => ({
|
||||
value: cam.cameraCode || '',
|
||||
label: `${cam.cameraCode || cam.stream}${cam.srcUrl ? ` (${cam.srcUrl})` : ''}`,
|
||||
label: `${cam.app || cam.stream}${cam.srcUrl ? ` (${cam.srcUrl})` : ''}`,
|
||||
camera: cam,
|
||||
}));
|
||||
} catch {
|
||||
@@ -105,6 +121,7 @@ async function onCameraSelected(val: string) {
|
||||
const opt = cameraOptions.value.find((o) => o.value === val);
|
||||
if (opt) {
|
||||
cameraCode.value = val;
|
||||
currentCamera.value = opt.camera;
|
||||
showCameraSelector.value = false;
|
||||
await buildSnapUrl();
|
||||
loadRois();
|
||||
@@ -280,7 +297,7 @@ function handlePush() {
|
||||
<div class="page-header">
|
||||
<div class="header-left">
|
||||
<Button size="small" @click="goBack">返回</Button>
|
||||
<h3>{{ cameraCode }} - ROI配置</h3>
|
||||
<h3>{{ currentCamera?.app || cameraCode }} - ROI配置</h3>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user