Compare commits

...

6 Commits

Author SHA1 Message Date
yangsy
f9f761b4e9 chore: 版本信息和更新日志 2026-03-11 15:20:03 +08:00
yangsy
4090c7e6c5 fix: 使用设备名称和IP地址组合作为设备树中设备节点的key 2026-03-11 15:17:19 +08:00
yangsy
38b43b1c45 feat: 改进设备树搜索功能,增加搜索类型选择 2026-03-11 15:17:19 +08:00
yangsy
9eafc7871b chore: 添加2号线API代理配置 2026-03-11 15:17:19 +08:00
yangsy
3eb5b06f59 fix: 录像缺失信息渲染条件 2026-03-11 15:17:19 +08:00
yangsy
f2fc2e732d docs: 更新README.md 2026-03-11 15:17:19 +08:00
6 changed files with 93 additions and 17 deletions

View File

@@ -65,8 +65,11 @@ src/
vimp-log-page.vue # 视频平台日志页面 vimp-log-page.vue # 视频平台日志页面
permission/ permission/
permission-page.vue # 权限管理页面 permission-page.vue # 权限管理页面
error/ system/
not-found-page.vue # 404 页面 changelog/
changelog-page.vue # 更新记录页面
error/
not-found-page.vue # 404 页面
``` ```
## 数据轮询 ## 数据轮询

View File

