Compare commits

2 Commits

Author SHA1 Message Date
yangsy
1c71151a6b docs: 更新README.md 2026-03-02 15:39:06 +08:00
yangsy
68c5d12e14 chore: 版本信息和更新日志 2026-03-02 15:24:15 +08:00
5 changed files with 14 additions and 80 deletions

View File

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

View File

@@ -1,4 +1,4 @@
{ {
"version": "0.39.0", "version": "0.39.0",
"buildTime": "2026-03-11 14:35:45" "buildTime": "2026-03-02 15:14:53"
} }

View File

@@ -626,14 +626,9 @@ 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>
<template v-if="dailyCheckContext.info.percent > 0"> <div>缺失时长:{{ formatDuration(dailyCheckContext.info.total, { withinDay: true }) }}</div>
<div>缺失时长{{ formatDuration(dailyCheckContext.info.total, { withinDay: true }) }}</div> <div>缺失比例{{ dailyCheckContext.info.percent.toFixed(2) }}%</div>
<div>缺失比例:{{ dailyCheckContext.info.percent.toFixed(2) }}%</div> <div v-if="dailyCheckContext.info.percent > 0" style="font-size: xx-small; opacity: 0.5; cursor: pointer" @click="onClickDailyCheck">点击查看详情</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,19 +11,15 @@ 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,
@@ -352,7 +348,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.name}${device.ipAddress}`, key: `${device.id}`,
prefix: () => renderDeviceNodePrefix(device, stationCode), prefix: () => renderDeviceNodePrefix(device, stationCode),
suffix: () => `${device.ipAddress}`, suffix: () => `${device.ipAddress}`,
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站 // 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
@@ -386,13 +382,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.name}${device.ipAddress}`, key: `${device.id}`,
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.name}${device.ipAddress}`, key: `${device.id}`,
prefix: () => renderDeviceNodePrefix(device, stationCode), prefix: () => renderDeviceNodePrefix(device, stationCode),
suffix: () => `${device.ipAddress}`, suffix: () => `${device.ipAddress}`,
stationCode, stationCode,
@@ -414,7 +410,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.name}${device.ipAddress}`, key: `${device.id}`,
prefix: () => renderDeviceNodePrefix(device, stationCode), prefix: () => renderDeviceNodePrefix(device, stationCode),
suffix: () => `${device.ipAddress}`, suffix: () => `${device.ipAddress}`,
stationCode, stationCode,
@@ -429,26 +425,20 @@ 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, type: typeInput.value, status: statusInput.value }); return JSON.stringify({ search: searchInput.value, status: statusInput.value });
}); });
const searchFilter = (pattern: string, node: TreeOption): boolean => { const searchFilter = (pattern: string, node: TreeOption): boolean => {
const { search, type, status } = destr<{ search: string; type: SearchType; status: string }>(pattern); const { search, status } = destr<{ search: string; status: string }>(pattern);
const device = node['device'] as NdmDeviceResultVO | undefined; const device = node['device'] as NdmDeviceResultVO | undefined;
const { deviceStatus } = device ?? {}; const { name, ipAddress, deviceId, deviceStatus } = device ?? {};
const searchMatched = !!device?.[type]?.includes(search); const searchMatched = (name ?? '').includes(search) || (ipAddress ?? '').includes(search) || (deviceId ?? '').includes(search);
const statusMatched = status === '' || status === deviceStatus; const statusMatched = status === '' || status === deviceStatus;
return searchMatched && statusMatched; return searchMatched && statusMatched;
}; };
@@ -533,14 +523,7 @@ 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">
<NGrid :cols="10" :x-gap="8"> <NInput v-model:value="searchInput" placeholder="搜索设备名称、设备ID或IP地址" clearable />
<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,44 +76,6 @@ 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'] },
@@ -181,11 +143,6 @@ 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 },