@@ -1,4 +1,12 @@
[ [
{
"version": "0.39.0",
"date": "2026-03-02",
"changes": {
"fixes": [{ "content": "修复设备树搜索时节点错乱的问题" }],
"feats": [{ "content": "新版录像记录诊断卡片" }, { "content": "新增平台更新记录页面" }]
}
},
{ {
"version": "0.38.5", "version": "0.38.5",
"date": "2026-02-06", "date": "2026-02-06",

View File

@@ -1,4 +1,4 @@
{ {
"version": "0.38.5", "version": "0.39.0",
"buildTime": "2026-02-06 10:25:07" "buildTime": "2026-03-11 14:35:45"
} }

View File

@@ -626,9 +626,14 @@ const columns: DataTableColumns<DailyLossItem['chunks'][number]> = [
<template #default> <template #default>
<template v-if="!!dailyCheckContext.info"> <template v-if="!!dailyCheckContext.info">
<div>日期:{{ dailyCheckContext.info.date }}</div> <div>日期:{{ dailyCheckContext.info.date }}</div>
<div>缺失时长:{{ formatDuration(dailyCheckContext.info.total, { withinDay: true }) }}</div> <template v-if="dailyCheckContext.info.percent > 0">
<div>缺失比例{{ dailyCheckContext.info.percent.toFixed(2) }}%</div> <div>缺失时长{{ formatDuration(dailyCheckContext.info.total, { withinDay: true }) }}</div>
<div v-if="dailyCheckContext.info.percent > 0" style="font-size: xx-small; opacity: 0.5; cursor: pointer" @click="onClickDailyCheck">点击查看详情</div> <div>缺失比例:{{ dailyCheckContext.info.percent.toFixed(2) }}%</div>
<div style="font-size: xx-small; opacity: 0.5; cursor: pointer" @click="onClickDailyCheck">点击查看详情</div>
</template>
<template v-else>
<div>录像完整</div>
</template>
</template> </template>
</template> </template>
</NPopover> </NPopover>

View File

@@ -11,15 +11,19 @@ import {
NButton, NButton,
NDropdown, NDropdown,
NFlex, NFlex,
NGrid,
NGridItem,
NInput, NInput,
NRadio, NRadio,
NRadioGroup, NRadioGroup,
NSelect,
NTab, NTab,
NTabs, NTabs,
NTag, NTag,
NTree, NTree,
useThemeVars, useThemeVars,
type DropdownOption, type DropdownOption,
type SelectOption,
type TagProps, type TagProps,
type TreeInst, type TreeInst,
type TreeOption, type TreeOption,
@@ -348,7 +352,7 @@ const lineDeviceTreeData = computed<Record<Station['code'], TreeOption[]>>(() =>
const device = dev as NdmDeviceResultVO; const device = dev as NdmDeviceResultVO;
return { return {
label: `${device.name}`, label: `${device.name}`,
key: `${device.id}`, key: `${device.name}${device.ipAddress}`,
prefix: () => renderDeviceNodePrefix(device, stationCode), prefix: () => renderDeviceNodePrefix(device, stationCode),
suffix: () => `${device.ipAddress}`, suffix: () => `${device.ipAddress}`,
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站 // 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
@@ -382,13 +386,13 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
children: clusters.map<TreeOption>((device) => { children: clusters.map<TreeOption>((device) => {
return { return {
label: `${device.name}`, label: `${device.name}`,
key: `${device.id}`, key: `${device.name}${device.ipAddress}`,
prefix: () => renderDeviceNodePrefix(device, stationCode), prefix: () => renderDeviceNodePrefix(device, stationCode),
suffix: () => `${device.ipAddress}`, suffix: () => `${device.ipAddress}`,
children: singletons.map<TreeOption>((device) => { children: singletons.map<TreeOption>((device) => {
return { return {
label: `${device.name}`, label: `${device.name}`,
key: `${device.id}`, key: `${device.name}${device.ipAddress}`,
prefix: () => renderDeviceNodePrefix(device, stationCode), prefix: () => renderDeviceNodePrefix(device, stationCode),
suffix: () => `${device.ipAddress}`, suffix: () => `${device.ipAddress}`,
stationCode, stationCode,
@@ -410,7 +414,7 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
children: stationDevices[deviceType].map<TreeOption>((device) => { children: stationDevices[deviceType].map<TreeOption>((device) => {
return { return {
label: `${device.name}`, label: `${device.name}`,
key: `${device.id}`, key: `${device.name}${device.ipAddress}`,
prefix: () => renderDeviceNodePrefix(device, stationCode), prefix: () => renderDeviceNodePrefix(device, stationCode),
suffix: () => `${device.ipAddress}`, suffix: () => `${device.ipAddress}`,
stationCode, stationCode,
@@ -425,20 +429,26 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
// ========== 设备树搜索 ========== // ========== 设备树搜索 ==========
const searchInput = ref(''); const searchInput = ref('');
const searchTypeOptions: SelectOption[] = [
{ label: '设备名称', value: 'name' },
{ label: 'IP地址', value: 'ipAddress' },
];
type SearchType = 'name' | 'ipAddress';
const typeInput = ref<SearchType>('name');
const statusInput = ref(''); const statusInput = ref('');
// 设备树将搜索框单选框的值都交给NTree的pattern属性 // 设备树将搜索框、选择器以及单选框的值都交给NTree的pattern属性
// 但是如果一个车站下没有匹配的设备,那么这个车站节点也不会显示 // 但是如果一个车站下没有匹配的设备,那么这个车站节点也不会显示
const searchPattern = computed(() => { const searchPattern = computed(() => {
const search = searchInput.value; const search = searchInput.value;
const status = statusInput.value; const status = statusInput.value;
if (!search && !status) return ''; // 如果pattern非空会导致NTree组件认为筛选完成UI上发生全量匹配 if (!search && !status) return ''; // 如果pattern非空会导致NTree组件认为筛选完成UI上发生全量匹配
return JSON.stringify({ search: searchInput.value, status: statusInput.value }); return JSON.stringify({ search: searchInput.value, type: typeInput.value, status: statusInput.value });
}); });
const searchFilter = (pattern: string, node: TreeOption): boolean => { const searchFilter = (pattern: string, node: TreeOption): boolean => {
const { search, status } = destr<{ search: string; status: string }>(pattern); const { search, type, status } = destr<{ search: string; type: SearchType; status: string }>(pattern);
const device = node['device'] as NdmDeviceResultVO | undefined; const device = node['device'] as NdmDeviceResultVO | undefined;
const { name, ipAddress, deviceId, deviceStatus } = device ?? {}; const { deviceStatus } = device ?? {};
const searchMatched = (name ?? '').includes(search) || (ipAddress ?? '').includes(search) || (deviceId ?? '').includes(search); const searchMatched = !!device?.[type]?.includes(search);
const statusMatched = status === '' || status === deviceStatus; const statusMatched = status === '' || status === deviceStatus;
return searchMatched && statusMatched; return searchMatched && statusMatched;
}; };
@@ -523,7 +533,14 @@ onMounted(() => {
<div style="height: 100%; display: flex; flex-direction: column"> <div style="height: 100%; display: flex; flex-direction: column">
<!-- 搜索和筛选 --> <!-- 搜索和筛选 -->
<div style="padding: 12px; flex: 0 0 auto"> <div style="padding: 12px; flex: 0 0 auto">
<NInput v-model:value="searchInput" placeholder="搜索设备名称、设备ID或IP地址" clearable /> <NGrid :cols="10" :x-gap="8">
<NGridItem :span="7">
<NInput v-model:value="searchInput" placeholder="搜索设备名称或IP地址" clearable />
</NGridItem>
<NGridItem :span="3">
<NSelect v-model:value="typeInput" :options="searchTypeOptions" placeholder="搜索类型" />
</NGridItem>
</NGrid>
<NFlex align="center"> <NFlex align="center">
<NRadioGroup v-model:value="statusInput"> <NRadioGroup v-model:value="statusInput">
<NRadio value="">全部</NRadio> <NRadio value="">全部</NRadio>

View File

@@ -76,6 +76,44 @@ const line01ApiProxyList: ProxyItem[] = [
{ key: '/0128/api', target: 'http://10.14.55.10:18760', rewrite: ['/0128/api', '/api'] }, { key: '/0128/api', target: 'http://10.14.55.10:18760', rewrite: ['/0128/api', '/api'] },
]; ];
const line02ApiProxyList: ProxyItem[] = [
{ key: '/0275/api', target: 'http://10.14.128.10:18760', rewrite: ['/0275/api', '/api'] },
{ key: '/0202/api', target: 'http://10.14.129.10:18760', rewrite: ['/0202/api', '/api'] },
{ key: '/0203/api', target: 'http://10.14.131.10:18760', rewrite: ['/0203/api', '/api'] },
{ key: '/0204/api', target: 'http://10.14.133.10:18760', rewrite: ['/0204/api', '/api'] },
{ key: '/0205/api', target: 'http://10.14.135.10:18760', rewrite: ['/0205/api', '/api'] },
{ key: '/0206/api', target: 'http://10.14.137.10:18760', rewrite: ['/0206/api', '/api'] },
{ key: '/0207/api', target: 'http://10.14.139.10:18760', rewrite: ['/0207/api', '/api'] },
{ key: '/0208/api', target: 'http://10.14.141.10:18760', rewrite: ['/0208/api', '/api'] },
{ key: '/0209/api', target: 'http://10.14.143.10:18760', rewrite: ['/0209/api', '/api'] },
{ key: '/0210/api', target: 'http://10.14.145.10:18760', rewrite: ['/0210/api', '/api'] },
{ key: '/0211/api', target: 'http://10.14.147.10:18760', rewrite: ['/0211/api', '/api'] },
{ key: '/0212/api', target: 'http://10.14.149.10:18760', rewrite: ['/0212/api', '/api'] },
{ key: '/0213/api', target: 'http://10.14.151.10:18760', rewrite: ['/0213/api', '/api'] },
{ key: '/0214/api', target: 'http://10.14.153.10:18760', rewrite: ['/0214/api', '/api'] },
{ key: '/0215/api', target: 'http://10.14.155.10:18760', rewrite: ['/0215/api', '/api'] },
{ key: '/0216/api', target: 'http://10.14.157.10:18760', rewrite: ['/0216/api', '/api'] },
{ key: '/0217/api', target: 'http://10.14.159.10:18760', rewrite: ['/0217/api', '/api'] },
{ key: '/0224/api', target: 'http://10.14.161.10:18760', rewrite: ['/0224/api', '/api'] },
{ key: '/0225/api', target: 'http://10.14.163.10:18760', rewrite: ['/0225/api', '/api'] },
{ key: '/0226/api', target: 'http://10.14.165.10:18760', rewrite: ['/0226/api', '/api'] },
{ key: '/0227/api', target: 'http://10.14.167.10:18760', rewrite: ['/0227/api', '/api'] },
{ key: '/0228/api', target: 'http://10.14.169.10:18760', rewrite: ['/0228/api', '/api'] },
{ key: '/0229/api', target: 'http://10.14.171.10:18760', rewrite: ['/0229/api', '/api'] },
{ key: '/0230/api', target: 'http://10.14.173.10:18760', rewrite: ['/0230/api', '/api'] },
{ key: '/0231/api', target: 'http://10.14.175.10:18760', rewrite: ['/0231/api', '/api'] },
{ key: '/0232/api', target: 'http://10.14.177.10:18760', rewrite: ['/0232/api', '/api'] },
{ key: '/0233/api', target: 'http://10.14.179.10:18760', rewrite: ['/0233/api', '/api'] },
{ key: '/0234/api', target: 'http://10.14.181.10:18760', rewrite: ['/0234/api', '/api'] },
{ key: '/0235/api', target: 'http://10.14.183.10:18760', rewrite: ['/0235/api', '/api'] },
{ key: '/0236/api', target: 'http://10.14.185.10:18760', rewrite: ['/0236/api', '/api'] },
{ key: '/0237/api', target: 'http://10.14.187.10:18760', rewrite: ['/0237/api', '/api'] },
{ key: '/0238/api', target: 'http://10.14.191.10:18760', rewrite: ['/0238/api', '/api'] },
{ key: '/0280/api', target: 'http://10.14.244.10:18760', rewrite: ['/0280/api', '/api'] },
{ key: '/0281/api', target: 'http://10.14.248.10:18760', rewrite: ['/0281/api', '/api'] },
{ key: '/0282/api', target: 'http://10.14.252.10:18760', rewrite: ['/0282/api', '/api'] },
];
const line04ApiProxyList: ProxyItem[] = [ const line04ApiProxyList: ProxyItem[] = [
{ key: '/0475/api', target: 'http://10.15.128.10:18760', rewrite: ['/0475/api', '/api'] }, { key: '/0475/api', target: 'http://10.15.128.10:18760', rewrite: ['/0475/api', '/api'] },
{ key: '/0480/api', target: 'http://10.15.244.10:18760', rewrite: ['/0480/api', '/api'] }, { key: '/0480/api', target: 'http://10.15.244.10:18760', rewrite: ['/0480/api', '/api'] },
@@ -143,6 +181,11 @@ const apiProxyList: ProxyItem[] = [
// { key: '/ws', target: 'ws://10.14.0.10:18103', ws: true }, // { key: '/ws', target: 'ws://10.14.0.10:18103', ws: true },
...line01ApiProxyList, ...line01ApiProxyList,
// { key: '/minio', target: 'http://10.14.128.10:9000', rewrite: ['/minio', ''] },
// { key: '/api', target: 'http://10.14.128.10:18760' },
// { key: '/ws', target: 'ws://10.14.128.10:18103', ws: true },
...line02ApiProxyList,
// { key: '/minio', target: 'http://10.15.128.10:9000', rewrite: ['/minio', ''] }, // { key: '/minio', target: 'http://10.15.128.10:9000', rewrite: ['/minio', ''] },
// { key: '/api', target: 'http://10.15.128.10:18760' }, // { key: '/api', target: 'http://10.15.128.10:18760' },
// { key: '/ws', target: 'ws://10.15.128.10:18103', ws: true }, // { key: '/ws', target: 'ws://10.15.128.10:18103', ws: true },