Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20513557d7 |
@@ -1,271 +0,0 @@
|
|||||||
# 网络设备管理平台
|
|
||||||
|
|
||||||
## 项目概述
|
|
||||||
|
|
||||||
这是网络设备管理平台的前端项目,用于在地铁线路运管中心检测和查看各车站网络设备的详细数据、运行情况、异常告警,并提供分析、统计、日志和权限管理能力。
|
|
||||||
|
|
||||||
主要设备类型:
|
|
||||||
|
|
||||||
- 摄像机
|
|
||||||
- 网络录像机
|
|
||||||
- 交换机
|
|
||||||
- 解码器
|
|
||||||
- 智能安防箱
|
|
||||||
- 媒体服务器
|
|
||||||
- 视频服务器
|
|
||||||
- 网络键盘
|
|
||||||
- 报警主机
|
|
||||||
|
|
||||||
## 技术栈
|
|
||||||
|
|
||||||
- 包管理:pnpm
|
|
||||||
- 构建工具:Vite
|
|
||||||
- 前端框架:Vue 3
|
|
||||||
- 语言:TypeScript
|
|
||||||
- 路由:Vue Router
|
|
||||||
- 组件库:Naive UI
|
|
||||||
- 状态管理:Pinia + pinia-plugin-persistedstate
|
|
||||||
- 服务端状态/轮询:TanStack Vue Query
|
|
||||||
- 本地持久化:localStorage、sessionStorage、IndexedDB/localforage
|
|
||||||
- 网络请求:axios
|
|
||||||
- 实时消息:STOMP/WebSocket(`@stomp/stompjs`)
|
|
||||||
- 图表:ECharts
|
|
||||||
- 图标:lucide-vue-next
|
|
||||||
- 样式:Sass
|
|
||||||
|
|
||||||
## 环境与脚本
|
|
||||||
|
|
||||||
### 运行环境
|
|
||||||
|
|
||||||
- Node.js:`^20.19.0 || >=22.12.0`
|
|
||||||
- pnpm:以 `package.json` 中 `packageManager` 为准
|
|
||||||
|
|
||||||
### 常用命令
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm install # 安装依赖
|
|
||||||
pnpm dev # 启动开发服务,默认端口 9763
|
|
||||||
pnpm build # 类型检查 + Vite 构建 + 构建产物压缩
|
|
||||||
pnpm preview # 预览构建产物
|
|
||||||
pnpm build-only # 仅执行 Vite 构建
|
|
||||||
pnpm type-check # 执行 vue-tsc 类型检查
|
|
||||||
pnpm lint # 执行 ESLint,并带 --fix
|
|
||||||
pnpm format # 使用 Prettier 格式化 src/
|
|
||||||
```
|
|
||||||
|
|
||||||
当前项目未配置测试脚本,也未发现测试文件;不要声称已运行单元测试。需要验证改动时,优先运行 `pnpm type-check`、`pnpm lint`、`pnpm build` 中与改动相关的命令。
|
|
||||||
|
|
||||||
### 构建流程
|
|
||||||
|
|
||||||
`pnpm build` 的实际流程为:
|
|
||||||
|
|
||||||
1. `tsx build/pre-build.ts`:根据 `package.json` 的版本和构建时间写入 `public/manifest.json`。
|
|
||||||
2. `vue-tsc --build`:类型检查。
|
|
||||||
3. `vite build`:生成 `dist/`。
|
|
||||||
4. `tsx build/post-build.ts`:基于 `dist/` 生成 `.zip`、`.tar`、`.tar.gz` 压缩包。
|
|
||||||
|
|
||||||
压缩包命名格式为:`ndm-web-platform_v<version>_<YYMMDD-HHmmss>`。
|
|
||||||
|
|
||||||
## 目录结构
|
|
||||||
|
|
||||||
项目源码集中在 `src/`:
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/
|
|
||||||
apis/ # 接口客户端、接口模型、业务请求封装
|
|
||||||
components/ # 业务组件与全局组件
|
|
||||||
device/
|
|
||||||
global/
|
|
||||||
permission/
|
|
||||||
station/
|
|
||||||
composables/ # 组合式函数
|
|
||||||
alarm/
|
|
||||||
common/
|
|
||||||
device/
|
|
||||||
permission/
|
|
||||||
query/ # TanStack Query 轮询与请求编排
|
|
||||||
station/
|
|
||||||
stomp/ # STOMP/WebSocket 客户端
|
|
||||||
constants/ # 常量
|
|
||||||
enums/ # 枚举
|
|
||||||
helpers/ # 辅助逻辑
|
|
||||||
layouts/
|
|
||||||
app-layout.vue # 登录后主布局
|
|
||||||
pages/ # 页面
|
|
||||||
plugins/ # Pinia 持久化等插件
|
|
||||||
router/
|
|
||||||
index.ts # 路由配置与守卫
|
|
||||||
stores/ # Pinia stores
|
|
||||||
styles/ # 全局样式
|
|
||||||
types/ # 全局/工具类型
|
|
||||||
utils/ # 通用工具函数
|
|
||||||
```
|
|
||||||
|
|
||||||
路径别名:`@/*` 指向 `src/*`。
|
|
||||||
|
|
||||||
## 页面与路由
|
|
||||||
|
|
||||||
路由配置在 `src/router/index.ts`。登录页独立于主布局;除 `/login` 外,其余页面都作为 `src/layouts/app-layout.vue` 的子路由。
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/
|
|
||||||
router/
|
|
||||||
index.ts
|
|
||||||
layouts/
|
|
||||||
app-layout.vue
|
|
||||||
pages/
|
|
||||||
login/
|
|
||||||
login-page.vue # 登录页,对应 /login
|
|
||||||
station/
|
|
||||||
station-page.vue # 车站状态页/首页,对应 /station
|
|
||||||
device/
|
|
||||||
device-page.vue # 设备诊断页,对应 /device
|
|
||||||
alarm/
|
|
||||||
alarm-log-page.vue # 设备告警记录,对应 /alarm/alarm-log
|
|
||||||
alarm-ignore-page.vue # 告警忽略管理,对应 /alarm/alarm-ignore
|
|
||||||
log/
|
|
||||||
vimp-log-page.vue # 视频平台日志,对应 /log/vimp-log
|
|
||||||
call-log-page.vue # 上级调用日志,对应 /log/call-log
|
|
||||||
permission/
|
|
||||||
permission-page.vue # 权限管理,对应 /permission
|
|
||||||
system/
|
|
||||||
changelog/
|
|
||||||
changelog-page.vue # 更新记录,对应 /changelog
|
|
||||||
error/
|
|
||||||
not-found-page.vue # 404 页面,对应 catch-all
|
|
||||||
```
|
|
||||||
|
|
||||||
路由守卫规则:
|
|
||||||
|
|
||||||
- 未登录访问非 `/login` 页面会跳转到 `/login`。
|
|
||||||
- 已登录访问 `/login` 会跳转到 `/`。
|
|
||||||
- `/` 默认重定向到 `/station`。
|
|
||||||
- `/permission` 需要 `useUserStore().isLamp` 为真,否则跳转到 404。
|
|
||||||
|
|
||||||
## 数据轮询与状态管理
|
|
||||||
|
|
||||||
由于后端服务按车站分布,前端需要向各车站服务依次请求数据。项目采用“单点驱动 + 变更监听 + 级联触发”的轮询模式。
|
|
||||||
|
|
||||||
核心文件位于 `src/composables/query/`:
|
|
||||||
|
|
||||||
- `use-line-stations-query.ts`:查询所有车站,是业务轮询入口。
|
|
||||||
- `use-user-permission-query.ts`:查询并计算当前用户在各车站的权限,负责调度后续设备/告警查询。
|
|
||||||
- `use-line-devices-query.ts`:查询设备数据。
|
|
||||||
- `use-line-alarms-query.ts`:查询告警数据。
|
|
||||||
- `use-verify-user-query.ts`:用户登录/校验相关请求。
|
|
||||||
- `use-version-check-query.ts`:版本检查,依赖构建阶段生成的 `/manifest.json`。
|
|
||||||
|
|
||||||
关键 Pinia stores 位于 `src/stores/`:
|
|
||||||
|
|
||||||
- `user.ts`:用户登录态、Token、用户类型等。
|
|
||||||
- `station.ts`:车站数据。
|
|
||||||
- `device.ts`:设备数据。
|
|
||||||
- `alarm.ts`:告警数据。
|
|
||||||
- `permission.ts`:权限数据。
|
|
||||||
- `setting.ts`:系统设置、调试开关、网络开关等。
|
|
||||||
- `polling.ts`:轮询状态控制。
|
|
||||||
- `unread.ts`:未读状态。
|
|
||||||
|
|
||||||
持久化注意事项:
|
|
||||||
|
|
||||||
- 大体量业务数据会使用 IndexedDB/localforage。
|
|
||||||
- 普通设置类状态会使用 localStorage/sessionStorage。
|
|
||||||
- `src/main.ts` 会比较 `VITE_STORAGE_VERSION` 与本地 `ndm-storage-version`,不一致时清空 localStorage 并清空 localforage。
|
|
||||||
|
|
||||||
## 接口与代理
|
|
||||||
|
|
||||||
接口相关代码位于 `src/apis/`:
|
|
||||||
|
|
||||||
- `client/`:HTTP 客户端基础封装。
|
|
||||||
- `domain/`:领域相关类型/逻辑。
|
|
||||||
- `model/`:接口模型。
|
|
||||||
- `request/`:按业务拆分的请求函数。
|
|
||||||
|
|
||||||
开发代理配置在 `vite.config.ts`:
|
|
||||||
|
|
||||||
- 开发服务端口:`9763`。
|
|
||||||
- 已配置多个线路/站点前缀代理,包括 01、02、04、10、21 相关站点。
|
|
||||||
- 当前配置包含 `/api`、`/minio`、`/ws` 等代理项。
|
|
||||||
- `ProxyItem.rewrite` 用于将本地请求前缀改写为后端真实路径,例如将 `/1001/api` 改写为 `/api`。
|
|
||||||
|
|
||||||
修改代理时,应同步检查 `key`、`target`、`rewrite`、`ws` 是否匹配实际环境。
|
|
||||||
|
|
||||||
## 环境变量
|
|
||||||
|
|
||||||
项目使用 Vite 环境变量,当前变量集中在 `.env`。文档或回答中只列变量名和用途,不要复述真实密钥、密码或授权值。
|
|
||||||
|
|
||||||
常见变量:
|
|
||||||
|
|
||||||
- `VITE_APP_TITLE`:页面标题。
|
|
||||||
- `VITE_REQUEST_INTERVAL`:轮询间隔,单位秒。
|
|
||||||
- `VITE_NDM_APP_KEY`:网管 appKey。
|
|
||||||
- `VITE_LAMP_CLIENT_ID`:LAMP clientId。
|
|
||||||
- `VITE_LAMP_CLIENT_SECRET`:LAMP clientSecret。
|
|
||||||
- `VITE_LAMP_USERNAME`:LAMP 登录用户名。
|
|
||||||
- `VITE_LAMP_PASSWORD`:LAMP 登录密码。
|
|
||||||
- `VITE_LAMP_AUTHORIZATION`:已有 Authorization 时直接使用,否则由 clientId/clientSecret 生成。
|
|
||||||
- `VITE_STORAGE_VERSION`:本地缓存版本,用于触发缓存清理。
|
|
||||||
- `VITE_DEBUG_CODE`:调试模式授权码。
|
|
||||||
|
|
||||||
## 调试模式与离线开发
|
|
||||||
|
|
||||||
调试模式默认隐藏,用于开发、联调和故障排查。
|
|
||||||
|
|
||||||
开启方式:
|
|
||||||
|
|
||||||
1. 使用快捷键 `Ctrl + Alt + D` 唤起验证弹窗。
|
|
||||||
2. 输入 `VITE_DEBUG_CODE` 对应授权码。
|
|
||||||
3. 验证通过后,“系统设置”中会显示调试分组。
|
|
||||||
|
|
||||||
调试相关能力:
|
|
||||||
|
|
||||||
- 显示设备原始数据。
|
|
||||||
- 控制是否轮询车站。
|
|
||||||
- 控制是否主动请求。
|
|
||||||
- 控制是否订阅 STOMP/WebSocket 消息。
|
|
||||||
- 启用模拟用户。
|
|
||||||
- 允许在特定场景下直接操作本地 IndexedDB。
|
|
||||||
|
|
||||||
离线开发:
|
|
||||||
|
|
||||||
- 如果浏览器已有现场缓存,可在调试模式中关闭轮询、主动请求和消息订阅,直接查看本地缓存。
|
|
||||||
- 全新环境可在登录页控制台设置 `window.$mockUser.value = true` 进入模拟登录,再通过调试面板导入 `docs/data/` 下的数据:
|
|
||||||
- `ndm-station-store.json`
|
|
||||||
- `ndm-device-store.json`
|
|
||||||
- `ndm-alarm-store.json`
|
|
||||||
|
|
||||||
## 代码风格与约定
|
|
||||||
|
|
||||||
- 遵循现有 Vue SFC、TypeScript、组合式函数和 Pinia 写法。
|
|
||||||
- 新增业务请求时优先放入 `src/apis/request/` 对应业务分类。
|
|
||||||
- 新增页面时同步更新 `src/router/index.ts`,并保持 `src/pages/` 目录与路由语义一致。
|
|
||||||
- 新增共享逻辑优先放入 `src/composables/`、`src/helpers/` 或 `src/utils/`,不要在页面中堆积重复逻辑。
|
|
||||||
- 新增全局/业务组件时优先放入 `src/components/` 对应分类。
|
|
||||||
- 使用 `@/` 引用 `src/` 下模块。
|
|
||||||
- 不要在代码、文档或回复中泄露真实密码、Token、Authorization 或现场地址以外的敏感信息。
|
|
||||||
|
|
||||||
格式化配置来自 `.prettierrc.json`:
|
|
||||||
|
|
||||||
- LF 换行
|
|
||||||
- 2 空格缩进
|
|
||||||
- 单引号
|
|
||||||
- 使用分号
|
|
||||||
- `trailingComma: all`
|
|
||||||
- `printWidth: 200`
|
|
||||||
|
|
||||||
ESLint 重点规则:
|
|
||||||
|
|
||||||
- `@typescript-eslint/no-unused-vars` 为 warn。
|
|
||||||
- `@typescript-eslint/no-explicit-any` 当前关闭,但新增代码仍应尽量保持类型清晰。
|
|
||||||
- `vue/multi-word-component-names` 当前关闭。
|
|
||||||
|
|
||||||
## 协作规则
|
|
||||||
|
|
||||||
1. 默认不要直接修改项目代码;当用户明确授权修改时,只修改授权范围内的文件。
|
|
||||||
2. 修改前先阅读相关文件和既有实现,避免凭空猜测目录、接口或状态结构。
|
|
||||||
3. 修改后说明改了哪些文件、为什么改、如何验证。
|
|
||||||
4. 文档类修改至少重新读取目标文件确认内容正确。
|
|
||||||
5. 代码类修改应按影响范围运行 `pnpm type-check`、`pnpm lint`、`pnpm build` 或更小范围的可用验证命令。
|
|
||||||
6. 不要提交 Git commit,除非用户明确要求。
|
|
||||||
7. 不要删除失败测试或通过压制类型错误来规避问题。
|
|
||||||
8. 不要在最终说明中声称执行了未实际执行的命令。
|
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ndm-web-platform",
|
"name": "ndm-web-platform",
|
||||||
"version": "0.0.0",
|
"version": "0.40.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -1,40 +1,4 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"version": "0.42.0",
|
|
||||||
"date": "2026-05-20",
|
|
||||||
"changes": {
|
|
||||||
"fixes": [
|
|
||||||
{ "content": "优化安防箱环境数据卡片,避免空标签渲染并改进风扇信息展示" },
|
|
||||||
{ "content": "修复设备硬件卡片进度条异常值显示问题,并完善状态判断逻辑" }
|
|
||||||
],
|
|
||||||
"feats": [
|
|
||||||
{ "content": "新增服务器网卡信息展示" },
|
|
||||||
{ "content": "新增安防箱网卡信息展示,并优化相关卡片标题" },
|
|
||||||
{ "content": "为交换机诊断信息新增温度字段" },
|
|
||||||
{ "content": "新增录像机环境状态卡片和网卡信息展示" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "0.41.0",
|
|
||||||
"date": "2026-05-19",
|
|
||||||
"changes": {
|
|
||||||
"fixes": [
|
|
||||||
{ "content": "修复交换机端口卡片在端口数据为空时仍展示的问题" },
|
|
||||||
{ "content": "修复安防箱门禁状态和防雷状态显示错误的问题" },
|
|
||||||
{ "content": "修复解码器和录像机历史诊断中硬件占用卡片的加载状态同步问题" }
|
|
||||||
],
|
|
||||||
"feats": [
|
|
||||||
{ "content": "新增摄像机阈值配置功能" },
|
|
||||||
{ "content": "新增摄像机硬件占用率历史诊断记录卡片" },
|
|
||||||
{ "content": "优化设备诊断页的信息展示结构,支持按设备型号、网卡信息、接入信息等分组展示" },
|
|
||||||
{ "content": "为摄像机诊断信息新增网卡信息、IP信息和设备通用信息展示" },
|
|
||||||
{ "content": "新增多厂商安防箱支持,支持BeiDian和NingTech安防箱的空开控制与设备重启" },
|
|
||||||
{ "content": "安防箱配置新增开关数量和团体字符串(写)" },
|
|
||||||
{ "content": "为各类设备实体新增团体字符串(写)字段" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"version": "0.40.0",
|
"version": "0.40.0",
|
||||||
"date": "2026-04-10",
|
"date": "2026-04-10",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"version": "0.42.0",
|
"version": "0.40.0",
|
||||||
"buildTime": "2026-05-20 13:52:07"
|
"buildTime": "2026-04-10 15:42:03"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
"001": { "name": "1号线", "color": "#E81B38" },
|
|
||||||
"002": { "name": "2号线", "color": "#8AC63F" },
|
|
||||||
"003": { "name": "3号线", "color": "#FBD005" },
|
|
||||||
"004": { "name": "4号线", "color": "#4F2D8B" },
|
|
||||||
"005": { "name": "5号线", "color": "#9056A3" },
|
|
||||||
"006": { "name": "6号线", "color": "#D61670" },
|
|
||||||
"007": { "name": "7号线", "color": "#F37120" },
|
|
||||||
"008": { "name": "8号线", "color": "#009DD8" },
|
|
||||||
"009": { "name": "9号线", "color": "#7AC7EA" },
|
|
||||||
"010": { "name": "10号线", "color": "#BCA8D1" },
|
|
||||||
"011": { "name": "11号线", "color": "#7D2030" },
|
|
||||||
"012": { "name": "12号线", "color": "#007C65" },
|
|
||||||
"013": { "name": "13号线", "color": "#E795C0" },
|
|
||||||
"014": { "name": "14号线", "color": "#5E5C29" },
|
|
||||||
"015": { "name": "15号线", "color": "#759092" },
|
|
||||||
"016": { "name": "16号线", "color": "#8ED1C0" },
|
|
||||||
"017": { "name": "17号线", "color": "#B87975" },
|
|
||||||
"018": { "name": "18号线", "color": "#BB8C51" },
|
|
||||||
"019": { "name": "19号线", "color": "#BABABA" },
|
|
||||||
"020": { "name": "20号线", "color": "#BABABA" },
|
|
||||||
"021": { "name": "21号线", "color": "#BABABA" },
|
|
||||||
"022": { "name": "22号线", "color": "#BABABA" },
|
|
||||||
"023": { "name": "23号线", "color": "#BABABA" },
|
|
||||||
"024": { "name": "24号线", "color": "#BABABA" },
|
|
||||||
"025": { "name": "25号线", "color": "#BABABA" },
|
|
||||||
"051": { "name": "浦江线", "color": "#BABABA" },
|
|
||||||
"501": { "name": "COCC", "color": "#BABABA" },
|
|
||||||
"502": { "name": "BCOCC", "color": "#BABABA" },
|
|
||||||
"601": { "name": "上海火车站分控", "color": "#BABABA" },
|
|
||||||
"602": { "name": "徐家汇分控", "color": "#BABABA" },
|
|
||||||
"603": { "name": "宜山路分控", "color": "#BABABA" },
|
|
||||||
"604": { "name": "陆家嘴分控", "color": "#BABABA" },
|
|
||||||
"605": { "name": "人民广场分控", "color": "#BABABA" },
|
|
||||||
"606": { "name": "东宝兴路分控", "color": "#BABABA" },
|
|
||||||
"607": { "name": "虹桥枢纽分控", "color": "#BABABA" },
|
|
||||||
"608": { "name": "松江大学城分控", "color": "#BABABA" },
|
|
||||||
"609": { "name": "民生路分控", "color": "#BABABA" },
|
|
||||||
"610": { "name": "西藏南路分控", "color": "#BABABA" },
|
|
||||||
"611": { "name": "延吉中路分控", "color": "#BABABA" },
|
|
||||||
"612": { "name": "迪士尼分控", "color": "#BABABA" },
|
|
||||||
"900": { "name": "轨交总队", "color": "#BABABA" }
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"code": "01",
|
|
||||||
"name": "地下区",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "01001", "name": "地下01层" },
|
|
||||||
{ "code": "01002", "name": "地下02层" },
|
|
||||||
{ "code": "01003", "name": "地下03层" },
|
|
||||||
{ "code": "01004", "name": "地下04层" },
|
|
||||||
{ "code": "01005", "name": "地下05层" },
|
|
||||||
{ "code": "01006", "name": "地下06层" },
|
|
||||||
{ "code": "01007", "name": "地下07层" },
|
|
||||||
{ "code": "01008", "name": "地下08层" },
|
|
||||||
{ "code": "01009", "name": "地下09层" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "02",
|
|
||||||
"name": "调度大厅",
|
|
||||||
"subs": [{ "code": "02001", "name": "调度大厅" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "03",
|
|
||||||
"name": "一区",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "03001", "name": "01层" },
|
|
||||||
{ "code": "03002", "name": "02层" },
|
|
||||||
{ "code": "03003", "name": "03层" },
|
|
||||||
{ "code": "03004", "name": "04层" },
|
|
||||||
{ "code": "03005", "name": "05层" },
|
|
||||||
{ "code": "03006", "name": "06层" },
|
|
||||||
{ "code": "03007", "name": "07层" },
|
|
||||||
{ "code": "03008", "name": "08层" },
|
|
||||||
{ "code": "03009", "name": "09层" },
|
|
||||||
{ "code": "03010", "name": "10层" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "04",
|
|
||||||
"name": "二区",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "04001", "name": "11层" },
|
|
||||||
{ "code": "04002", "name": "12层" },
|
|
||||||
{ "code": "04003", "name": "13层" },
|
|
||||||
{ "code": "04004", "name": "14层" },
|
|
||||||
{ "code": "04005", "name": "15层" },
|
|
||||||
{ "code": "04006", "name": "16层" },
|
|
||||||
{ "code": "04007", "name": "17层" },
|
|
||||||
{ "code": "04008", "name": "18层" },
|
|
||||||
{ "code": "04009", "name": "19层" },
|
|
||||||
{ "code": "04010", "name": "20层" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "05",
|
|
||||||
"name": "三区",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "05001", "name": "21层" },
|
|
||||||
{ "code": "05002", "name": "22层" },
|
|
||||||
{ "code": "05003", "name": "23层" },
|
|
||||||
{ "code": "05004", "name": "24层" },
|
|
||||||
{ "code": "05005", "name": "25层" },
|
|
||||||
{ "code": "05006", "name": "26层" },
|
|
||||||
{ "code": "05007", "name": "27层" },
|
|
||||||
{ "code": "05008", "name": "28层" },
|
|
||||||
{ "code": "05009", "name": "29层" },
|
|
||||||
{ "code": "05010", "name": "30层" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "06",
|
|
||||||
"name": "四区",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "06001", "name": "31层" },
|
|
||||||
{ "code": "06002", "name": "32层" },
|
|
||||||
{ "code": "06003", "name": "33层" },
|
|
||||||
{ "code": "06004", "name": "34层" },
|
|
||||||
{ "code": "06005", "name": "35层" },
|
|
||||||
{ "code": "06006", "name": "36层" },
|
|
||||||
{ "code": "06007", "name": "37层" },
|
|
||||||
{ "code": "06008", "name": "38层" },
|
|
||||||
{ "code": "06009", "name": "39层" },
|
|
||||||
{ "code": "06010", "name": "40层" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "07",
|
|
||||||
"name": "五区",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "07001", "name": "41层" },
|
|
||||||
{ "code": "07002", "name": "42层" },
|
|
||||||
{ "code": "07003", "name": "43层" },
|
|
||||||
{ "code": "07004", "name": "44层" },
|
|
||||||
{ "code": "07005", "name": "45层" },
|
|
||||||
{ "code": "07006", "name": "46层" },
|
|
||||||
{ "code": "07007", "name": "47层" },
|
|
||||||
{ "code": "07008", "name": "48层" },
|
|
||||||
{ "code": "07009", "name": "49层" },
|
|
||||||
{ "code": "07010", "name": "50层" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,378 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"code": "01",
|
|
||||||
"name": "停车库",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "01001", "name": "库门01" },
|
|
||||||
{ "code": "01002", "name": "库门02" },
|
|
||||||
{ "code": "01003", "name": "库门03" },
|
|
||||||
{ "code": "01004", "name": "库门04" },
|
|
||||||
{ "code": "01005", "name": "库门05" },
|
|
||||||
{ "code": "01006", "name": "库门06" },
|
|
||||||
{ "code": "01007", "name": "库门07" },
|
|
||||||
{ "code": "01008", "name": "库门08" },
|
|
||||||
{ "code": "01009", "name": "库门09" },
|
|
||||||
{ "code": "01010", "name": "库门10" },
|
|
||||||
{ "code": "01011", "name": "库门11" },
|
|
||||||
{ "code": "01012", "name": "库门12" },
|
|
||||||
{ "code": "01013", "name": "库门13" },
|
|
||||||
{ "code": "01014", "name": "库门14" },
|
|
||||||
{ "code": "01015", "name": "库门15" },
|
|
||||||
{ "code": "01016", "name": "库门16" },
|
|
||||||
{ "code": "01017", "name": "库门17" },
|
|
||||||
{ "code": "01018", "name": "库门18" },
|
|
||||||
{ "code": "01019", "name": "库门19" },
|
|
||||||
{ "code": "01020", "name": "库门20" },
|
|
||||||
{ "code": "01031", "name": "库门31" },
|
|
||||||
{ "code": "01032", "name": "库门32" },
|
|
||||||
{ "code": "01033", "name": "库门33" },
|
|
||||||
{ "code": "01034", "name": "库门34" },
|
|
||||||
{ "code": "01035", "name": "库门35" },
|
|
||||||
{ "code": "01036", "name": "库门36" },
|
|
||||||
{ "code": "01037", "name": "库门37" },
|
|
||||||
{ "code": "01038", "name": "库门38" },
|
|
||||||
{ "code": "01039", "name": "库门39" },
|
|
||||||
{ "code": "01040", "name": "库门40" },
|
|
||||||
{ "code": "01041", "name": "库门41" },
|
|
||||||
{ "code": "01042", "name": "库门42" },
|
|
||||||
{ "code": "01043", "name": "库门43" },
|
|
||||||
{ "code": "01044", "name": "库门44" },
|
|
||||||
{ "code": "01045", "name": "库门45" },
|
|
||||||
{ "code": "01046", "name": "库门46" },
|
|
||||||
{ "code": "01047", "name": "库门47" },
|
|
||||||
{ "code": "01048", "name": "库门48" },
|
|
||||||
{ "code": "01049", "name": "库门49" },
|
|
||||||
{ "code": "01050", "name": "库门50" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "02",
|
|
||||||
"name": "其它车库",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "02001", "name": "检修库01" },
|
|
||||||
{ "code": "02002", "name": "检修库02" },
|
|
||||||
{ "code": "02003", "name": "检修库03" },
|
|
||||||
{ "code": "02004", "name": "检修库04" },
|
|
||||||
{ "code": "02005", "name": "检修库05" },
|
|
||||||
{ "code": "02006", "name": "洗车库01" },
|
|
||||||
{ "code": "02007", "name": "洗车库02" },
|
|
||||||
{ "code": "02008", "name": "洗车库03" },
|
|
||||||
{ "code": "02009", "name": "洗车库04" },
|
|
||||||
{ "code": "02010", "name": "洗车库05" },
|
|
||||||
{ "code": "02011", "name": "镟轮库01" },
|
|
||||||
{ "code": "02012", "name": "镟轮库02" },
|
|
||||||
{ "code": "02013", "name": "镟轮库03" },
|
|
||||||
{ "code": "02014", "name": "镟轮库04" },
|
|
||||||
{ "code": "02015", "name": "镟轮库05" },
|
|
||||||
{ "code": "02016", "name": "地下机动车库01" },
|
|
||||||
{ "code": "02017", "name": "地下机动车库02" },
|
|
||||||
{ "code": "02018", "name": "地下机动车库03" },
|
|
||||||
{ "code": "02019", "name": "地下机动车库04" },
|
|
||||||
{ "code": "02020", "name": "地下机动车库05" },
|
|
||||||
{ "code": "02021", "name": "特种车库01" },
|
|
||||||
{ "code": "02022", "name": "特种车库02" },
|
|
||||||
{ "code": "02023", "name": "特种车库03" },
|
|
||||||
{ "code": "02024", "name": "特种车库04" },
|
|
||||||
{ "code": "02025", "name": "特种车库05" },
|
|
||||||
{ "code": "02026", "name": "联合车库01" },
|
|
||||||
{ "code": "02027", "name": "联合车库02" },
|
|
||||||
{ "code": "02028", "name": "联合车库03" },
|
|
||||||
{ "code": "02029", "name": "联合车库04" },
|
|
||||||
{ "code": "02030", "name": "联合车库05" },
|
|
||||||
{ "code": "02031", "name": "静调库01" },
|
|
||||||
{ "code": "02032", "name": "静调库02" },
|
|
||||||
{ "code": "02033", "name": "静调库03" },
|
|
||||||
{ "code": "02034", "name": "静调库04" },
|
|
||||||
{ "code": "02035", "name": "静调库05" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "03",
|
|
||||||
"name": "办公楼",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "03001", "name": "信号楼01" },
|
|
||||||
{ "code": "03002", "name": "信号楼02" },
|
|
||||||
{ "code": "03003", "name": "信号楼03" },
|
|
||||||
{ "code": "03004", "name": "信号楼04" },
|
|
||||||
{ "code": "03005", "name": "信号楼05" },
|
|
||||||
{ "code": "03006", "name": "信号楼06" },
|
|
||||||
{ "code": "03007", "name": "信号楼07" },
|
|
||||||
{ "code": "03008", "name": "信号楼08" },
|
|
||||||
{ "code": "03009", "name": "信号楼09" },
|
|
||||||
{ "code": "03010", "name": "信号楼10" },
|
|
||||||
{ "code": "03011", "name": "办公楼01" },
|
|
||||||
{ "code": "03012", "name": "办公楼02" },
|
|
||||||
{ "code": "03013", "name": "办公楼03" },
|
|
||||||
{ "code": "03014", "name": "办公楼04" },
|
|
||||||
{ "code": "03015", "name": "办公楼05" },
|
|
||||||
{ "code": "03016", "name": "办公楼06" },
|
|
||||||
{ "code": "03017", "name": "办公楼07" },
|
|
||||||
{ "code": "03018", "name": "办公楼08" },
|
|
||||||
{ "code": "03019", "name": "办公楼09" },
|
|
||||||
{ "code": "03020", "name": "办公楼10" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "04",
|
|
||||||
"name": "出入口",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "04001", "name": "出入口01" },
|
|
||||||
{ "code": "04002", "name": "出入口02" },
|
|
||||||
{ "code": "04003", "name": "出入口03" },
|
|
||||||
{ "code": "04004", "name": "出入口04" },
|
|
||||||
{ "code": "04005", "name": "出入口05" },
|
|
||||||
{ "code": "04006", "name": "喉部01" },
|
|
||||||
{ "code": "04007", "name": "喉部02" },
|
|
||||||
{ "code": "04008", "name": "喉部03" },
|
|
||||||
{ "code": "04009", "name": "喉部04" },
|
|
||||||
{ "code": "04010", "name": "喉部05" },
|
|
||||||
{ "code": "04011", "name": "峒口01" },
|
|
||||||
{ "code": "04012", "name": "峒口02" },
|
|
||||||
{ "code": "04013", "name": "峒口03" },
|
|
||||||
{ "code": "04014", "name": "峒口04" },
|
|
||||||
{ "code": "04015", "name": "峒口05" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "05",
|
|
||||||
"name": "周界",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "05001", "name": "周界01" },
|
|
||||||
{ "code": "05002", "name": "周界02" },
|
|
||||||
{ "code": "05003", "name": "周界03" },
|
|
||||||
{ "code": "05004", "name": "周界04" },
|
|
||||||
{ "code": "05005", "name": "周界05" },
|
|
||||||
{ "code": "05006", "name": "周界06" },
|
|
||||||
{ "code": "05007", "name": "周界07" },
|
|
||||||
{ "code": "05008", "name": "周界08" },
|
|
||||||
{ "code": "05009", "name": "周界09" },
|
|
||||||
{ "code": "05010", "name": "周界10" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "06",
|
|
||||||
"name": "仓库",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "06001", "name": "危险物品仓库01" },
|
|
||||||
{ "code": "06002", "name": "危险物品仓库02" },
|
|
||||||
{ "code": "06003", "name": "危险物品仓库03" },
|
|
||||||
{ "code": "06004", "name": "危险物品仓库04" },
|
|
||||||
{ "code": "06005", "name": "危险物品仓库05" },
|
|
||||||
{ "code": "06006", "name": "物流仓库01" },
|
|
||||||
{ "code": "06007", "name": "物流仓库02" },
|
|
||||||
{ "code": "06008", "name": "物流仓库03" },
|
|
||||||
{ "code": "06009", "name": "物流仓库04" },
|
|
||||||
{ "code": "06010", "name": "物流仓库05" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "07",
|
|
||||||
"name": "道路",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "07001", "name": "道路01" },
|
|
||||||
{ "code": "07002", "name": "道路02" },
|
|
||||||
{ "code": "07003", "name": "道路03" },
|
|
||||||
{ "code": "07004", "name": "道路04" },
|
|
||||||
{ "code": "07005", "name": "道路05" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "08",
|
|
||||||
"name": "其它",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "08001", "name": "变电站01" },
|
|
||||||
{ "code": "08002", "name": "变电站02" },
|
|
||||||
{ "code": "08003", "name": "变电站03" },
|
|
||||||
{ "code": "08004", "name": "变电站04" },
|
|
||||||
{ "code": "08005", "name": "变电站05" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "11",
|
|
||||||
"name": "停车库(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "11001", "name": "库门01" },
|
|
||||||
{ "code": "11002", "name": "库门02" },
|
|
||||||
{ "code": "11003", "name": "库门03" },
|
|
||||||
{ "code": "11004", "name": "库门04" },
|
|
||||||
{ "code": "11005", "name": "库门05" },
|
|
||||||
{ "code": "11006", "name": "库门06" },
|
|
||||||
{ "code": "11007", "name": "库门07" },
|
|
||||||
{ "code": "11008", "name": "库门08" },
|
|
||||||
{ "code": "11009", "name": "库门09" },
|
|
||||||
{ "code": "11010", "name": "库门10" },
|
|
||||||
{ "code": "11011", "name": "库门11" },
|
|
||||||
{ "code": "11012", "name": "库门12" },
|
|
||||||
{ "code": "11013", "name": "库门13" },
|
|
||||||
{ "code": "11014", "name": "库门14" },
|
|
||||||
{ "code": "11015", "name": "库门15" },
|
|
||||||
{ "code": "11016", "name": "库门16" },
|
|
||||||
{ "code": "11017", "name": "库门17" },
|
|
||||||
{ "code": "11018", "name": "库门18" },
|
|
||||||
{ "code": "11019", "name": "库门19" },
|
|
||||||
{ "code": "11020", "name": "库门20" },
|
|
||||||
{ "code": "11031", "name": "库门31" },
|
|
||||||
{ "code": "11032", "name": "库门32" },
|
|
||||||
{ "code": "11033", "name": "库门33" },
|
|
||||||
{ "code": "11034", "name": "库门34" },
|
|
||||||
{ "code": "11035", "name": "库门35" },
|
|
||||||
{ "code": "11036", "name": "库门36" },
|
|
||||||
{ "code": "11037", "name": "库门37" },
|
|
||||||
{ "code": "11038", "name": "库门38" },
|
|
||||||
{ "code": "11039", "name": "库门39" },
|
|
||||||
{ "code": "11040", "name": "库门40" },
|
|
||||||
{ "code": "11041", "name": "库门41" },
|
|
||||||
{ "code": "11042", "name": "库门42" },
|
|
||||||
{ "code": "11043", "name": "库门43" },
|
|
||||||
{ "code": "11044", "name": "库门44" },
|
|
||||||
{ "code": "11045", "name": "库门45" },
|
|
||||||
{ "code": "11046", "name": "库门46" },
|
|
||||||
{ "code": "11047", "name": "库门47" },
|
|
||||||
{ "code": "11048", "name": "库门48" },
|
|
||||||
{ "code": "11049", "name": "库门49" },
|
|
||||||
{ "code": "11050", "name": "库门50" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "12",
|
|
||||||
"name": "其它车库(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "12001", "name": "检修库01" },
|
|
||||||
{ "code": "12002", "name": "检修库02" },
|
|
||||||
{ "code": "12003", "name": "检修库03" },
|
|
||||||
{ "code": "12004", "name": "检修库04" },
|
|
||||||
{ "code": "12005", "name": "检修库05" },
|
|
||||||
{ "code": "12006", "name": "洗车库01" },
|
|
||||||
{ "code": "12007", "name": "洗车库02" },
|
|
||||||
{ "code": "12008", "name": "洗车库03" },
|
|
||||||
{ "code": "12009", "name": "洗车库04" },
|
|
||||||
{ "code": "12010", "name": "洗车库05" },
|
|
||||||
{ "code": "12011", "name": "镟轮库01" },
|
|
||||||
{ "code": "12012", "name": "镟轮库02" },
|
|
||||||
{ "code": "12013", "name": "镟轮库03" },
|
|
||||||
{ "code": "12014", "name": "镟轮库04" },
|
|
||||||
{ "code": "12015", "name": "镟轮库05" },
|
|
||||||
{ "code": "12016", "name": "地下机动车库01" },
|
|
||||||
{ "code": "12017", "name": "地下机动车库02" },
|
|
||||||
{ "code": "12018", "name": "地下机动车库03" },
|
|
||||||
{ "code": "12019", "name": "地下机动车库04" },
|
|
||||||
{ "code": "12020", "name": "地下机动车库05" },
|
|
||||||
{ "code": "12021", "name": "特种车库01" },
|
|
||||||
{ "code": "12022", "name": "特种车库02" },
|
|
||||||
{ "code": "12023", "name": "特种车库03" },
|
|
||||||
{ "code": "12024", "name": "特种车库04" },
|
|
||||||
{ "code": "12025", "name": "特种车库05" },
|
|
||||||
{ "code": "12026", "name": "联合车库01" },
|
|
||||||
{ "code": "12027", "name": "联合车库02" },
|
|
||||||
{ "code": "12028", "name": "联合车库03" },
|
|
||||||
{ "code": "12029", "name": "联合车库04" },
|
|
||||||
{ "code": "12030", "name": "联合车库05" },
|
|
||||||
{ "code": "12031", "name": "静调库01" },
|
|
||||||
{ "code": "12032", "name": "静调库02" },
|
|
||||||
{ "code": "12033", "name": "静调库03" },
|
|
||||||
{ "code": "12034", "name": "静调库04" },
|
|
||||||
{ "code": "12035", "name": "静调库05" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "13",
|
|
||||||
"name": "办公楼(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "13001", "name": "信号楼01" },
|
|
||||||
{ "code": "13002", "name": "信号楼02" },
|
|
||||||
{ "code": "13003", "name": "信号楼03" },
|
|
||||||
{ "code": "13004", "name": "信号楼04" },
|
|
||||||
{ "code": "13005", "name": "信号楼05" },
|
|
||||||
{ "code": "13006", "name": "信号楼06" },
|
|
||||||
{ "code": "13007", "name": "信号楼07" },
|
|
||||||
{ "code": "13008", "name": "信号楼08" },
|
|
||||||
{ "code": "13009", "name": "信号楼09" },
|
|
||||||
{ "code": "13010", "name": "信号楼10" },
|
|
||||||
{ "code": "13011", "name": "办公楼01" },
|
|
||||||
{ "code": "13012", "name": "办公楼02" },
|
|
||||||
{ "code": "13013", "name": "办公楼03" },
|
|
||||||
{ "code": "13014", "name": "办公楼04" },
|
|
||||||
{ "code": "13015", "name": "办公楼05" },
|
|
||||||
{ "code": "13016", "name": "办公楼06" },
|
|
||||||
{ "code": "13017", "name": "办公楼07" },
|
|
||||||
{ "code": "13018", "name": "办公楼08" },
|
|
||||||
{ "code": "13019", "name": "办公楼09" },
|
|
||||||
{ "code": "13020", "name": "办公楼10" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "14",
|
|
||||||
"name": "出入口(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "14001", "name": "出入口01" },
|
|
||||||
{ "code": "14002", "name": "出入口02" },
|
|
||||||
{ "code": "14003", "name": "出入口03" },
|
|
||||||
{ "code": "14004", "name": "出入口04" },
|
|
||||||
{ "code": "14005", "name": "出入口05" },
|
|
||||||
{ "code": "14006", "name": "喉部01" },
|
|
||||||
{ "code": "14007", "name": "喉部02" },
|
|
||||||
{ "code": "14008", "name": "喉部03" },
|
|
||||||
{ "code": "14009", "name": "喉部04" },
|
|
||||||
{ "code": "14010", "name": "喉部05" },
|
|
||||||
{ "code": "14011", "name": "峒口01" },
|
|
||||||
{ "code": "14012", "name": "峒口02" },
|
|
||||||
{ "code": "14013", "name": "峒口03" },
|
|
||||||
{ "code": "14014", "name": "峒口04" },
|
|
||||||
{ "code": "14015", "name": "峒口05" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "15",
|
|
||||||
"name": "周界(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "15001", "name": "周界01" },
|
|
||||||
{ "code": "15002", "name": "周界02" },
|
|
||||||
{ "code": "15003", "name": "周界03" },
|
|
||||||
{ "code": "15004", "name": "周界04" },
|
|
||||||
{ "code": "15005", "name": "周界05" },
|
|
||||||
{ "code": "15006", "name": "周界06" },
|
|
||||||
{ "code": "15007", "name": "周界07" },
|
|
||||||
{ "code": "15008", "name": "周界08" },
|
|
||||||
{ "code": "15009", "name": "周界09" },
|
|
||||||
{ "code": "15010", "name": "周界10" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "16",
|
|
||||||
"name": "仓库(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "16001", "name": "危险物品仓库01" },
|
|
||||||
{ "code": "16002", "name": "危险物品仓库02" },
|
|
||||||
{ "code": "16003", "name": "危险物品仓库03" },
|
|
||||||
{ "code": "16004", "name": "危险物品仓库04" },
|
|
||||||
{ "code": "16005", "name": "危险物品仓库05" },
|
|
||||||
{ "code": "16006", "name": "物流仓库01" },
|
|
||||||
{ "code": "16007", "name": "物流仓库02" },
|
|
||||||
{ "code": "16008", "name": "物流仓库03" },
|
|
||||||
{ "code": "16009", "name": "物流仓库04" },
|
|
||||||
{ "code": "16010", "name": "物流仓库05" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "17",
|
|
||||||
"name": "道路(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "17001", "name": "道路01" },
|
|
||||||
{ "code": "17002", "name": "道路02" },
|
|
||||||
{ "code": "17003", "name": "道路03" },
|
|
||||||
{ "code": "17004", "name": "道路04" },
|
|
||||||
{ "code": "17005", "name": "道路05" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "18",
|
|
||||||
"name": "其它(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "18001", "name": "变电站01" },
|
|
||||||
{ "code": "18002", "name": "变电站02" },
|
|
||||||
{ "code": "18003", "name": "变电站03" },
|
|
||||||
{ "code": "18004", "name": "变电站04" },
|
|
||||||
{ "code": "18005", "name": "变电站05" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,470 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"code": "01",
|
|
||||||
"name": "站厅层",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "01001", "name": "客服中心01" },
|
|
||||||
{ "code": "01002", "name": "客服中心02" },
|
|
||||||
{ "code": "01003", "name": "客服中心03" },
|
|
||||||
{ "code": "01004", "name": "客服中心04" },
|
|
||||||
{ "code": "01005", "name": "闸机组01" },
|
|
||||||
{ "code": "01006", "name": "闸机组02" },
|
|
||||||
{ "code": "01007", "name": "闸机组03" },
|
|
||||||
{ "code": "01008", "name": "闸机组04" },
|
|
||||||
{ "code": "01009", "name": "闸机组05" },
|
|
||||||
{ "code": "01010", "name": "闸机组06" },
|
|
||||||
{ "code": "01011", "name": "闸机组07" },
|
|
||||||
{ "code": "01012", "name": "闸机组08" },
|
|
||||||
{ "code": "01013", "name": "闸机组09" },
|
|
||||||
{ "code": "01014", "name": "闸机组10" },
|
|
||||||
{ "code": "01015", "name": "闸机组11" },
|
|
||||||
{ "code": "01016", "name": "闸机组12" },
|
|
||||||
{ "code": "01017", "name": "闸机组13" },
|
|
||||||
{ "code": "01018", "name": "闸机组14" },
|
|
||||||
{ "code": "01019", "name": "闸机组15" },
|
|
||||||
{ "code": "01020", "name": "闸机组16" },
|
|
||||||
{ "code": "01021", "name": "闸机组17" },
|
|
||||||
{ "code": "01022", "name": "闸机组18" },
|
|
||||||
{ "code": "01023", "name": "闸机组19" },
|
|
||||||
{ "code": "01024", "name": "闸机组20" },
|
|
||||||
{ "code": "01025", "name": "人工售票01" },
|
|
||||||
{ "code": "01026", "name": "人工售票02" },
|
|
||||||
{ "code": "01027", "name": "人工售票03" },
|
|
||||||
{ "code": "01028", "name": "人工售票04" },
|
|
||||||
{ "code": "01029", "name": "人工售票05" },
|
|
||||||
{ "code": "01030", "name": "自动售票01" },
|
|
||||||
{ "code": "01031", "name": "自动售票02" },
|
|
||||||
{ "code": "01032", "name": "自动售票03" },
|
|
||||||
{ "code": "01033", "name": "自动售票04" },
|
|
||||||
{ "code": "01034", "name": "自动售票05" },
|
|
||||||
{ "code": "01035", "name": "站厅球机" },
|
|
||||||
{ "code": "01036", "name": "直升梯01" },
|
|
||||||
{ "code": "01037", "name": "直升梯02" },
|
|
||||||
{ "code": "01038", "name": "直升梯03" },
|
|
||||||
{ "code": "01039", "name": "直升梯04" },
|
|
||||||
{ "code": "01040", "name": "公共区01" },
|
|
||||||
{ "code": "01041", "name": "公共区02" },
|
|
||||||
{ "code": "01042", "name": "公共区03" },
|
|
||||||
{ "code": "01043", "name": "公共区04" },
|
|
||||||
{ "code": "01044", "name": "公共区05" },
|
|
||||||
{ "code": "01045", "name": "站厅楼梯01" },
|
|
||||||
{ "code": "01046", "name": "站厅楼梯02" },
|
|
||||||
{ "code": "01047", "name": "站厅楼梯03" },
|
|
||||||
{ "code": "01048", "name": "站厅楼梯04" },
|
|
||||||
{ "code": "01049", "name": "站厅楼梯05" },
|
|
||||||
{ "code": "01050", "name": "站厅扶梯01" },
|
|
||||||
{ "code": "01051", "name": "站厅扶梯02" },
|
|
||||||
{ "code": "01052", "name": "站厅扶梯03" },
|
|
||||||
{ "code": "01053", "name": "站厅扶梯04" },
|
|
||||||
{ "code": "01054", "name": "站厅扶梯05" },
|
|
||||||
{ "code": "01055", "name": "01号口" },
|
|
||||||
{ "code": "01056", "name": "02号口" },
|
|
||||||
{ "code": "01057", "name": "03号口" },
|
|
||||||
{ "code": "01058", "name": "04号口" },
|
|
||||||
{ "code": "01059", "name": "05号口" },
|
|
||||||
{ "code": "01060", "name": "06号口" },
|
|
||||||
{ "code": "01061", "name": "07号口" },
|
|
||||||
{ "code": "01062", "name": "08号口" },
|
|
||||||
{ "code": "01063", "name": "09号口" },
|
|
||||||
{ "code": "01064", "name": "10号口" },
|
|
||||||
{ "code": "01065", "name": "11号口" },
|
|
||||||
{ "code": "01066", "name": "12号口" },
|
|
||||||
{ "code": "01067", "name": "13号口" },
|
|
||||||
{ "code": "01068", "name": "14号口" },
|
|
||||||
{ "code": "01069", "name": "15号口" },
|
|
||||||
{ "code": "01070", "name": "16号口" },
|
|
||||||
{ "code": "01071", "name": "17号口" },
|
|
||||||
{ "code": "01072", "name": "18号口" },
|
|
||||||
{ "code": "01073", "name": "19号口" },
|
|
||||||
{ "code": "01074", "name": "20号口" },
|
|
||||||
{ "code": "01075", "name": "21号口" },
|
|
||||||
{ "code": "01076", "name": "22号口" },
|
|
||||||
{ "code": "01077", "name": "23号口" },
|
|
||||||
{ "code": "01078", "name": "24号口" },
|
|
||||||
{ "code": "01079", "name": "25号口" },
|
|
||||||
{ "code": "01080", "name": "26号口" },
|
|
||||||
{ "code": "01081", "name": "换乘厅01" },
|
|
||||||
{ "code": "01082", "name": "换乘厅02" },
|
|
||||||
{ "code": "01083", "name": "长通道01" },
|
|
||||||
{ "code": "01084", "name": "长通道02" },
|
|
||||||
{ "code": "01085", "name": "安检区" },
|
|
||||||
{ "code": "01086", "name": "预留1" },
|
|
||||||
{ "code": "01087", "name": "预留2" },
|
|
||||||
{ "code": "01088", "name": "预留3" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "02",
|
|
||||||
"name": "站台层",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "02001", "name": "站台球机" },
|
|
||||||
{ "code": "02002", "name": "公共区01" },
|
|
||||||
{ "code": "02003", "name": "公共区02" },
|
|
||||||
{ "code": "02004", "name": "公共区03" },
|
|
||||||
{ "code": "02005", "name": "公共区04" },
|
|
||||||
{ "code": "02006", "name": "公共区05" },
|
|
||||||
{ "code": "02007", "name": "上行01" },
|
|
||||||
{ "code": "02008", "name": "上行02" },
|
|
||||||
{ "code": "02009", "name": "上行03" },
|
|
||||||
{ "code": "02010", "name": "上行04" },
|
|
||||||
{ "code": "02011", "name": "上行05" },
|
|
||||||
{ "code": "02012", "name": "下行01" },
|
|
||||||
{ "code": "02013", "name": "下行02" },
|
|
||||||
{ "code": "02014", "name": "下行03" },
|
|
||||||
{ "code": "02015", "name": "下行04" },
|
|
||||||
{ "code": "02016", "name": "下行05" },
|
|
||||||
{ "code": "02017", "name": "站台楼梯01" },
|
|
||||||
{ "code": "02018", "name": "站台楼梯02" },
|
|
||||||
{ "code": "02019", "name": "站台楼梯03" },
|
|
||||||
{ "code": "02020", "name": "站台楼梯04" },
|
|
||||||
{ "code": "02021", "name": "站台楼梯05" },
|
|
||||||
{ "code": "02022", "name": "预留1" },
|
|
||||||
{ "code": "02023", "name": "预留2" },
|
|
||||||
{ "code": "02024", "name": "预留3" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "03",
|
|
||||||
"name": "设备区",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "03001", "name": "站厅走道01" },
|
|
||||||
{ "code": "03002", "name": "站厅走道02" },
|
|
||||||
{ "code": "03003", "name": "站厅走道03" },
|
|
||||||
{ "code": "03004", "name": "站厅走道04" },
|
|
||||||
{ "code": "03005", "name": "站厅走道05" },
|
|
||||||
{ "code": "03006", "name": "站厅走道06" },
|
|
||||||
{ "code": "03007", "name": "站厅走道07" },
|
|
||||||
{ "code": "03008", "name": "站厅走道08" },
|
|
||||||
{ "code": "03009", "name": "站厅走道09" },
|
|
||||||
{ "code": "03010", "name": "站厅走道10" },
|
|
||||||
{ "code": "03011", "name": "站厅楼梯01" },
|
|
||||||
{ "code": "03012", "name": "站厅楼梯02" },
|
|
||||||
{ "code": "03013", "name": "站厅楼梯03" },
|
|
||||||
{ "code": "03014", "name": "站厅楼梯04" },
|
|
||||||
{ "code": "03015", "name": "站厅楼梯05" },
|
|
||||||
{ "code": "03016", "name": "站厅楼梯06" },
|
|
||||||
{ "code": "03017", "name": "站厅楼梯07" },
|
|
||||||
{ "code": "03018", "name": "站厅楼梯08" },
|
|
||||||
{ "code": "03019", "name": "站厅楼梯09" },
|
|
||||||
{ "code": "03020", "name": "站厅楼梯10" },
|
|
||||||
{ "code": "03021", "name": "站台走道01" },
|
|
||||||
{ "code": "03022", "name": "站台走道02" },
|
|
||||||
{ "code": "03023", "name": "站台走道03" },
|
|
||||||
{ "code": "03024", "name": "站台走道04" },
|
|
||||||
{ "code": "03025", "name": "站台走道05" },
|
|
||||||
{ "code": "03026", "name": "站台走道06" },
|
|
||||||
{ "code": "03027", "name": "站台走道07" },
|
|
||||||
{ "code": "03028", "name": "站台走道08" },
|
|
||||||
{ "code": "03029", "name": "站台走道09" },
|
|
||||||
{ "code": "03030", "name": "站台走道10" },
|
|
||||||
{ "code": "03031", "name": "站台楼梯01" },
|
|
||||||
{ "code": "03032", "name": "站台楼梯02" },
|
|
||||||
{ "code": "03033", "name": "站台楼梯03" },
|
|
||||||
{ "code": "03034", "name": "站台楼梯04" },
|
|
||||||
{ "code": "03035", "name": "站台楼梯05" },
|
|
||||||
{ "code": "03036", "name": "站台楼梯06" },
|
|
||||||
{ "code": "03037", "name": "站台楼梯07" },
|
|
||||||
{ "code": "03038", "name": "站台楼梯08" },
|
|
||||||
{ "code": "03039", "name": "站台楼梯09" },
|
|
||||||
{ "code": "03040", "name": "站台楼梯10" },
|
|
||||||
{ "code": "03041", "name": "编码室" },
|
|
||||||
{ "code": "03042", "name": "车控室01" },
|
|
||||||
{ "code": "03043", "name": "车控室02" },
|
|
||||||
{ "code": "03044", "name": "主变电站01" },
|
|
||||||
{ "code": "03045", "name": "主变电站02" },
|
|
||||||
{ "code": "03046", "name": "降压变电所01" },
|
|
||||||
{ "code": "03047", "name": "降压变电所02" },
|
|
||||||
{ "code": "03048", "name": "通信设备室" },
|
|
||||||
{ "code": "03049", "name": "信号设备室" },
|
|
||||||
{ "code": "03050", "name": "弱电电源室" },
|
|
||||||
{ "code": "03051", "name": "民用通信机房" },
|
|
||||||
{ "code": "03052", "name": "区间通风机房" },
|
|
||||||
{ "code": "03053", "name": "环控机房" },
|
|
||||||
{ "code": "03054", "name": "冷水机房" },
|
|
||||||
{ "code": "03055", "name": "环控电控室" },
|
|
||||||
{ "code": "03056", "name": "线间楼梯01" },
|
|
||||||
{ "code": "03057", "name": "线间楼梯02" },
|
|
||||||
{ "code": "03058", "name": "线间楼梯03" },
|
|
||||||
{ "code": "03059", "name": "线间楼梯04" },
|
|
||||||
{ "code": "03060", "name": "线间楼梯05" },
|
|
||||||
{ "code": "03061", "name": "线间通道01" },
|
|
||||||
{ "code": "03062", "name": "线间通道02" },
|
|
||||||
{ "code": "03063", "name": "线间通道03" },
|
|
||||||
{ "code": "03064", "name": "线间通道04" },
|
|
||||||
{ "code": "03065", "name": "线间通道05" },
|
|
||||||
{ "code": "03066", "name": "站长室外" },
|
|
||||||
{ "code": "03067", "name": "气瓶间" },
|
|
||||||
{ "code": "03068", "name": "消防泵房" },
|
|
||||||
{ "code": "03069", "name": "钢瓶室" },
|
|
||||||
{ "code": "03070", "name": "民用通信机房" },
|
|
||||||
{ "code": "03071", "name": "民用机房" },
|
|
||||||
{ "code": "03074", "name": "公网引入室" },
|
|
||||||
{ "code": "03075", "name": "环控电控室" },
|
|
||||||
{ "code": "03076", "name": "UPS间" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "04",
|
|
||||||
"name": "区间内",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "04001", "name": "区间变电01" },
|
|
||||||
{ "code": "04002", "name": "区间变电02" },
|
|
||||||
{ "code": "04003", "name": "区间变电03" },
|
|
||||||
{ "code": "04004", "name": "区间变电04" },
|
|
||||||
{ "code": "04005", "name": "区间变电05" },
|
|
||||||
{ "code": "04006", "name": "上行峒口" },
|
|
||||||
{ "code": "04007", "name": "下行峒口" },
|
|
||||||
{ "code": "04008", "name": "上行轨道" },
|
|
||||||
{ "code": "04009", "name": "下行轨道" },
|
|
||||||
{ "code": "04010", "name": "上行风井" },
|
|
||||||
{ "code": "04011", "name": "下行风井" },
|
|
||||||
{ "code": "04012", "name": "旁通道" },
|
|
||||||
{ "code": "04013", "name": "道岔" },
|
|
||||||
{ "code": "04014", "name": "预留1" },
|
|
||||||
{ "code": "04015", "name": "预留2" },
|
|
||||||
{ "code": "04016", "name": "预留3" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "05",
|
|
||||||
"name": "派出所",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "05001", "name": "监控室" },
|
|
||||||
{ "code": "05002", "name": "综合执法站" },
|
|
||||||
{ "code": "05003", "name": "预留1" },
|
|
||||||
{ "code": "05004", "name": "预留2" },
|
|
||||||
{ "code": "05005", "name": "预留3" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "06",
|
|
||||||
"name": "站厅层(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "06001", "name": "客服中心01" },
|
|
||||||
{ "code": "06002", "name": "客服中心02" },
|
|
||||||
{ "code": "06003", "name": "客服中心03" },
|
|
||||||
{ "code": "06004", "name": "客服中心04" },
|
|
||||||
{ "code": "06005", "name": "闸机组01" },
|
|
||||||
{ "code": "06006", "name": "闸机组02" },
|
|
||||||
{ "code": "06007", "name": "闸机组03" },
|
|
||||||
{ "code": "06008", "name": "闸机组04" },
|
|
||||||
{ "code": "06009", "name": "闸机组05" },
|
|
||||||
{ "code": "06010", "name": "闸机组06" },
|
|
||||||
{ "code": "06011", "name": "闸机组07" },
|
|
||||||
{ "code": "06012", "name": "闸机组08" },
|
|
||||||
{ "code": "06013", "name": "闸机组09" },
|
|
||||||
{ "code": "06014", "name": "闸机组10" },
|
|
||||||
{ "code": "06015", "name": "闸机组11" },
|
|
||||||
{ "code": "06016", "name": "闸机组12" },
|
|
||||||
{ "code": "06017", "name": "闸机组13" },
|
|
||||||
{ "code": "06018", "name": "闸机组14" },
|
|
||||||
{ "code": "06019", "name": "闸机组15" },
|
|
||||||
{ "code": "06020", "name": "闸机组16" },
|
|
||||||
{ "code": "06021", "name": "闸机组17" },
|
|
||||||
{ "code": "06022", "name": "闸机组18" },
|
|
||||||
{ "code": "06023", "name": "闸机组19" },
|
|
||||||
{ "code": "06024", "name": "闸机组20" },
|
|
||||||
{ "code": "06025", "name": "人工售票01" },
|
|
||||||
{ "code": "06026", "name": "人工售票02" },
|
|
||||||
{ "code": "06027", "name": "人工售票03" },
|
|
||||||
{ "code": "06028", "name": "人工售票04" },
|
|
||||||
{ "code": "06029", "name": "人工售票05" },
|
|
||||||
{ "code": "06030", "name": "自动售票01" },
|
|
||||||
{ "code": "06031", "name": "自动售票02" },
|
|
||||||
{ "code": "06032", "name": "自动售票03" },
|
|
||||||
{ "code": "06033", "name": "自动售票04" },
|
|
||||||
{ "code": "06034", "name": "自动售票05" },
|
|
||||||
{ "code": "06035", "name": "站厅球机" },
|
|
||||||
{ "code": "06036", "name": "直升梯01" },
|
|
||||||
{ "code": "06037", "name": "直升梯02" },
|
|
||||||
{ "code": "06038", "name": "直升梯03" },
|
|
||||||
{ "code": "06039", "name": "直升梯04" },
|
|
||||||
{ "code": "06040", "name": "公共区01" },
|
|
||||||
{ "code": "06041", "name": "公共区02" },
|
|
||||||
{ "code": "06042", "name": "公共区03" },
|
|
||||||
{ "code": "06043", "name": "公共区04" },
|
|
||||||
{ "code": "06044", "name": "公共区05" },
|
|
||||||
{ "code": "06045", "name": "站厅楼梯01" },
|
|
||||||
{ "code": "06046", "name": "站厅楼梯02" },
|
|
||||||
{ "code": "06047", "name": "站厅楼梯03" },
|
|
||||||
{ "code": "06048", "name": "站厅楼梯04" },
|
|
||||||
{ "code": "06049", "name": "站厅楼梯05" },
|
|
||||||
{ "code": "06050", "name": "站厅扶梯01" },
|
|
||||||
{ "code": "06051", "name": "站厅扶梯02" },
|
|
||||||
{ "code": "06052", "name": "站厅扶梯03" },
|
|
||||||
{ "code": "06053", "name": "站厅扶梯04" },
|
|
||||||
{ "code": "06054", "name": "站厅扶梯05" },
|
|
||||||
{ "code": "06055", "name": "01号口" },
|
|
||||||
{ "code": "06056", "name": "02号口" },
|
|
||||||
{ "code": "06057", "name": "03号口" },
|
|
||||||
{ "code": "06058", "name": "04号口" },
|
|
||||||
{ "code": "06059", "name": "05号口" },
|
|
||||||
{ "code": "06060", "name": "06号口" },
|
|
||||||
{ "code": "06061", "name": "07号口" },
|
|
||||||
{ "code": "06062", "name": "08号口" },
|
|
||||||
{ "code": "06063", "name": "09号口" },
|
|
||||||
{ "code": "06064", "name": "10号口" },
|
|
||||||
{ "code": "06065", "name": "11号口" },
|
|
||||||
{ "code": "06066", "name": "12号口" },
|
|
||||||
{ "code": "06067", "name": "13号口" },
|
|
||||||
{ "code": "06068", "name": "14号口" },
|
|
||||||
{ "code": "06069", "name": "15号口" },
|
|
||||||
{ "code": "06070", "name": "16号口" },
|
|
||||||
{ "code": "06071", "name": "17号口" },
|
|
||||||
{ "code": "06072", "name": "18号口" },
|
|
||||||
{ "code": "06073", "name": "19号口" },
|
|
||||||
{ "code": "06074", "name": "20号口" },
|
|
||||||
{ "code": "06075", "name": "21号口" },
|
|
||||||
{ "code": "06076", "name": "22号口" },
|
|
||||||
{ "code": "06077", "name": "23号口" },
|
|
||||||
{ "code": "06078", "name": "24号口" },
|
|
||||||
{ "code": "06079", "name": "25号口" },
|
|
||||||
{ "code": "06080", "name": "26号口" },
|
|
||||||
{ "code": "06081", "name": "换乘厅01" },
|
|
||||||
{ "code": "06082", "name": "换乘厅02" },
|
|
||||||
{ "code": "06083", "name": "长通道01" },
|
|
||||||
{ "code": "06084", "name": "长通道02" },
|
|
||||||
{ "code": "06085", "name": "安检区" },
|
|
||||||
{ "code": "06086", "name": "预留1" },
|
|
||||||
{ "code": "06087", "name": "预留2" },
|
|
||||||
{ "code": "06088", "name": "预留3" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "07",
|
|
||||||
"name": "站台层(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "07001", "name": "站台球机" },
|
|
||||||
{ "code": "07002", "name": "公共区01" },
|
|
||||||
{ "code": "07003", "name": "公共区02" },
|
|
||||||
{ "code": "07004", "name": "公共区03" },
|
|
||||||
{ "code": "07005", "name": "公共区04" },
|
|
||||||
{ "code": "07006", "name": "公共区05" },
|
|
||||||
{ "code": "07007", "name": "上行01" },
|
|
||||||
{ "code": "07008", "name": "上行02" },
|
|
||||||
{ "code": "07009", "name": "上行03" },
|
|
||||||
{ "code": "07010", "name": "上行04" },
|
|
||||||
{ "code": "07011", "name": "上行05" },
|
|
||||||
{ "code": "07012", "name": "下行01" },
|
|
||||||
{ "code": "07013", "name": "下行02" },
|
|
||||||
{ "code": "07014", "name": "下行03" },
|
|
||||||
{ "code": "07015", "name": "下行04" },
|
|
||||||
{ "code": "07016", "name": "下行05" },
|
|
||||||
{ "code": "07017", "name": "站台楼梯01" },
|
|
||||||
{ "code": "07018", "name": "站台楼梯02" },
|
|
||||||
{ "code": "07019", "name": "站台楼梯03" },
|
|
||||||
{ "code": "07020", "name": "站台楼梯04" },
|
|
||||||
{ "code": "07021", "name": "站台楼梯05" },
|
|
||||||
{ "code": "07022", "name": "预留1" },
|
|
||||||
{ "code": "07023", "name": "预留2" },
|
|
||||||
{ "code": "07024", "name": "预留3" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "08",
|
|
||||||
"name": "设备区(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "08001", "name": "站厅走道01" },
|
|
||||||
{ "code": "08002", "name": "站厅走道02" },
|
|
||||||
{ "code": "08003", "name": "站厅走道03" },
|
|
||||||
{ "code": "08004", "name": "站厅走道04" },
|
|
||||||
{ "code": "08005", "name": "站厅走道05" },
|
|
||||||
{ "code": "08006", "name": "站厅走道06" },
|
|
||||||
{ "code": "08007", "name": "站厅走道07" },
|
|
||||||
{ "code": "08008", "name": "站厅走道08" },
|
|
||||||
{ "code": "08009", "name": "站厅走道09" },
|
|
||||||
{ "code": "08010", "name": "站厅走道10" },
|
|
||||||
{ "code": "08011", "name": "站厅楼梯01" },
|
|
||||||
{ "code": "08012", "name": "站厅楼梯02" },
|
|
||||||
{ "code": "08013", "name": "站厅楼梯03" },
|
|
||||||
{ "code": "08014", "name": "站厅楼梯04" },
|
|
||||||
{ "code": "08015", "name": "站厅楼梯05" },
|
|
||||||
{ "code": "08016", "name": "站厅楼梯06" },
|
|
||||||
{ "code": "08017", "name": "站厅楼梯07" },
|
|
||||||
{ "code": "08018", "name": "站厅楼梯08" },
|
|
||||||
{ "code": "08019", "name": "站厅楼梯09" },
|
|
||||||
{ "code": "08020", "name": "站厅楼梯10" },
|
|
||||||
{ "code": "08021", "name": "站台走道01" },
|
|
||||||
{ "code": "08022", "name": "站台走道02" },
|
|
||||||
{ "code": "08023", "name": "站台走道03" },
|
|
||||||
{ "code": "08024", "name": "站台走道04" },
|
|
||||||
{ "code": "08025", "name": "站台走道05" },
|
|
||||||
{ "code": "08026", "name": "站台走道06" },
|
|
||||||
{ "code": "08027", "name": "站台走道07" },
|
|
||||||
{ "code": "08028", "name": "站台走道08" },
|
|
||||||
{ "code": "08029", "name": "站台走道09" },
|
|
||||||
{ "code": "08030", "name": "站台走道10" },
|
|
||||||
{ "code": "08031", "name": "站台楼梯01" },
|
|
||||||
{ "code": "08032", "name": "站台楼梯02" },
|
|
||||||
{ "code": "08033", "name": "站台楼梯03" },
|
|
||||||
{ "code": "08034", "name": "站台楼梯04" },
|
|
||||||
{ "code": "08035", "name": "站台楼梯05" },
|
|
||||||
{ "code": "08036", "name": "站台楼梯06" },
|
|
||||||
{ "code": "08037", "name": "站台楼梯07" },
|
|
||||||
{ "code": "08038", "name": "站台楼梯08" },
|
|
||||||
{ "code": "08039", "name": "站台楼梯09" },
|
|
||||||
{ "code": "08040", "name": "站台楼梯10" },
|
|
||||||
{ "code": "08041", "name": "编码室" },
|
|
||||||
{ "code": "08042", "name": "车控室01" },
|
|
||||||
{ "code": "08043", "name": "车控室02" },
|
|
||||||
{ "code": "08044", "name": "主变电站01" },
|
|
||||||
{ "code": "08045", "name": "主变电站02" },
|
|
||||||
{ "code": "08046", "name": "降压变电所01" },
|
|
||||||
{ "code": "08047", "name": "降压变电所02" },
|
|
||||||
{ "code": "08048", "name": "通信设备室" },
|
|
||||||
{ "code": "08049", "name": "信号设备室" },
|
|
||||||
{ "code": "08050", "name": "弱电电源室" },
|
|
||||||
{ "code": "08051", "name": "民用通信机房" },
|
|
||||||
{ "code": "08052", "name": "区间通风机房" },
|
|
||||||
{ "code": "08053", "name": "环控机房" },
|
|
||||||
{ "code": "08054", "name": "冷水机房" },
|
|
||||||
{ "code": "08055", "name": "环控电控室" },
|
|
||||||
{ "code": "08056", "name": "线间楼梯01" },
|
|
||||||
{ "code": "08057", "name": "线间楼梯02" },
|
|
||||||
{ "code": "08058", "name": "线间楼梯03" },
|
|
||||||
{ "code": "08059", "name": "线间楼梯04" },
|
|
||||||
{ "code": "08060", "name": "线间楼梯05" },
|
|
||||||
{ "code": "08061", "name": "线间通道01" },
|
|
||||||
{ "code": "08062", "name": "线间通道02" },
|
|
||||||
{ "code": "08063", "name": "线间通道03" },
|
|
||||||
{ "code": "08064", "name": "线间通道04" },
|
|
||||||
{ "code": "08065", "name": "线间通道05" },
|
|
||||||
{ "code": "08066", "name": "预留1" },
|
|
||||||
{ "code": "08067", "name": "预留2" },
|
|
||||||
{ "code": "08068", "name": "预留3" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "09",
|
|
||||||
"name": "区间内(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "09001", "name": "区间变电01" },
|
|
||||||
{ "code": "09002", "name": "区间变电02" },
|
|
||||||
{ "code": "09003", "name": "区间变电03" },
|
|
||||||
{ "code": "09004", "name": "区间变电04" },
|
|
||||||
{ "code": "09005", "name": "区间变电05" },
|
|
||||||
{ "code": "09006", "name": "上行峒口" },
|
|
||||||
{ "code": "09007", "name": "下行峒口" },
|
|
||||||
{ "code": "09008", "name": "上行轨道" },
|
|
||||||
{ "code": "09009", "name": "下行轨道" },
|
|
||||||
{ "code": "09010", "name": "上行风井" },
|
|
||||||
{ "code": "09011", "name": "下行风井" },
|
|
||||||
{ "code": "09012", "name": "旁通道" },
|
|
||||||
{ "code": "09013", "name": "道岔" },
|
|
||||||
{ "code": "09014", "name": "预留1" },
|
|
||||||
{ "code": "09015", "name": "预留2" },
|
|
||||||
{ "code": "09016", "name": "预留3" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "10",
|
|
||||||
"name": "派出所(录)",
|
|
||||||
"subs": [
|
|
||||||
{ "code": "10001", "name": "监控室" },
|
|
||||||
{ "code": "10002", "name": "综合执法站" },
|
|
||||||
{ "code": "10003", "name": "预留1" },
|
|
||||||
{ "code": "10004", "name": "预留2" },
|
|
||||||
{ "code": "10005", "name": "预留3" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,45 +1,5 @@
|
|||||||
export interface NdmCameraDiagInfo {
|
export interface NdmCameraDiagInfo {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
ethInfo?: {
|
|
||||||
adminStatus?: string; // '1'
|
|
||||||
desc?: string; // 'IPcamera'
|
|
||||||
ifType?: string; // '5'
|
|
||||||
inDiscards?: string; // '0'
|
|
||||||
inErrors?: string; // '0'
|
|
||||||
inNUcastPkts?: string; // '0'
|
|
||||||
inOctets?: string; // '0'
|
|
||||||
inUcastPkts?: string; // '0'
|
|
||||||
inUnknownProtos?: string; // '0'
|
|
||||||
index?: string; // '1'
|
|
||||||
lastChange?: string; // '0:00:00.00'
|
|
||||||
mTU?: string; // '1500'
|
|
||||||
macAddress?: string; // '04:ee:cd:52:3a:a5'
|
|
||||||
operStatus?: string; // '1'
|
|
||||||
outDiscards?: string; // '0'
|
|
||||||
outErrors?: string; // '0'
|
|
||||||
outNUcastPkts?: string; // '0'
|
|
||||||
outOctets?: string; // '0'
|
|
||||||
outQLen?: string; // '0'
|
|
||||||
outUcastPkts?: string; // '0'
|
|
||||||
specific?: string; // '0.0'
|
|
||||||
speed?: string; // '10000000'
|
|
||||||
};
|
|
||||||
ipInfo?: {
|
|
||||||
broadcastAddress?: string; // '0'
|
|
||||||
iPAddress?: string; // '0'
|
|
||||||
index?: string; // '1'
|
|
||||||
mASK?: string; // '255.255.255.0'
|
|
||||||
reasmMaxSize?: string; // '0'
|
|
||||||
};
|
|
||||||
logTime?: string;
|
logTime?: string;
|
||||||
stCommonInfo?: {
|
info?: string;
|
||||||
设备ID?: string;
|
|
||||||
软件版本?: string;
|
|
||||||
设备厂商?: string;
|
|
||||||
设备别名?: string;
|
|
||||||
设备型号?: string;
|
|
||||||
硬件版本?: string;
|
|
||||||
内存使用率?: string;
|
|
||||||
CPU使用率?: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,37 +10,6 @@ export interface NdmNvrDiagInfo {
|
|||||||
totalSize?: number;
|
totalSize?: number;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
ethInfo: {
|
|
||||||
adminStatus?: string; // '1'
|
|
||||||
desc?: string; // 'bond1'
|
|
||||||
ifType?: string; // '6'
|
|
||||||
inDiscards?: string; // '17931688'
|
|
||||||
inErrors?: string; // '0'
|
|
||||||
inNUcastPkts?: string; // '40945433'
|
|
||||||
inOctets?: string; // '3453544614'
|
|
||||||
inUcastPkts?: string; // '3375411816'
|
|
||||||
inUnknownProtos?: string; // '0'
|
|
||||||
index?: string; // '8'
|
|
||||||
lastChange?: string; // '0:05:42.15'
|
|
||||||
mTU?: string; // '1500'
|
|
||||||
macAddress?: string; // '04:7b:cb:69:92:58'
|
|
||||||
operStatus?: string; // '1'
|
|
||||||
outDiscards?: string; // '0'
|
|
||||||
outErrors?: string; // '0'
|
|
||||||
outNUcastPkts?: string; // '0'
|
|
||||||
outOctets?: string; // '3443476717'
|
|
||||||
outQLen?: string; // '0'
|
|
||||||
outUcastPkts?: string; // '415381735'
|
|
||||||
specific?: string; // '0.0'
|
|
||||||
speed?: string; // '2000000000'
|
|
||||||
};
|
|
||||||
ipInfo: {
|
|
||||||
broadcastAddress?: string; // '1'
|
|
||||||
iPAddress?: string; // '10.14.1.22'
|
|
||||||
index?: string; // '8'
|
|
||||||
mASK?: string; // '255.255.255.0'
|
|
||||||
reasmMaxSize?: string; // '0'
|
|
||||||
};
|
|
||||||
stCommonInfo?: {
|
stCommonInfo?: {
|
||||||
设备ID?: string;
|
设备ID?: string;
|
||||||
软件版本?: string;
|
软件版本?: string;
|
||||||
@@ -51,16 +20,12 @@ export interface NdmNvrDiagInfo {
|
|||||||
内存使用率?: string;
|
内存使用率?: string;
|
||||||
CPU使用率?: string;
|
CPU使用率?: string;
|
||||||
};
|
};
|
||||||
cdFanInfo?: NdmNvrFanInfo[];
|
cdFanInfo?: {
|
||||||
cdPowerSupplyInfo?: NdmNvrPowerSupplyInfo[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NdmNvrFanInfo {
|
|
||||||
索引号?: string;
|
索引号?: string;
|
||||||
'风扇转速(rpm)'?: string;
|
'风扇转速(rpm)'?: string;
|
||||||
}
|
}[];
|
||||||
|
cdPowerSupplyInfo?: {
|
||||||
export interface NdmNvrPowerSupplyInfo {
|
|
||||||
索引号?: string;
|
索引号?: string;
|
||||||
电源状态?: string;
|
电源状态?: string;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,5 @@
|
|||||||
export interface NdmSecurityBoxDiagInfo {
|
export interface NdmSecurityBoxDiagInfo {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
ethInfo?: {
|
|
||||||
adminStatus?: string; // '1'
|
|
||||||
desc?: string; // 'br-lan'
|
|
||||||
ifType?: string; // '6'
|
|
||||||
inDiscards?: string; // '84977'
|
|
||||||
inErrors?: string; // '0'
|
|
||||||
inNUcastPkts?: string; // '233473'
|
|
||||||
inOctets?: string; // '92355850'
|
|
||||||
inUcastPkts?: string; // '899970'
|
|
||||||
inUnknownProtos?: string; // '0'
|
|
||||||
index?: string; // '8'
|
|
||||||
lastChange?: string; // '0:00:00.00'
|
|
||||||
mTU?: string; // '1500'
|
|
||||||
macAddress?: string; // '56:62:bc:d3:9e:37'
|
|
||||||
operStatus?: string; // '1'
|
|
||||||
outDiscards?: string; // '0'
|
|
||||||
outErrors?: string; // '0'
|
|
||||||
outNUcastPkts?: string; // '0'
|
|
||||||
outOctets?: string; // '68175904'
|
|
||||||
outQLen?: string; // '0'
|
|
||||||
outUcastPkts?: string; // '748100'
|
|
||||||
specific?: string; // '0.0'
|
|
||||||
speed?: string; // '0'
|
|
||||||
};
|
|
||||||
ipInfo?: {
|
|
||||||
broadcastAddress?: string; // '1'
|
|
||||||
iPAddress?: string; // '10.24.18.101'
|
|
||||||
index?: string; // '8'
|
|
||||||
mASK?: string; // '255.255.255.0'
|
|
||||||
reasmMaxSize?: string; // '0'
|
|
||||||
};
|
|
||||||
info?: [
|
info?: [
|
||||||
{
|
{
|
||||||
addrCode?: number;
|
addrCode?: number;
|
||||||
@@ -43,14 +12,8 @@ export interface NdmSecurityBoxDiagInfo {
|
|||||||
];
|
];
|
||||||
stCommonInfo?: {
|
stCommonInfo?: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
设备ID?: string; // 'NTBoxMetro'
|
内存使用率?: string;
|
||||||
软件版本?: string; // 'V0101'
|
CPU使用率?: string;
|
||||||
设备厂商?: string; // 'NingTech'
|
|
||||||
设备别名?: string; // 'SUN-IBOX'
|
|
||||||
设备型号?: string; // 'SUN-IBOX'
|
|
||||||
硬件版本?: string; // 'V0101'
|
|
||||||
内存使用率?: string; // '18'
|
|
||||||
CPU使用率?: string; // '1'
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,35 +6,4 @@ export interface NdmServerDiagInfo {
|
|||||||
磁盘使用率?: string;
|
磁盘使用率?: string;
|
||||||
系统运行时间?: string;
|
系统运行时间?: string;
|
||||||
};
|
};
|
||||||
ethInfo?: {
|
|
||||||
adminStatus?: string; // '1'
|
|
||||||
desc?: string; // 'Intel Corporation I350 Gigabit Network Connection'
|
|
||||||
ifType?: string; // '6'
|
|
||||||
inDiscards?: string; // '6707634'
|
|
||||||
inErrors?: string; // '0'
|
|
||||||
inNUcastPkts?: string; // '8991944'
|
|
||||||
inOctets?: string; // '4220524983'
|
|
||||||
inUcastPkts?: string; // '2342740610'
|
|
||||||
inUnknownProtos?: string; // '0'
|
|
||||||
index?: string; // '2'
|
|
||||||
lastChange?: string; // '0:03:15.26'
|
|
||||||
mTU?: string; // '1500'
|
|
||||||
macAddress?: string; // 'e8:78:ee:f6:8d:98'
|
|
||||||
operStatus?: string; // '1'
|
|
||||||
outDiscards?: string; // '0'
|
|
||||||
outErrors?: string; // '0'
|
|
||||||
outNUcastPkts?: string; // '0'
|
|
||||||
outOctets?: string; // '1415770066'
|
|
||||||
outQLen?: string; // '0'
|
|
||||||
outUcastPkts?: string; // '3335494729'
|
|
||||||
specific?: string; // '0.0'
|
|
||||||
speed?: string; // '1000000000'
|
|
||||||
};
|
|
||||||
ipInfo?: {
|
|
||||||
broadcastAddress?: string; // '1'
|
|
||||||
iPAddress?: string; // '10.14.1.8'
|
|
||||||
index?: string; // '2'
|
|
||||||
mASK?: string; // '255.255.255.0'
|
|
||||||
reasmMaxSize?: string; // '0'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ export interface NdmSwitchDiagInfo {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
cpuRatio?: string; // 因环境不同可能不存在
|
cpuRatio?: string; // 因环境不同可能不存在
|
||||||
memoryRatio?: string; // 因环境不同可能不存在
|
memoryRatio?: string; // 因环境不同可能不存在
|
||||||
temperature?: number;
|
|
||||||
logTime?: string;
|
logTime?: string;
|
||||||
info?: {
|
info?: {
|
||||||
overFlowPorts?: string[];
|
overFlowPorts?: string[];
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ export interface NdmSecurityBox extends BaseModel {
|
|||||||
description: string;
|
description: string;
|
||||||
deviceStatus: string;
|
deviceStatus: string;
|
||||||
deviceType: string;
|
deviceType: string;
|
||||||
circuitCount: number;
|
|
||||||
community: string;
|
community: string;
|
||||||
writeCommunity: string;
|
|
||||||
frontendConfig: string;
|
frontendConfig: string;
|
||||||
linkDescription: string;
|
linkDescription: string;
|
||||||
snmpEnabled: boolean;
|
snmpEnabled: boolean;
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export interface NdmSwitch extends BaseModel {
|
|||||||
deviceStatus: string;
|
deviceStatus: string;
|
||||||
deviceType: string;
|
deviceType: string;
|
||||||
community: string;
|
community: string;
|
||||||
writeCommunity: string;
|
|
||||||
frontendConfig: string;
|
frontendConfig: string;
|
||||||
linkDescription: string;
|
linkDescription: string;
|
||||||
snmpEnabled: boolean;
|
snmpEnabled: boolean;
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ export interface NdmNvr extends BaseModel {
|
|||||||
deviceStatus: string;
|
deviceStatus: string;
|
||||||
deviceType: string;
|
deviceType: string;
|
||||||
community: string;
|
community: string;
|
||||||
writeCommunity: string;
|
|
||||||
frontendConfig: string;
|
frontendConfig: string;
|
||||||
linkDescription: string;
|
linkDescription: string;
|
||||||
snmpEnabled: boolean;
|
snmpEnabled: boolean;
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export interface NdmCamera extends BaseModel {
|
|||||||
deviceType: string;
|
deviceType: string;
|
||||||
cameraType: string;
|
cameraType: string;
|
||||||
community: string;
|
community: string;
|
||||||
writeCommunity: string;
|
|
||||||
frontendConfig: string;
|
frontendConfig: string;
|
||||||
linkDescription: string;
|
linkDescription: string;
|
||||||
snmpEnabled: boolean;
|
snmpEnabled: boolean;
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export interface NdmDecoder extends BaseModel {
|
|||||||
deviceStatus: string;
|
deviceStatus: string;
|
||||||
deviceType: string;
|
deviceType: string;
|
||||||
community: string;
|
community: string;
|
||||||
writeCommunity: string;
|
|
||||||
frontendConfig: string;
|
frontendConfig: string;
|
||||||
linkDescription: string;
|
linkDescription: string;
|
||||||
snmpEnabled: boolean;
|
snmpEnabled: boolean;
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export interface NdmKeyboard extends BaseModel {
|
|||||||
deviceStatus: string;
|
deviceStatus: string;
|
||||||
deviceType: string;
|
deviceType: string;
|
||||||
community: string;
|
community: string;
|
||||||
writeCommunity: string;
|
|
||||||
frontendConfig: string;
|
frontendConfig: string;
|
||||||
linkDescription: string;
|
linkDescription: string;
|
||||||
snmpEnabled: boolean;
|
snmpEnabled: boolean;
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export interface NdmMediaServer extends BaseModel {
|
|||||||
deviceStatus: string;
|
deviceStatus: string;
|
||||||
deviceType: string;
|
deviceType: string;
|
||||||
community: string;
|
community: string;
|
||||||
writeCommunity: string;
|
|
||||||
frontendConfig: string;
|
frontendConfig: string;
|
||||||
linkDescription: string;
|
linkDescription: string;
|
||||||
snmpEnabled: boolean;
|
snmpEnabled: boolean;
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export interface NdmVideoServer extends BaseModel {
|
|||||||
deviceStatus: string;
|
deviceStatus: string;
|
||||||
deviceType: string;
|
deviceType: string;
|
||||||
community: string;
|
community: string;
|
||||||
writeCommunity: string;
|
|
||||||
frontendConfig: string;
|
frontendConfig: string;
|
||||||
linkDescription: string;
|
linkDescription: string;
|
||||||
snmpEnabled: boolean;
|
snmpEnabled: boolean;
|
||||||
|
|||||||
@@ -93,46 +93,22 @@ export const probeSecurityBoxApi = async (ids: string[], options?: { stationCode
|
|||||||
unwrapVoidResponse(resp);
|
unwrapVoidResponse(resp);
|
||||||
};
|
};
|
||||||
|
|
||||||
// beidian安防箱切换空开状态
|
export const turnCitcuitStatusApi = async (ipAddress: string, circuitIndex: number, status: number, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||||
export const turnCircuitStatusBeidianApi = async (community: string, ipAddress: string, circuitIndex: number, status: number, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
|
||||||
const { stationCode, signal } = options ?? {};
|
const { stationCode, signal } = options ?? {};
|
||||||
const client = stationCode ? ndmClient : userClient;
|
const client = stationCode ? ndmClient : userClient;
|
||||||
const prefix = stationCode ? `/${stationCode}` : '';
|
const prefix = stationCode ? `/${stationCode}` : '';
|
||||||
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/turnStatus`;
|
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/turnStatus`;
|
||||||
const resp = await client.post<boolean>(endpoint, { community, ipAddress, circuit: `${circuitIndex}`, status }, { signal });
|
const resp = await client.post<boolean>(endpoint, { community: 'public', ipAddress, circuit: `${circuitIndex}`, status }, { signal });
|
||||||
const data = unwrapResponse(resp);
|
const data = unwrapResponse(resp);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// beidian安防箱重启
|
export const rebootSecurityBoxApi = async (ipAddress: string, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||||
export const rebootSecurityBoxBeidianApi = async (community: string, ipAddress: string, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
|
||||||
const { stationCode, signal } = options ?? {};
|
const { stationCode, signal } = options ?? {};
|
||||||
const client = stationCode ? ndmClient : userClient;
|
const client = stationCode ? ndmClient : userClient;
|
||||||
const prefix = stationCode ? `/${stationCode}` : '';
|
const prefix = stationCode ? `/${stationCode}` : '';
|
||||||
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/reboot`;
|
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/reboot`;
|
||||||
const resp = await client.post<boolean>(endpoint, { community, ipAddress }, { signal });
|
const resp = await client.post<boolean>(endpoint, { community: 'public', ipAddress }, { signal });
|
||||||
const data = unwrapResponse(resp);
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ningtech安防箱切换空开状态
|
|
||||||
export const turnCircuitStatusNingTechApi = async (community: string, ipAddress: string, circuitIndex: number, status: number, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
|
||||||
const { stationCode, signal } = options ?? {};
|
|
||||||
const client = stationCode ? ndmClient : userClient;
|
|
||||||
const prefix = stationCode ? `/${stationCode}` : '';
|
|
||||||
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/turnStatusNingTech`;
|
|
||||||
const resp = await client.post<boolean>(endpoint, { community, ipAddress, circuit: `${circuitIndex}`, status }, { signal });
|
|
||||||
const data = unwrapResponse(resp);
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ningtech安防箱重启
|
|
||||||
export const rebootSecurityBoxNingTechApi = async (community: string, ipAddress: string, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
|
||||||
const { stationCode, signal } = options ?? {};
|
|
||||||
const client = stationCode ? ndmClient : userClient;
|
|
||||||
const prefix = stationCode ? `/${stationCode}` : '';
|
|
||||||
const endpoint = `${prefix}/api/ndm/ndmSecurityBox/rebootNingTech`;
|
|
||||||
const resp = await client.post<boolean>(endpoint, { community, ipAddress }, { signal });
|
|
||||||
const data = unwrapResponse(resp);
|
const data = unwrapResponse(resp);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NCard, NDescriptions, NDescriptionsItem, NFlex, NText } from 'naive-ui';
|
import { NCard, NDescriptions, NDescriptionsItem } from 'naive-ui';
|
||||||
import { computed, toRefs } from 'vue';
|
import { computed, toRefs } from 'vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
commonInfo?: Array<{
|
commonInfo?: Record<string, string>;
|
||||||
title: string;
|
|
||||||
items: Array<{
|
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
}>;
|
|
||||||
}>;
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { commonInfo } = toRefs(props);
|
const { commonInfo } = toRefs(props);
|
||||||
|
|
||||||
const showCard = computed(() => (commonInfo.value?.length ?? 0) > 0);
|
const showCard = computed(() => !!commonInfo.value);
|
||||||
|
|
||||||
|
const commonInfoEntries = computed(() => Object.entries(commonInfo.value ?? {}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -22,20 +18,11 @@ const showCard = computed(() => (commonInfo.value?.length ?? 0) > 0);
|
|||||||
<template #header>
|
<template #header>
|
||||||
<div>设备信息</div>
|
<div>设备信息</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="!!commonInfo && commonInfo.length > 0">
|
<NDescriptions bordered label-placement="left" :columns="2">
|
||||||
<NFlex vertical :size="16">
|
<template v-for="item in commonInfoEntries" :key="item[0]">
|
||||||
<template v-for="({ title, items }, index) in commonInfo" :key="`${title}-${index}`">
|
<NDescriptionsItem :label="item[0]">{{ item[1] }}</NDescriptionsItem>
|
||||||
<NFlex vertical>
|
|
||||||
<NText strong :depth="3">{{ title }}</NText>
|
|
||||||
<NDescriptions bordered :label-placement="'left'" :columns="2">
|
|
||||||
<template v-for="({ label, value }, index) in items" :key="`${label}-${index}`">
|
|
||||||
<NDescriptionsItem :label="label">{{ value }}</NDescriptionsItem>
|
|
||||||
</template>
|
</template>
|
||||||
</NDescriptions>
|
</NDescriptions>
|
||||||
</NFlex>
|
|
||||||
</template>
|
|
||||||
</NFlex>
|
|
||||||
</template>
|
|
||||||
</NCard>
|
</NCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
+12
-30
@@ -1,18 +1,3 @@
|
|||||||
<script lang="ts">
|
|
||||||
const parsePercentMetric = (raw?: string) => {
|
|
||||||
const trimmed = raw?.trim();
|
|
||||||
|
|
||||||
if (!!trimmed) {
|
|
||||||
const value = Number(trimmed);
|
|
||||||
if (Number.isFinite(value)) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ClockCheckIcon, CpuIcon, HardDriveIcon, MemoryStickIcon } from 'lucide-vue-next';
|
import { ClockCheckIcon, CpuIcon, HardDriveIcon, MemoryStickIcon } from 'lucide-vue-next';
|
||||||
import { NCard, NFlex, NIcon, NProgress, type ProgressStatus } from 'naive-ui';
|
import { NCard, NFlex, NIcon, NProgress, type ProgressStatus } from 'naive-ui';
|
||||||
@@ -36,27 +21,24 @@ const showCard = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const cpuPercent = computed(() => {
|
const cpuPercent = computed(() => {
|
||||||
return parsePercentMetric(cpuUsage.value);
|
if (!cpuUsage?.value) return 0;
|
||||||
|
return parseFloat(cpuUsage.value.replace('%', ''));
|
||||||
});
|
});
|
||||||
|
|
||||||
const memPercent = computed(() => {
|
const memPercent = computed(() => {
|
||||||
return parsePercentMetric(memUsage.value);
|
if (!memUsage?.value) return 0;
|
||||||
|
return parseFloat(memUsage.value.replace('%', ''));
|
||||||
});
|
});
|
||||||
|
|
||||||
const diskPercent = computed(() => {
|
const diskPercent = computed(() => {
|
||||||
return parsePercentMetric(diskUsage.value);
|
if (!diskUsage?.value) return 0;
|
||||||
|
return parseFloat(diskUsage.value.replace('%', ''));
|
||||||
});
|
});
|
||||||
|
|
||||||
const formattedRunningTime = computed(() => {
|
const formattedRunningTime = computed(() => {
|
||||||
return (runningTime?.value ?? '-').replace('days', '天');
|
return (runningTime?.value ?? '-').replace('days', '天');
|
||||||
});
|
});
|
||||||
|
|
||||||
const getProgressPercentage = (percent: number) => {
|
|
||||||
if (percent < 0) return 0;
|
|
||||||
if (percent > 100) return 100;
|
|
||||||
return percent;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getProgressStatus = (percent: number): ProgressStatus => {
|
const getProgressStatus = (percent: number): ProgressStatus => {
|
||||||
if (percent >= 90) return 'error';
|
if (percent >= 90) return 'error';
|
||||||
if (percent >= 70) return 'warning';
|
if (percent >= 70) return 'warning';
|
||||||
@@ -71,20 +53,20 @@ const getProgressStatus = (percent: number): ProgressStatus => {
|
|||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<NFlex vertical>
|
<NFlex vertical>
|
||||||
<NFlex v-if="cpuPercent" style="width: 100%" align="center" :wrap="false">
|
<NFlex v-if="cpuUsage" style="width: 100%" align="center" :wrap="false">
|
||||||
<NIcon :component="CpuIcon" />
|
<NIcon :component="CpuIcon" />
|
||||||
<span style="word-break: keep-all">{{ cpuUsageLabel || 'CPU' }}</span>
|
<span style="word-break: keep-all">{{ cpuUsageLabel || 'CPU' }}</span>
|
||||||
<NProgress :percentage="getProgressPercentage(cpuPercent)" :status="getProgressStatus(cpuPercent)">{{ cpuPercent }}%</NProgress>
|
<NProgress :percentage="cpuPercent" :status="getProgressStatus(cpuPercent)">{{ cpuPercent }}%</NProgress>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
<NFlex v-if="memPercent" style="width: 100%" align="center" :wrap="false">
|
<NFlex v-if="memUsage" style="width: 100%" align="center" :wrap="false">
|
||||||
<NIcon :component="MemoryStickIcon" />
|
<NIcon :component="MemoryStickIcon" />
|
||||||
<span style="word-break: keep-all">{{ memUsageLabel || '内存' }}</span>
|
<span style="word-break: keep-all">{{ memUsageLabel || '内存' }}</span>
|
||||||
<NProgress :percentage="getProgressPercentage(memPercent)" :status="getProgressStatus(memPercent)">{{ memPercent }}%</NProgress>
|
<NProgress :percentage="memPercent" :status="getProgressStatus(memPercent)">{{ memPercent }}%</NProgress>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
<NFlex v-if="diskPercent" style="width: 100%" align="center" :wrap="false">
|
<NFlex v-if="diskUsage" style="width: 100%" align="center" :wrap="false">
|
||||||
<NIcon :component="HardDriveIcon" />
|
<NIcon :component="HardDriveIcon" />
|
||||||
<span style="word-break: keep-all">{{ diskUsageLabel || '磁盘' }}</span>
|
<span style="word-break: keep-all">{{ diskUsageLabel || '磁盘' }}</span>
|
||||||
<NProgress :percentage="getProgressPercentage(diskPercent)" :status="getProgressStatus(diskPercent)">{{ diskPercent }}%</NProgress>
|
<NProgress :percentage="diskPercent" :status="getProgressStatus(diskPercent)">{{ diskPercent }}%</NProgress>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
<NFlex v-if="runningTime" style="width: 100%" align="center" :wrap="false">
|
<NFlex v-if="runningTime" style="width: 100%" align="center" :wrap="false">
|
||||||
<NIcon :component="ClockCheckIcon" />
|
<NIcon :component="ClockCheckIcon" />
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import type { ComponentInstance } from 'vue';
|
|
||||||
import DeviceCommonCard from './device-common-card.vue';
|
import DeviceCommonCard from './device-common-card.vue';
|
||||||
import DeviceHardwareCard from './device-hardware-card.vue';
|
import DeviceHardwareCard from './device-hardware-card.vue';
|
||||||
import DeviceHeaderCard from './device-header-card.vue';
|
import DeviceHeaderCard from './device-header-card.vue';
|
||||||
import NvrEnvCard from './nvr-env-card.vue';
|
|
||||||
import NvrDiskCard from './nvr-disk-card.vue';
|
import NvrDiskCard from './nvr-disk-card.vue';
|
||||||
import NvrRecordCheckCard from './nvr-record-check-card.vue';
|
import NvrRecordCheckCard from './nvr-record-check-card.vue';
|
||||||
import SecurityBoxCircuitCard from './security-box-circuit-card.vue';
|
import SecurityBoxCircuitCard from './security-box-circuit-card.vue';
|
||||||
@@ -11,13 +9,10 @@ import SecurityBoxEnvCard from './security-box-env-card.vue';
|
|||||||
import SwitchPortCard from './switch-port-card.vue';
|
import SwitchPortCard from './switch-port-card.vue';
|
||||||
import SwitchPortLinkModal from './switch-port-link-modal.vue';
|
import SwitchPortLinkModal from './switch-port-link-modal.vue';
|
||||||
|
|
||||||
export type DeviceCommonCardProps = ComponentInstance<typeof DeviceCommonCard>['$props'];
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DeviceCommonCard,
|
DeviceCommonCard,
|
||||||
DeviceHardwareCard,
|
DeviceHardwareCard,
|
||||||
DeviceHeaderCard,
|
DeviceHeaderCard,
|
||||||
NvrEnvCard,
|
|
||||||
NvrDiskCard,
|
NvrDiskCard,
|
||||||
NvrRecordCheckCard,
|
NvrRecordCheckCard,
|
||||||
SecurityBoxCircuitCard,
|
SecurityBoxCircuitCard,
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { NdmNvrFanInfo, NdmNvrPowerSupplyInfo } from '@/apis';
|
|
||||||
import { FanIcon, PlugIcon } from 'lucide-vue-next';
|
|
||||||
import { NCard, NFlex, NIcon, NTag } from 'naive-ui';
|
|
||||||
import { computed, toRefs } from 'vue';
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
fanInfo?: NdmNvrFanInfo[];
|
|
||||||
powerSupplyInfo?: NdmNvrPowerSupplyInfo[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const { fanInfo, powerSupplyInfo } = toRefs(props);
|
|
||||||
|
|
||||||
const showCard = computed(() => {
|
|
||||||
return Object.values(props).some((value) => !!value);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<NCard v-if="showCard" hoverable size="small">
|
|
||||||
<template #header>
|
|
||||||
<span>录像机环境状态</span>
|
|
||||||
</template>
|
|
||||||
<template #default>
|
|
||||||
<NFlex vertical>
|
|
||||||
<NTag v-for="info in fanInfo ?? []" :key="info['索引号']">
|
|
||||||
<template #icon>
|
|
||||||
<NIcon :component="FanIcon" />
|
|
||||||
</template>
|
|
||||||
<template #default>
|
|
||||||
<span>风扇{{ info['索引号'] }}: {{ info['风扇转速(rpm)'] }} RPM</span>
|
|
||||||
</template>
|
|
||||||
</NTag>
|
|
||||||
<NTag v-for="info in powerSupplyInfo ?? []" :key="info['索引号']">
|
|
||||||
<template #icon>
|
|
||||||
<NIcon :component="PlugIcon" />
|
|
||||||
</template>
|
|
||||||
<template #default>
|
|
||||||
<span>电源{{ info['索引号'] }}: {{ info['电源状态'] }}</span>
|
|
||||||
</template>
|
|
||||||
</NTag>
|
|
||||||
</NFlex>
|
|
||||||
</template>
|
|
||||||
</NCard>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
+7
-21
@@ -2,6 +2,8 @@
|
|||||||
import {
|
import {
|
||||||
detailDeviceApi,
|
detailDeviceApi,
|
||||||
probeDeviceApi,
|
probeDeviceApi,
|
||||||
|
rebootSecurityBoxApi,
|
||||||
|
turnCitcuitStatusApi,
|
||||||
updateDeviceApi,
|
updateDeviceApi,
|
||||||
type LinkDescription,
|
type LinkDescription,
|
||||||
type NdmDeviceResultVO,
|
type NdmDeviceResultVO,
|
||||||
@@ -14,7 +16,6 @@ import { SecurityBoxCircuitLinkModal } from '@/components';
|
|||||||
import { usePermission } from '@/composables';
|
import { usePermission } from '@/composables';
|
||||||
import { SELECT_DEVICE_FN_INJECTION_KEY } from '@/constants';
|
import { SELECT_DEVICE_FN_INJECTION_KEY } from '@/constants';
|
||||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
import { normalizeSecurityBoxWriteCommunity, dispatchRebootSecurityBoxApi, dispatchTurnCircuitStatusApi, normalizeSecurityBoxCircuitIndex } from '@/helpers';
|
|
||||||
import { useDeviceStore, useSettingStore } from '@/stores';
|
import { useDeviceStore, useSettingStore } from '@/stores';
|
||||||
import { parseErrorFeedback } from '@/utils';
|
import { parseErrorFeedback } from '@/utils';
|
||||||
import { useMutation } from '@tanstack/vue-query';
|
import { useMutation } from '@tanstack/vue-query';
|
||||||
@@ -30,7 +31,6 @@ import { computed, inject, ref, toRefs } from 'vue';
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
ndmDevice: NdmSecurityBoxResultVO;
|
ndmDevice: NdmSecurityBoxResultVO;
|
||||||
station: Station;
|
station: Station;
|
||||||
vendor?: string;
|
|
||||||
circuits?: NdmSecurityBoxCircuit[];
|
circuits?: NdmSecurityBoxCircuit[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ const { useLocalDB } = storeToRefs(settingStore);
|
|||||||
|
|
||||||
const { hasPermission } = usePermission();
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
const { ndmDevice, station, vendor, circuits } = toRefs(props);
|
const { ndmDevice, station, circuits } = toRefs(props);
|
||||||
|
|
||||||
const showCard = computed(() => !!circuits.value && circuits.value.length > 0);
|
const showCard = computed(() => !!circuits.value && circuits.value.length > 0);
|
||||||
|
|
||||||
@@ -52,10 +52,6 @@ const localCircuits = ref<NdmSecurityBoxCircuit[]>([]);
|
|||||||
|
|
||||||
watchImmediate(circuits, (newCircuits) => {
|
watchImmediate(circuits, (newCircuits) => {
|
||||||
localCircuits.value = newCircuits?.map((circuit) => ({ ...circuit })) ?? [];
|
localCircuits.value = newCircuits?.map((circuit) => ({ ...circuit })) ?? [];
|
||||||
const circuitCount = ndmDevice.value.circuitCount;
|
|
||||||
if (!!circuitCount) {
|
|
||||||
localCircuits.value = localCircuits.value.slice(0, circuitCount);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getCircuitStatusTagType = (circuit: NdmSecurityBoxCircuit): TagProps['type'] => {
|
const getCircuitStatusTagType = (circuit: NdmSecurityBoxCircuit): TagProps['type'] => {
|
||||||
@@ -83,19 +79,13 @@ const { mutate: turnStatus, isPending: turning } = useMutation({
|
|||||||
window.$loadingBar.start();
|
window.$loadingBar.start();
|
||||||
|
|
||||||
const { circuitIndex, newStatus } = params;
|
const { circuitIndex, newStatus } = params;
|
||||||
const community = normalizeSecurityBoxWriteCommunity(ndmDevice.value, vendor.value);
|
if (!ndmDevice.value.ipAddress) {
|
||||||
const ipAddress = ndmDevice.value.ipAddress;
|
|
||||||
if (!ipAddress) {
|
|
||||||
throw new Error('设备IP地址不存在');
|
throw new Error('设备IP地址不存在');
|
||||||
}
|
}
|
||||||
const status = newStatus ? 1 : 0;
|
const status = newStatus ? 1 : 0;
|
||||||
|
|
||||||
const stationCode = station.value.code;
|
const stationCode = station.value.code;
|
||||||
const signal = abortController.value.signal;
|
const signal = abortController.value.signal;
|
||||||
|
await turnCitcuitStatusApi(ndmDevice.value.ipAddress, circuitIndex, status, { stationCode, signal });
|
||||||
const turnCircuitStatusApi = dispatchTurnCircuitStatusApi(vendor.value);
|
|
||||||
const normalizedCircuitIndex = normalizeSecurityBoxCircuitIndex(circuitIndex, vendor.value);
|
|
||||||
await turnCircuitStatusApi(community, ipAddress, normalizedCircuitIndex, status, { stationCode, signal });
|
|
||||||
await probeDeviceApi(ndmDevice.value, { stationCode, signal });
|
await probeDeviceApi(ndmDevice.value, { stationCode, signal });
|
||||||
return status;
|
return status;
|
||||||
},
|
},
|
||||||
@@ -120,17 +110,13 @@ const { mutate: reboot, isPending: rebooting } = useMutation({
|
|||||||
|
|
||||||
window.$loadingBar.start();
|
window.$loadingBar.start();
|
||||||
|
|
||||||
const community = normalizeSecurityBoxWriteCommunity(ndmDevice.value, vendor.value);
|
if (!ndmDevice.value.ipAddress) {
|
||||||
const ipAddress = ndmDevice.value.ipAddress;
|
|
||||||
if (!ipAddress) {
|
|
||||||
throw new Error('设备IP地址不存在');
|
throw new Error('设备IP地址不存在');
|
||||||
}
|
}
|
||||||
|
|
||||||
const stationCode = station.value.code;
|
const stationCode = station.value.code;
|
||||||
const signal = abortController.value.signal;
|
const signal = abortController.value.signal;
|
||||||
|
await rebootSecurityBoxApi(ndmDevice.value.ipAddress, { stationCode, signal });
|
||||||
const rebootSecurityBoxApi = dispatchRebootSecurityBoxApi(vendor.value);
|
|
||||||
await rebootSecurityBoxApi(community, ipAddress, { stationCode, signal });
|
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
window.$loadingBar.finish();
|
window.$loadingBar.finish();
|
||||||
|
|||||||
+22
-17
@@ -16,35 +16,40 @@ const showCard = computed(() => {
|
|||||||
return Object.values(props).some((value) => !!value);
|
return Object.values(props).some((value) => !!value);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 防雷状态
|
|
||||||
const lightningProtectionStatus = computed(() => {
|
|
||||||
if (!switches?.value || switches.value.length < 2) return null;
|
|
||||||
const status = switches.value.at(0)!;
|
|
||||||
return status === 0 ? '失效' : status === 1 ? '生效' : '-';
|
|
||||||
});
|
|
||||||
|
|
||||||
// 门禁状态
|
// 门禁状态
|
||||||
const accessControlStatus = computed(() => {
|
const accessControlStatus = computed(() => {
|
||||||
if (!switches?.value || switches.value.length < 1) return null;
|
if (!switches?.value || switches.value.length < 1) return null;
|
||||||
const status = switches.value.at(1)!;
|
const status = switches.value.at(0)!;
|
||||||
return status === 0 ? '开门' : status === 1 ? '关门' : '-';
|
return status === 0 ? '开门' : status === 1 ? '关门' : '-';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 防雷状态
|
||||||
|
const lightningProtectionStatus = computed(() => {
|
||||||
|
if (!switches?.value || switches.value.length < 2) return null;
|
||||||
|
const status = switches.value.at(1)!;
|
||||||
|
return status === 0 ? '正常' : status === 1 ? '失效' : '-';
|
||||||
|
});
|
||||||
|
|
||||||
const getStatusTagType = (status: string | null) => {
|
const getStatusTagType = (status: string | null) => {
|
||||||
if (['生效', '关门'].includes(status ?? '')) return 'success';
|
if (['正常'].includes(status ?? '')) return 'success';
|
||||||
if (['失效', '开门'].includes(status ?? '')) return 'error';
|
if (['失效'].includes(status ?? '')) return 'error';
|
||||||
return 'default';
|
return 'default';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formattedFanSpeeds = computed(() => {
|
||||||
|
if (!fanSpeeds?.value || fanSpeeds.value.length === 0) return null;
|
||||||
|
return fanSpeeds.value.map((speed, index) => `风扇${index + 1}: ${speed} RPM`).join(', ');
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NCard v-if="showCard" hoverable size="small">
|
<NCard v-if="showCard" hoverable size="small">
|
||||||
<template #header>
|
<template #header>
|
||||||
<span>安防箱环境状态</span>
|
<span>安防箱状态</span>
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<NFlex vertical>
|
<NFlex vertical>
|
||||||
<NTag v-if="!!temperature">
|
<NTag>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="ThermometerIcon" />
|
<NIcon :component="ThermometerIcon" />
|
||||||
</template>
|
</template>
|
||||||
@@ -52,7 +57,7 @@ const getStatusTagType = (status: string | null) => {
|
|||||||
<span>温度: {{ temperature }}℃</span>
|
<span>温度: {{ temperature }}℃</span>
|
||||||
</template>
|
</template>
|
||||||
</NTag>
|
</NTag>
|
||||||
<NTag v-if="!!humidity">
|
<NTag>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="DropletIcon" />
|
<NIcon :component="DropletIcon" />
|
||||||
</template>
|
</template>
|
||||||
@@ -60,15 +65,15 @@ const getStatusTagType = (status: string | null) => {
|
|||||||
<span>湿度: {{ humidity }}%</span>
|
<span>湿度: {{ humidity }}%</span>
|
||||||
</template>
|
</template>
|
||||||
</NTag>
|
</NTag>
|
||||||
<NTag v-for="(speed, index) in fanSpeeds ?? []" :key="index">
|
<NTag>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="FanIcon" />
|
<NIcon :component="FanIcon" />
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<span>风扇{{ index + 1 }}: {{ speed }} RPM</span>
|
<span>风扇: {{ formattedFanSpeeds }}</span>
|
||||||
</template>
|
</template>
|
||||||
</NTag>
|
</NTag>
|
||||||
<NTag v-if="!!accessControlStatus" :type="getStatusTagType(accessControlStatus)">
|
<NTag :type="getStatusTagType(accessControlStatus)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="ShieldIcon" />
|
<NIcon :component="ShieldIcon" />
|
||||||
</template>
|
</template>
|
||||||
@@ -76,7 +81,7 @@ const getStatusTagType = (status: string | null) => {
|
|||||||
<span>门禁: {{ accessControlStatus }}</span>
|
<span>门禁: {{ accessControlStatus }}</span>
|
||||||
</template>
|
</template>
|
||||||
</NTag>
|
</NTag>
|
||||||
<NTag v-if="!!lightningProtectionStatus" :type="getStatusTagType(lightningProtectionStatus)">
|
<NTag :type="getStatusTagType(lightningProtectionStatus)">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<NIcon :component="ZapIcon" />
|
<NIcon :component="ZapIcon" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const { hasPermission } = usePermission();
|
|||||||
|
|
||||||
const { ndmDevice, station, ports } = toRefs(props);
|
const { ndmDevice, station, ports } = toRefs(props);
|
||||||
|
|
||||||
const showCard = computed(() => !!ports.value && ports.value.length > 0);
|
const showCard = computed(() => !!ports.value);
|
||||||
|
|
||||||
const switchSlots = computed(() => {
|
const switchSlots = computed(() => {
|
||||||
// 解析端口名称,将端口按槽位进行分组
|
// 解析端口名称,将端口按槽位进行分组
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NFlex } from 'naive-ui';
|
import { NFlex } from 'naive-ui';
|
||||||
import { DeviceCommonCard, DeviceHeaderCard, type DeviceCommonCardProps } from '@/components';
|
import { DeviceCommonCard, DeviceHeaderCard } from '@/components';
|
||||||
import type { NdmAlarmHostResultVO, Station } from '@/apis';
|
import type { NdmAlarmHostResultVO, Station } from '@/apis';
|
||||||
import { computed, toRefs } from 'vue';
|
import { computed, toRefs } from 'vue';
|
||||||
|
|
||||||
@@ -11,18 +11,13 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const { ndmDevice, station } = toRefs(props);
|
const { ndmDevice, station } = toRefs(props);
|
||||||
|
|
||||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
const commonInfo = computed(() => {
|
||||||
const { createdTime, updatedTime, manufacturer } = ndmDevice.value;
|
const { createdTime, updatedTime, manufacturer } = ndmDevice.value;
|
||||||
return [
|
return {
|
||||||
{
|
创建时间: createdTime ?? '-',
|
||||||
title: '设备接入信息',
|
更新时间: updatedTime ?? '-',
|
||||||
items: [
|
制造商: manufacturer ?? '-',
|
||||||
{ label: '创建时间', value: createdTime || '-' },
|
};
|
||||||
{ label: '更新时间', value: updatedTime || '-' },
|
|
||||||
{ label: '制造商', value: manufacturer || '-' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,11 @@ const isCameraTypeCode = (code: string): code is CameraType => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmCameraDiagInfo, NdmCameraResultVO, Station } from '@/apis';
|
import type { NdmCameraResultVO, Station } from '@/apis';
|
||||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, type DeviceCommonCardProps } from '@/components';
|
import { DeviceCommonCard, DeviceHeaderCard } from '@/components';
|
||||||
import { useSettingStore } from '@/stores';
|
import { useSettingStore } from '@/stores';
|
||||||
import { useQuery, useQueryClient } from '@tanstack/vue-query';
|
import { useQuery, useQueryClient } from '@tanstack/vue-query';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import destr from 'destr';
|
|
||||||
import { NDescriptions, NDescriptionsItem, NFlex } from 'naive-ui';
|
import { NDescriptions, NDescriptionsItem, NFlex } from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { computed, toRefs, watch } from 'vue';
|
import { computed, toRefs, watch } from 'vue';
|
||||||
@@ -114,17 +113,7 @@ watch(activeRequests, (active) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const lastDiagInfo = computed(() => {
|
const commonInfo = computed(() => {
|
||||||
const result = destr<any>(ndmDevice.value.lastDiagInfo);
|
|
||||||
if (!result) return null;
|
|
||||||
if (typeof result !== 'object') return null;
|
|
||||||
return result as NdmCameraDiagInfo;
|
|
||||||
});
|
|
||||||
|
|
||||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
|
||||||
const { stCommonInfo } = lastDiagInfo.value ?? {};
|
|
||||||
const { 设备ID, 软件版本, 设备厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
|
|
||||||
const { ethInfo, ipInfo } = lastDiagInfo.value ?? {};
|
|
||||||
const {
|
const {
|
||||||
createdTime,
|
createdTime,
|
||||||
updatedTime,
|
updatedTime,
|
||||||
@@ -137,58 +126,22 @@ const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
|||||||
onvifMinorIndex,
|
onvifMinorIndex,
|
||||||
icmpEnabled,
|
icmpEnabled,
|
||||||
community,
|
community,
|
||||||
ipAddress,
|
|
||||||
//
|
//
|
||||||
} = ndmDevice.value;
|
} = ndmDevice.value;
|
||||||
|
return {
|
||||||
let operStatus = '-';
|
创建时间: createdTime ?? '-',
|
||||||
if (!!ethInfo?.operStatus) {
|
更新时间: updatedTime ?? '-',
|
||||||
operStatus = ethInfo?.operStatus === '1' ? '正常' : '异常';
|
制造商: manufacturer ?? '-',
|
||||||
}
|
GB28181启用: `${!!gb28181Enabled ? '是' : '否'}`,
|
||||||
|
ONVIF端口: `${onvifPort ?? '-'}`,
|
||||||
return [
|
ONVIF用户名: onvifUsername ?? '-',
|
||||||
{
|
ONVIF密码: onvifPassword ?? '-',
|
||||||
title: '设备型号信息',
|
ONVIF主流索引: `${onvifMajorIndex ?? '-'}`,
|
||||||
items: [
|
ONVIF辅流索引: `${onvifMinorIndex ?? '-'}`,
|
||||||
{ label: '设备ID', value: 设备ID || '-' },
|
ICMP启用: `${!!icmpEnabled ? '是' : '否'}`,
|
||||||
{ label: '软件版本', value: 软件版本 || '-' },
|
团体字符串: community ?? '-',
|
||||||
{ label: '设备厂商', value: 设备厂商 || manufacturer || '-' },
|
};
|
||||||
{ label: '设备别名', value: 设备别名 || '-' },
|
|
||||||
{ label: '设备型号', value: 设备型号 || '-' },
|
|
||||||
{ label: '硬件版本', value: 硬件版本 || '-' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '设备网卡信息',
|
|
||||||
items: [
|
|
||||||
{ label: 'IP地址', value: ipAddress || '-' },
|
|
||||||
{ label: '子网掩码', value: ipInfo?.mASK || '-' },
|
|
||||||
{ label: 'MAC地址', value: ethInfo?.macAddress || '-' },
|
|
||||||
{ label: '连接速率', value: ethInfo?.speed || '-' },
|
|
||||||
{ label: 'MTU', value: ethInfo?.mTU || '-' },
|
|
||||||
{ label: '运行状态', value: operStatus },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '设备接入信息',
|
|
||||||
items: [
|
|
||||||
{ label: '创建时间', value: createdTime || '-' },
|
|
||||||
{ label: '更新时间', value: updatedTime || '-' },
|
|
||||||
{ label: 'GB28181启用', value: `${!!gb28181Enabled ? '是' : '否'}` },
|
|
||||||
{ label: 'ICMP启用', value: `${!!icmpEnabled ? '是' : '否'}` },
|
|
||||||
{ label: 'ONVIF用户名', value: onvifUsername || '-' },
|
|
||||||
{ label: 'ONVIF密码', value: onvifPassword || '-' },
|
|
||||||
{ label: 'ONVIF主流索引', value: `${onvifMajorIndex ?? '-'}` },
|
|
||||||
{ label: 'ONVIF辅流索引', value: `${onvifMinorIndex ?? '-'}` },
|
|
||||||
{ label: 'ONVIF端口', value: `${onvifPort ?? '-'}` },
|
|
||||||
{ label: '团体字符串', value: community || '-' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.CPU使用率);
|
|
||||||
const memUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.内存使用率);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -202,7 +155,6 @@ const memUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.内存使用
|
|||||||
</template>
|
</template>
|
||||||
</DeviceHeaderCard>
|
</DeviceHeaderCard>
|
||||||
<DeviceCommonCard :common-info="commonInfo" />
|
<DeviceCommonCard :common-info="commonInfo" />
|
||||||
<DeviceHardwareCard :cpu-usage="cpuUsage" :mem-usage="memUsage" />
|
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmCameraResultVO, Station } from '@/apis';
|
import type { NdmCameraResultVO, Station } from '@/apis';
|
||||||
import {
|
import { DeviceAlarmHistoryCard, DeviceIcmpHistoryCard, HistoryDiagFilterCard, type DeviceAlarmHistoryCardProps, type DeviceIcmpHistoryCardProps } from '@/components';
|
||||||
DeviceAlarmHistoryCard,
|
|
||||||
DeviceIcmpHistoryCard,
|
|
||||||
DeviceUsageHistoryCard,
|
|
||||||
HistoryDiagFilterCard,
|
|
||||||
type DeviceAlarmHistoryCardProps,
|
|
||||||
type DeviceIcmpHistoryCardProps,
|
|
||||||
type DeviceUsageHistoryCardProps,
|
|
||||||
} from '@/components';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { NFlex, type SelectOption } from 'naive-ui';
|
import { NFlex, type SelectOption } from 'naive-ui';
|
||||||
import { onMounted, ref, toRefs, watch } from 'vue';
|
import { onMounted, ref, toRefs, watch } from 'vue';
|
||||||
@@ -23,7 +15,6 @@ const { ndmDevice, station } = toRefs(props);
|
|||||||
const historyDiagOptions: SelectOption[] = [
|
const historyDiagOptions: SelectOption[] = [
|
||||||
{ label: '设备状态', value: 'icmp' },
|
{ label: '设备状态', value: 'icmp' },
|
||||||
{ label: '设备告警', value: 'alarm' },
|
{ label: '设备告警', value: 'alarm' },
|
||||||
{ label: '硬件占用', value: 'usage' },
|
|
||||||
];
|
];
|
||||||
const getWeekRange = (): [number, number] => {
|
const getWeekRange = (): [number, number] => {
|
||||||
const now = dayjs();
|
const now = dayjs();
|
||||||
@@ -36,8 +27,7 @@ const selected = ref<string[]>([...historyDiagOptions.map((option) => `${option.
|
|||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
const icmpLoading = ref<boolean>(false);
|
const icmpLoading = ref<boolean>(false);
|
||||||
const alarmLoading = ref<boolean>(false);
|
const alarmLoading = ref<boolean>(false);
|
||||||
const usageLoading = ref<boolean>(false);
|
watch([icmpLoading, alarmLoading], (loadings) => {
|
||||||
watch([icmpLoading, alarmLoading, usageLoading], (loadings) => {
|
|
||||||
loading.value = loadings.some((loading) => loading);
|
loading.value = loadings.some((loading) => loading);
|
||||||
});
|
});
|
||||||
const icmpHistoryQueryFn = ref<() => void>();
|
const icmpHistoryQueryFn = ref<() => void>();
|
||||||
@@ -48,14 +38,9 @@ const alarmHistoryQueryFn = ref<() => void>();
|
|||||||
const onExposeAlarmHistoryQueryFn: DeviceAlarmHistoryCardProps['onExposeQueryFn'] = (queryFn) => {
|
const onExposeAlarmHistoryQueryFn: DeviceAlarmHistoryCardProps['onExposeQueryFn'] = (queryFn) => {
|
||||||
alarmHistoryQueryFn.value = queryFn;
|
alarmHistoryQueryFn.value = queryFn;
|
||||||
};
|
};
|
||||||
const usageHistoryQueryFn = ref<() => void>();
|
|
||||||
const onExposeUsageHistoryQueryFn: DeviceUsageHistoryCardProps['onExposeQueryFn'] = (queryFn) => {
|
|
||||||
usageHistoryQueryFn.value = queryFn;
|
|
||||||
};
|
|
||||||
const queryData = () => {
|
const queryData = () => {
|
||||||
if (selected.value.includes('icmp')) icmpHistoryQueryFn.value?.();
|
if (selected.value.includes('icmp')) icmpHistoryQueryFn.value?.();
|
||||||
if (selected.value.includes('alarm')) alarmHistoryQueryFn.value?.();
|
if (selected.value.includes('alarm')) alarmHistoryQueryFn.value?.();
|
||||||
if (selected.value.includes('usage')) usageHistoryQueryFn.value?.();
|
|
||||||
};
|
};
|
||||||
const onQuery = () => {
|
const onQuery = () => {
|
||||||
queryData();
|
queryData();
|
||||||
@@ -85,16 +70,6 @@ onMounted(() => {
|
|||||||
v-model:loading="alarmLoading"
|
v-model:loading="alarmLoading"
|
||||||
@expose-query-fn="onExposeAlarmHistoryQueryFn"
|
@expose-query-fn="onExposeAlarmHistoryQueryFn"
|
||||||
/>
|
/>
|
||||||
<DeviceUsageHistoryCard
|
|
||||||
v-if="selected.includes('usage')"
|
|
||||||
:ndm-device="ndmDevice"
|
|
||||||
:station="station"
|
|
||||||
:cpu-usage-field="'stCommonInfo.CPU使用率'"
|
|
||||||
:mem-usage-field="'stCommonInfo.内存使用率'"
|
|
||||||
v-model:range="range"
|
|
||||||
v-model:loading="usageLoading"
|
|
||||||
@expose-query-fn="onExposeUsageHistoryQueryFn"
|
|
||||||
/>
|
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmDecoderDiagInfo, NdmDecoderResultVO, Station } from '@/apis';
|
import type { NdmDecoderDiagInfo, NdmDecoderResultVO, Station } from '@/apis';
|
||||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, type DeviceCommonCardProps } from '@/components';
|
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard } from '@/components';
|
||||||
import destr from 'destr';
|
import destr from 'destr';
|
||||||
import { NFlex } from 'naive-ui';
|
import { NFlex } from 'naive-ui';
|
||||||
import { computed, toRefs } from 'vue';
|
import { computed, toRefs } from 'vue';
|
||||||
@@ -19,23 +19,17 @@ const lastDiagInfo = computed(() => {
|
|||||||
return result as NdmDecoderDiagInfo;
|
return result as NdmDecoderDiagInfo;
|
||||||
});
|
});
|
||||||
|
|
||||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
const commonInfo = computed(() => {
|
||||||
const { stCommonInfo } = lastDiagInfo.value ?? {};
|
const { stCommonInfo } = lastDiagInfo.value ?? {};
|
||||||
const { 设备ID, 软件版本, 设备厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
|
const { 设备ID, 软件版本, 设备厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
|
||||||
|
return {
|
||||||
return [
|
设备ID: 设备ID ?? '-',
|
||||||
{
|
软件版本: 软件版本 ?? '-',
|
||||||
title: '设备型号信息',
|
设备厂商: 设备厂商 ?? '-',
|
||||||
items: [
|
设备别名: 设备别名 ?? '-',
|
||||||
{ label: '设备ID', value: 设备ID || '-' },
|
设备型号: 设备型号 ?? '-',
|
||||||
{ label: '软件版本', value: 软件版本 || '-' },
|
硬件版本: 硬件版本 ?? '-',
|
||||||
{ label: '设备厂商', value: 设备厂商 || '-' },
|
};
|
||||||
{ label: '设备别名', value: 设备别名 || '-' },
|
|
||||||
{ label: '设备型号', value: 设备型号 || '-' },
|
|
||||||
{ label: '硬件版本', value: 硬件版本 || '-' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.CPU使用率);
|
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.CPU使用率);
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ const selected = ref([...historyDiagOptions.map((option) => `${option.value}`)])
|
|||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
const icmpLoading = ref<boolean>(false);
|
const icmpLoading = ref<boolean>(false);
|
||||||
const alarmLoading = ref<boolean>(false);
|
const alarmLoading = ref<boolean>(false);
|
||||||
const usageLoading = ref<boolean>(false);
|
watch([icmpLoading, alarmLoading], (loadings) => {
|
||||||
watch([icmpLoading, alarmLoading, usageLoading], (loadings) => {
|
|
||||||
loading.value = loadings.some((loading) => loading);
|
loading.value = loadings.some((loading) => loading);
|
||||||
});
|
});
|
||||||
const icmpHistoryQueryFn = ref<() => void>();
|
const icmpHistoryQueryFn = ref<() => void>();
|
||||||
@@ -92,7 +91,7 @@ onMounted(() => {
|
|||||||
:cpu-usage-field="'stCommonInfo.CPU使用率'"
|
:cpu-usage-field="'stCommonInfo.CPU使用率'"
|
||||||
:mem-usage-field="'stCommonInfo.内存使用率'"
|
:mem-usage-field="'stCommonInfo.内存使用率'"
|
||||||
v-model:range="range"
|
v-model:range="range"
|
||||||
v-model:loading="usageLoading"
|
v-model:loading="alarmLoading"
|
||||||
@expose-query-fn="onExposeUsageHistoryQueryFn"
|
@expose-query-fn="onExposeUsageHistoryQueryFn"
|
||||||
/>
|
/>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmNvrDiagInfo, NdmNvrResultVO, Station } from '@/apis';
|
import type { NdmNvrDiagInfo, NdmNvrResultVO, Station } from '@/apis';
|
||||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, NvrDiskCard, NvrEnvCard, NvrRecordCheckCard, type DeviceCommonCardProps } from '@/components';
|
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, NvrDiskCard, NvrRecordCheckCard } from '@/components';
|
||||||
import { isNvrCluster } from '@/helpers';
|
import { isNvrCluster } from '@/helpers';
|
||||||
import destr from 'destr';
|
import destr from 'destr';
|
||||||
import { NFlex } from 'naive-ui';
|
import { NFlex } from 'naive-ui';
|
||||||
@@ -20,40 +20,18 @@ const lastDiagInfo = computed(() => {
|
|||||||
return result as NdmNvrDiagInfo;
|
return result as NdmNvrDiagInfo;
|
||||||
});
|
});
|
||||||
|
|
||||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
const commonInfo = computed(() => {
|
||||||
const { ipAddress } = ndmDevice.value;
|
const { stCommonInfo } = lastDiagInfo.value ?? {};
|
||||||
const { ethInfo, ipInfo, stCommonInfo } = lastDiagInfo.value ?? {};
|
if (!stCommonInfo) return undefined;
|
||||||
const { 设备ID, 软件版本, 生产厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
|
const { 设备ID, 软件版本, 生产厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo;
|
||||||
|
return {
|
||||||
let operStatus = '-';
|
设备ID: 设备ID ?? '-',
|
||||||
if (!!ethInfo?.operStatus) {
|
软件版本: 软件版本 ?? '-',
|
||||||
operStatus = ethInfo?.operStatus === '1' ? '正常' : '异常';
|
生产厂商: 生产厂商 ?? '-',
|
||||||
}
|
设备别名: 设备别名 ?? '-',
|
||||||
|
设备型号: 设备型号 ?? '-',
|
||||||
return [
|
硬件版本: 硬件版本 ?? '-',
|
||||||
{
|
};
|
||||||
title: '设备型号信息',
|
|
||||||
items: [
|
|
||||||
{ label: '设备ID', value: 设备ID || '-' },
|
|
||||||
{ label: '软件版本', value: 软件版本 || '-' },
|
|
||||||
{ label: '生产厂商', value: 生产厂商 || '-' },
|
|
||||||
{ label: '设备别名', value: 设备别名 || '-' },
|
|
||||||
{ label: '设备型号', value: 设备型号 || '-' },
|
|
||||||
{ label: '硬件版本', value: 硬件版本 || '-' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '设备网卡信息',
|
|
||||||
items: [
|
|
||||||
{ label: 'IP地址', value: ipAddress || '-' },
|
|
||||||
{ label: '子网掩码', value: ipInfo?.mASK || '-' },
|
|
||||||
{ label: 'MAC地址', value: ethInfo?.macAddress || '-' },
|
|
||||||
{ label: '连接速率', value: ethInfo?.speed || '-' },
|
|
||||||
{ label: 'MTU', value: ethInfo?.mTU || '-' },
|
|
||||||
{ label: '运行状态', value: operStatus },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.CPU使用率);
|
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.CPU使用率);
|
||||||
@@ -61,9 +39,6 @@ const memUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.内存使用
|
|||||||
|
|
||||||
const diskHealth = computed(() => lastDiagInfo.value?.info?.diskHealth);
|
const diskHealth = computed(() => lastDiagInfo.value?.info?.diskHealth);
|
||||||
const diskArray = computed(() => lastDiagInfo.value?.info?.groupInfoList);
|
const diskArray = computed(() => lastDiagInfo.value?.info?.groupInfoList);
|
||||||
|
|
||||||
const fanInfo = computed(() => lastDiagInfo.value?.cdFanInfo);
|
|
||||||
const powerSupplyInfo = computed(() => lastDiagInfo.value?.cdPowerSupplyInfo);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -71,7 +46,6 @@ const powerSupplyInfo = computed(() => lastDiagInfo.value?.cdPowerSupplyInfo);
|
|||||||
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
||||||
<DeviceCommonCard :common-info="commonInfo" />
|
<DeviceCommonCard :common-info="commonInfo" />
|
||||||
<DeviceHardwareCard :cpu-usage="cpuUsage" :mem-usage="memUsage" />
|
<DeviceHardwareCard :cpu-usage="cpuUsage" :mem-usage="memUsage" />
|
||||||
<NvrEnvCard :fan-info="fanInfo" :power-supply-info="powerSupplyInfo" />
|
|
||||||
<NvrDiskCard :disk-health="diskHealth" :disk-array="diskArray" />
|
<NvrDiskCard :disk-health="diskHealth" :disk-array="diskArray" />
|
||||||
<template v-if="isNvrCluster(ndmDevice)">
|
<template v-if="isNvrCluster(ndmDevice)">
|
||||||
<NvrRecordCheckCard :ndm-device="ndmDevice" :station="station" />
|
<NvrRecordCheckCard :ndm-device="ndmDevice" :station="station" />
|
||||||
|
|||||||
@@ -42,9 +42,8 @@ const selected = ref([...historyDiagOptions.value.map((option) => `${option.valu
|
|||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
const icmpLoading = ref<boolean>(false);
|
const icmpLoading = ref<boolean>(false);
|
||||||
const alarmLoading = ref<boolean>(false);
|
const alarmLoading = ref<boolean>(false);
|
||||||
const usageLoading = ref<boolean>(false);
|
|
||||||
const diskLoading = ref<boolean>(false);
|
const diskLoading = ref<boolean>(false);
|
||||||
watch([icmpLoading, alarmLoading, usageLoading, diskLoading], (loadings) => {
|
watch([icmpLoading, alarmLoading, diskLoading], (loadings) => {
|
||||||
loading.value = loadings.some((loading) => loading);
|
loading.value = loadings.some((loading) => loading);
|
||||||
});
|
});
|
||||||
const icmpHistoryQueryFn = ref<() => void>();
|
const icmpHistoryQueryFn = ref<() => void>();
|
||||||
@@ -104,7 +103,7 @@ onMounted(() => {
|
|||||||
:cpu-usage-field="'stCommonInfo.CPU使用率'"
|
:cpu-usage-field="'stCommonInfo.CPU使用率'"
|
||||||
:mem-usage-field="'stCommonInfo.内存使用率'"
|
:mem-usage-field="'stCommonInfo.内存使用率'"
|
||||||
v-model:range="range"
|
v-model:range="range"
|
||||||
v-model:loading="usageLoading"
|
v-model:loading="alarmLoading"
|
||||||
@expose-query-fn="onExposeUsageHistoryQueryFn"
|
@expose-query-fn="onExposeUsageHistoryQueryFn"
|
||||||
/>
|
/>
|
||||||
<NvrDiskHistoryCard v-if="selected.includes('disk')" :ndm-device="ndmDevice" :station="station" v-model:range="range" v-model:loading="diskLoading" @expose-query-fn="onExposeDiskHistoryQueryFn" />
|
<NvrDiskHistoryCard v-if="selected.includes('disk')" :ndm-device="ndmDevice" :station="station" v-model:range="range" v-model:loading="diskLoading" @expose-query-fn="onExposeDiskHistoryQueryFn" />
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { NdmSecurityBoxDiagInfo, NdmSecurityBoxResultVO, Station } from '@/apis';
|
import type { NdmSecurityBoxDiagInfo, NdmSecurityBoxResultVO, Station } from '@/apis';
|
||||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, SecurityBoxCircuitCard, SecurityBoxEnvCard, type DeviceCommonCardProps } from '@/components';
|
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, SecurityBoxCircuitCard, SecurityBoxEnvCard } from '@/components';
|
||||||
import { SECURITY_BOX_VENDOR_LITERALS } from '@/helpers';
|
|
||||||
import destr from 'destr';
|
import destr from 'destr';
|
||||||
import { NFlex } from 'naive-ui';
|
import { NFlex } from 'naive-ui';
|
||||||
import { computed, toRefs } from 'vue';
|
import { computed, toRefs } from 'vue';
|
||||||
@@ -20,54 +19,17 @@ const lastDiagInfo = computed(() => {
|
|||||||
return result as NdmSecurityBoxDiagInfo;
|
return result as NdmSecurityBoxDiagInfo;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 解析安防箱厂商
|
const commonInfo = computed(() => {
|
||||||
const vendor = computed(() => {
|
const { stCommonInfo } = lastDiagInfo.value ?? {};
|
||||||
// 先读取诊断信息中的 stCommonInfo 中的设备厂商
|
|
||||||
if (!!lastDiagInfo.value?.stCommonInfo?.设备厂商) {
|
|
||||||
return lastDiagInfo.value.stCommonInfo.设备厂商.trim().toLocaleLowerCase();
|
|
||||||
}
|
|
||||||
// 如果 stCommonInfo 中没有设备厂商,再读取 ndmDevice 中的 manufacturer
|
|
||||||
if (!!ndmDevice.value.manufacturer) {
|
|
||||||
return ndmDevice.value.manufacturer.trim().toLocaleLowerCase();
|
|
||||||
}
|
|
||||||
// 如果 ndmDevice 中也没有 manufacturer,返回 beidian 作为兜底
|
|
||||||
return SECURITY_BOX_VENDOR_LITERALS.beidian;
|
|
||||||
});
|
|
||||||
|
|
||||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
|
||||||
const { ipAddress } = ndmDevice.value;
|
|
||||||
const { ethInfo, ipInfo, stCommonInfo } = lastDiagInfo.value ?? {};
|
|
||||||
const { 设备ID, 软件版本, 设备厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
|
const { 设备ID, 软件版本, 设备厂商, 设备别名, 设备型号, 硬件版本 } = stCommonInfo ?? {};
|
||||||
|
return {
|
||||||
let operStatus = '-';
|
设备ID: 设备ID ?? '',
|
||||||
if (!!ethInfo?.operStatus) {
|
软件版本: 软件版本 ?? '',
|
||||||
operStatus = ethInfo?.operStatus === '1' ? '正常' : '异常';
|
设备厂商: 设备厂商 ?? '',
|
||||||
}
|
设备别名: 设备别名 ?? '',
|
||||||
|
设备型号: 设备型号 ?? '',
|
||||||
return [
|
硬件版本: 硬件版本 ?? '',
|
||||||
{
|
};
|
||||||
title: '设备型号信息',
|
|
||||||
items: [
|
|
||||||
{ label: '设备ID', value: 设备ID || '-' },
|
|
||||||
{ label: '软件版本', value: 软件版本 || '-' },
|
|
||||||
{ label: '设备厂商', value: 设备厂商 || '-' },
|
|
||||||
{ label: '设备别名', value: 设备别名 || '-' },
|
|
||||||
{ label: '设备型号', value: 设备型号 || '-' },
|
|
||||||
{ label: '硬件版本', value: 硬件版本 || '-' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: '设备网卡信息',
|
|
||||||
items: [
|
|
||||||
{ label: 'IP地址', value: ipAddress || '-' },
|
|
||||||
{ label: '子网掩码', value: ipInfo?.mASK || '-' },
|
|
||||||
{ label: 'MAC地址', value: ethInfo?.macAddress || '-' },
|
|
||||||
{ label: '连接速率', value: ethInfo?.speed || '-' },
|
|
||||||
{ label: 'MTU', value: ethInfo?.mTU || '-' },
|
|
||||||
{ label: '运行状态', value: operStatus },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.CPU使用率);
|
const cpuUsage = computed(() => lastDiagInfo.value?.stCommonInfo?.CPU使用率);
|
||||||
@@ -87,7 +49,7 @@ const circuits = computed(() => lastDiagInfo.value?.info?.at(0)?.circuits);
|
|||||||
<DeviceCommonCard :common-info="commonInfo" />
|
<DeviceCommonCard :common-info="commonInfo" />
|
||||||
<DeviceHardwareCard :cpu-usage="cpuUsage" :mem-usage="memUsage" />
|
<DeviceHardwareCard :cpu-usage="cpuUsage" :mem-usage="memUsage" />
|
||||||
<SecurityBoxEnvCard :fan-speeds="fanSpeeds" :temperature="temperature" :humidity="humidity" :switches="switches" />
|
<SecurityBoxEnvCard :fan-speeds="fanSpeeds" :temperature="temperature" :humidity="humidity" :switches="switches" />
|
||||||
<SecurityBoxCircuitCard :ndm-device="ndmDevice" :station="station" :vendor="vendor" :circuits="circuits" />
|
<SecurityBoxCircuitCard :ndm-device="ndmDevice" :station="station" :circuits="circuits" />
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useMutation } from '@tanstack/vue-query';
|
|||||||
import { isCancel } from 'axios';
|
import { isCancel } from 'axios';
|
||||||
import destr from 'destr';
|
import destr from 'destr';
|
||||||
import { isString } from 'es-toolkit';
|
import { isString } from 'es-toolkit';
|
||||||
import { NButton, NCard, NFlex, NForm, NFormItem, NFormItemGi, NGrid, NInput, NInputNumber, NSwitch, type FormInst, type FormRules } from 'naive-ui';
|
import { NButton, NCard, NFlex, NForm, NFormItem, NFormItemGi, NGrid, NInput, NSwitch, type FormInst, type FormRules } from 'naive-ui';
|
||||||
import { computed, onBeforeUnmount, ref, toRefs, useTemplateRef, watch } from 'vue';
|
import { computed, onBeforeUnmount, ref, toRefs, useTemplateRef, watch } from 'vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -126,15 +126,9 @@ onBeforeUnmount(() => {
|
|||||||
<NFormItem label-placement="left" label="型号">
|
<NFormItem label-placement="left" label="型号">
|
||||||
<NInput v-model:value="localDevice.model" />
|
<NInput v-model:value="localDevice.model" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label-placement="left" label="开关数量">
|
|
||||||
<NInputNumber clearable v-model:value="localDevice.circuitCount" />
|
|
||||||
</NFormItem>
|
|
||||||
<NFormItem label-placement="left" label="团体字符串">
|
<NFormItem label-placement="left" label="团体字符串">
|
||||||
<NInput v-model:value="localDevice.community" />
|
<NInput v-model:value="localDevice.community" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label-placement="left" label="团体字符串(写)">
|
|
||||||
<NInput v-model:value="localDevice.writeCommunity" />
|
|
||||||
</NFormItem>
|
|
||||||
<NFormItem label-placement="left" label="设备描述">
|
<NFormItem label-placement="left" label="设备描述">
|
||||||
<NInput v-model:value="localDevice.description" />
|
<NInput v-model:value="localDevice.description" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type NdmServerDiagInfo, type NdmServerResultVO, type Station } from '@/apis';
|
import { type NdmServerDiagInfo, type NdmServerResultVO, type Station } from '@/apis';
|
||||||
import { DeviceCommonCard, DeviceHardwareCard, DeviceHeaderCard, ServerAlive, ServerHighAvailable, ServerStreamPush, type DeviceCommonCardProps } from '@/components';
|
import { DeviceHardwareCard, DeviceHeaderCard, ServerAlive, ServerHighAvailable, ServerStreamPush } from '@/components';
|
||||||
import destr from 'destr';
|
import destr from 'destr';
|
||||||
import { NFlex } from 'naive-ui';
|
import { NFlex } from 'naive-ui';
|
||||||
import { computed, toRefs } from 'vue';
|
import { computed, toRefs } from 'vue';
|
||||||
@@ -19,30 +19,6 @@ const lastDiagInfo = computed(() => {
|
|||||||
return result as NdmServerDiagInfo;
|
return result as NdmServerDiagInfo;
|
||||||
});
|
});
|
||||||
|
|
||||||
const commonInfo = computed<DeviceCommonCardProps['commonInfo']>(() => {
|
|
||||||
const { ipAddress } = ndmDevice.value;
|
|
||||||
const { ethInfo, ipInfo } = lastDiagInfo.value ?? {};
|
|
||||||
|
|
||||||
let operStatus = '-';
|
|
||||||
if (!!ethInfo?.operStatus) {
|
|
||||||
operStatus = ethInfo?.operStatus === '1' ? '正常' : '异常';
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
title: '设备网卡信息',
|
|
||||||
items: [
|
|
||||||
{ label: 'IP地址', value: ipAddress || '-' },
|
|
||||||
{ label: '子网掩码', value: ipInfo?.mASK || '-' },
|
|
||||||
{ label: 'MAC地址', value: ethInfo?.macAddress || '-' },
|
|
||||||
{ label: '连接速率', value: ethInfo?.speed || '-' },
|
|
||||||
{ label: 'MTU', value: ethInfo?.mTU || '-' },
|
|
||||||
{ label: '运行状态', value: operStatus },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
const cpuUsage = computed(() => lastDiagInfo.value?.commInfo?.CPU使用率);
|
const cpuUsage = computed(() => lastDiagInfo.value?.commInfo?.CPU使用率);
|
||||||
const memUsage = computed(() => lastDiagInfo.value?.commInfo?.内存使用率);
|
const memUsage = computed(() => lastDiagInfo.value?.commInfo?.内存使用率);
|
||||||
const diskUsage = computed(() => lastDiagInfo.value?.commInfo?.磁盘使用率);
|
const diskUsage = computed(() => lastDiagInfo.value?.commInfo?.磁盘使用率);
|
||||||
@@ -53,7 +29,6 @@ const runningTime = computed(() => lastDiagInfo.value?.commInfo?.系统运行时
|
|||||||
<NFlex vertical>
|
<NFlex vertical>
|
||||||
<ServerHighAvailable :ndm-device="ndmDevice" :station="station" />
|
<ServerHighAvailable :ndm-device="ndmDevice" :station="station" />
|
||||||
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
<DeviceHeaderCard :ndm-device="ndmDevice" :station="station" />
|
||||||
<DeviceCommonCard :common-info="commonInfo" />
|
|
||||||
<DeviceHardwareCard running-time-label="服务器运行时间" :cpu-usage="cpuUsage" :mem-usage="memUsage" :disk-usage="diskUsage" :running-time="runningTime" />
|
<DeviceHardwareCard running-time-label="服务器运行时间" :cpu-usage="cpuUsage" :mem-usage="memUsage" :disk-usage="diskUsage" :running-time="runningTime" />
|
||||||
<ServerAlive :ndm-device="ndmDevice" :station="station" />
|
<ServerAlive :ndm-device="ndmDevice" :station="station" />
|
||||||
<ServerStreamPush :ndm-device="ndmDevice" :station="station" />
|
<ServerStreamPush :ndm-device="ndmDevice" :station="station" />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getHighAvailableApi, type NdmServerResultVO, type Station } from '@/api
|
|||||||
import { DEVICE_TYPE_LITERALS, tryGetDeviceType } from '@/enums';
|
import { DEVICE_TYPE_LITERALS, tryGetDeviceType } from '@/enums';
|
||||||
import { useSettingStore } from '@/stores';
|
import { useSettingStore } from '@/stores';
|
||||||
import { useQuery, useQueryClient } from '@tanstack/vue-query';
|
import { useQuery, useQueryClient } from '@tanstack/vue-query';
|
||||||
import { NAlert, NFlex } from 'naive-ui';
|
import { NAlert, NCard, NFlex } from 'naive-ui';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { computed, toRefs, watch } from 'vue';
|
import { computed, toRefs, watch } from 'vue';
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const createDeviceNodeKey = (stationCode?: Station['code'], device?: NdmDeviceRe
|
|||||||
import { initStationDevices, type NdmDeviceResultVO, type NdmNvrResultVO, type Station } from '@/apis';
|
import { initStationDevices, type NdmDeviceResultVO, type NdmNvrResultVO, type Station } from '@/apis';
|
||||||
import { useDeviceTree, usePermission, type UseDeviceTreeReturn } from '@/composables';
|
import { useDeviceTree, usePermission, type UseDeviceTreeReturn } from '@/composables';
|
||||||
import { DEVICE_TYPE_NAMES, DEVICE_TYPE_LITERALS, tryGetDeviceType, type DeviceType, PERMISSION_TYPE_LITERALS } from '@/enums';
|
import { DEVICE_TYPE_NAMES, DEVICE_TYPE_LITERALS, tryGetDeviceType, type DeviceType, PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||||
import { createNvrClusterRelationship, isNvrCluster, nvrInCluster } from '@/helpers';
|
import { isNvrCluster } from '@/helpers';
|
||||||
import { useDeviceStore, usePermissionStore } from '@/stores';
|
import { useDeviceStore, usePermissionStore } from '@/stores';
|
||||||
import { watchDebounced, watchImmediate } from '@vueuse/core';
|
import { watchDebounced, watchImmediate } from '@vueuse/core';
|
||||||
import destr from 'destr';
|
import destr from 'destr';
|
||||||
@@ -297,18 +297,8 @@ const renderDeviceNodePrefix = (device: NdmDeviceResultVO, stationCode: Station[
|
|||||||
return h(NFlex, { size: 'small' }, { default: () => [renderViewDeviceButton(device, stationCode), renderDeviceStatusTag(device)] });
|
return h(NFlex, { size: 'small' }, { default: () => [renderViewDeviceButton(device, stationCode), renderDeviceStatusTag(device)] });
|
||||||
};
|
};
|
||||||
// 全线设备树
|
// 全线设备树
|
||||||
const lineDeviceTreeData = computed<Record<DeviceType, TreeOption[]>>(() => {
|
const lineDeviceTreeData = computed<Record<Station['code'], TreeOption[]>>(() => {
|
||||||
const treeData: Record<DeviceType, TreeOption[]> = {
|
const treeData: Record<string, TreeOption[]> = {};
|
||||||
[DEVICE_TYPE_LITERALS.ndmCamera]: [],
|
|
||||||
[DEVICE_TYPE_LITERALS.ndmNvr]: [],
|
|
||||||
[DEVICE_TYPE_LITERALS.ndmSwitch]: [],
|
|
||||||
[DEVICE_TYPE_LITERALS.ndmDecoder]: [],
|
|
||||||
[DEVICE_TYPE_LITERALS.ndmSecurityBox]: [],
|
|
||||||
[DEVICE_TYPE_LITERALS.ndmMediaServer]: [],
|
|
||||||
[DEVICE_TYPE_LITERALS.ndmVideoServer]: [],
|
|
||||||
[DEVICE_TYPE_LITERALS.ndmKeyboard]: [],
|
|
||||||
[DEVICE_TYPE_LITERALS.ndmAlarmHost]: [],
|
|
||||||
};
|
|
||||||
deviceTabPanes.forEach(({ name: paneName /* , tab: paneTab */ }) => {
|
deviceTabPanes.forEach(({ name: paneName /* , tab: paneTab */ }) => {
|
||||||
treeData[paneName] = stations.value.map<TreeOption>((station) => {
|
treeData[paneName] = stations.value.map<TreeOption>((station) => {
|
||||||
const { name: stationName, code: stationCode } = station;
|
const { name: stationName, code: stationCode } = station;
|
||||||
@@ -317,39 +307,28 @@ const lineDeviceTreeData = computed<Record<DeviceType, TreeOption[]>>(() => {
|
|||||||
const offlineDevices = devices?.filter((device) => device.deviceStatus === '20');
|
const offlineDevices = devices?.filter((device) => device.deviceStatus === '20');
|
||||||
// 对于录像机,需要根据clusterList字段以分号分隔设备IP,进一步形成子树结构
|
// 对于录像机,需要根据clusterList字段以分号分隔设备IP,进一步形成子树结构
|
||||||
if (paneName === DEVICE_TYPE_LITERALS.ndmNvr) {
|
if (paneName === DEVICE_TYPE_LITERALS.ndmNvr) {
|
||||||
const nvrDevices = devices as NdmNvrResultVO[];
|
const nvrs = devices as NdmNvrResultVO[];
|
||||||
|
const nvrClusters: NdmNvrResultVO[] = [];
|
||||||
const { nvrClusters, nvrTreeMap, nvrStandalones } = createNvrClusterRelationship(nvrDevices);
|
const nvrSingletons: NdmNvrResultVO[] = [];
|
||||||
|
for (const device of nvrs) {
|
||||||
|
if (isNvrCluster(device)) {
|
||||||
|
nvrClusters.push(device);
|
||||||
|
} else {
|
||||||
|
nvrSingletons.push(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
label: stationName,
|
label: stationName,
|
||||||
key: stationCode,
|
key: stationCode,
|
||||||
prefix: () => renderStationNodePrefix(station),
|
prefix: () => renderStationNodePrefix(station),
|
||||||
suffix: () => renderIcmpStatistics(onlineDevices?.length ?? 0, offlineDevices?.length ?? 0, devices?.length ?? 0),
|
suffix: () => renderIcmpStatistics(onlineDevices?.length ?? 0, offlineDevices?.length ?? 0, devices?.length ?? 0),
|
||||||
children: [
|
children: nvrClusters.map<TreeOption>((cluster) => {
|
||||||
...nvrClusters.map((cluster) => {
|
|
||||||
return {
|
return {
|
||||||
label: `${cluster.name}`,
|
label: `${cluster.name}`,
|
||||||
key: createDeviceNodeKey(stationCode, cluster),
|
key: createDeviceNodeKey(stationCode, cluster),
|
||||||
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
||||||
suffix: () => `${cluster.ipAddress}`,
|
suffix: () => `${cluster.ipAddress}`,
|
||||||
children: (nvrTreeMap.get(cluster.ipAddress ?? '') ?? []).map((clusterNode) => {
|
children: nvrSingletons.map<TreeOption>((device) => {
|
||||||
return {
|
|
||||||
label: `${clusterNode.name}`,
|
|
||||||
key: createDeviceNodeKey(stationCode, clusterNode),
|
|
||||||
prefix: () => renderDeviceNodePrefix(clusterNode, stationCode),
|
|
||||||
suffix: () => `${clusterNode.ipAddress}`,
|
|
||||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
|
||||||
stationCode,
|
|
||||||
device: clusterNode,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
|
||||||
stationCode,
|
|
||||||
device: cluster,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
...nvrStandalones.map((device) => {
|
|
||||||
return {
|
return {
|
||||||
label: `${device.name}`,
|
label: `${device.name}`,
|
||||||
key: createDeviceNodeKey(stationCode, device),
|
key: createDeviceNodeKey(stationCode, device),
|
||||||
@@ -360,12 +339,15 @@ const lineDeviceTreeData = computed<Record<DeviceType, TreeOption[]>>(() => {
|
|||||||
device: device,
|
device: device,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
],
|
// 当选择设备时,能获取到设备的所有信息,以及设备所属的车站
|
||||||
|
stationCode,
|
||||||
|
device: cluster,
|
||||||
|
};
|
||||||
|
}),
|
||||||
stationCode,
|
stationCode,
|
||||||
deviceType: activeTab.value,
|
deviceType: activeTab.value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// 非录像机设备
|
|
||||||
return {
|
return {
|
||||||
label: stationName,
|
label: stationName,
|
||||||
key: stationCode,
|
key: stationCode,
|
||||||
@@ -400,36 +382,20 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
|
|||||||
const onlineCount = stationDevices[deviceType].filter((device) => device.deviceStatus === '10').length;
|
const onlineCount = stationDevices[deviceType].filter((device) => device.deviceStatus === '10').length;
|
||||||
const offlineCount = stationDevices[deviceType].filter((device) => device.deviceStatus === '20').length;
|
const offlineCount = stationDevices[deviceType].filter((device) => device.deviceStatus === '20').length;
|
||||||
if (deviceType === DEVICE_TYPE_LITERALS.ndmNvr) {
|
if (deviceType === DEVICE_TYPE_LITERALS.ndmNvr) {
|
||||||
const nvrDevices = stationDevices[deviceType] as NdmNvrResultVO[];
|
const nvrs = stationDevices[deviceType] as NdmNvrResultVO[];
|
||||||
|
const clusters = nvrs.filter((nvr) => isNvrCluster(nvr));
|
||||||
const { nvrClusters, nvrTreeMap, nvrStandalones } = createNvrClusterRelationship(nvrDevices);
|
const singletons = nvrs.filter((nvr) => !isNvrCluster(nvr));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: `${DEVICE_TYPE_NAMES[deviceType]}`,
|
label: `${DEVICE_TYPE_NAMES[deviceType]}`,
|
||||||
key: deviceType,
|
key: deviceType,
|
||||||
suffix: () => renderIcmpStatistics(onlineCount, offlineCount, nvrDevices.length),
|
suffix: () => renderIcmpStatistics(onlineCount, offlineCount, nvrs.length),
|
||||||
children: [
|
children: clusters.map<TreeOption>((cluster) => {
|
||||||
...nvrClusters.map((cluster) => {
|
|
||||||
return {
|
return {
|
||||||
label: `${cluster.name}`,
|
label: `${cluster.name}`,
|
||||||
key: createDeviceNodeKey(stationCode, cluster),
|
key: createDeviceNodeKey(stationCode, cluster),
|
||||||
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
prefix: () => renderDeviceNodePrefix(cluster, stationCode),
|
||||||
suffix: () => `${cluster.ipAddress}`,
|
suffix: () => `${cluster.ipAddress}`,
|
||||||
children: (nvrTreeMap.get(cluster.ipAddress ?? '') ?? []).map((clusterNode) => {
|
children: singletons.map<TreeOption>((device) => {
|
||||||
return {
|
|
||||||
label: `${clusterNode.name}`,
|
|
||||||
key: createDeviceNodeKey(stationCode, clusterNode),
|
|
||||||
prefix: () => renderDeviceNodePrefix(clusterNode, stationCode),
|
|
||||||
suffix: () => `${clusterNode.ipAddress}`,
|
|
||||||
stationCode,
|
|
||||||
device: clusterNode,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
stationCode,
|
|
||||||
device: cluster,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
...nvrStandalones.map((device) => {
|
|
||||||
return {
|
return {
|
||||||
label: `${device.name}`,
|
label: `${device.name}`,
|
||||||
key: createDeviceNodeKey(stationCode, device),
|
key: createDeviceNodeKey(stationCode, device),
|
||||||
@@ -439,12 +405,14 @@ const stationDeviceTreeData = computed<TreeOption[]>(() => {
|
|||||||
device,
|
device,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
],
|
stationCode,
|
||||||
|
device: cluster,
|
||||||
|
};
|
||||||
|
}),
|
||||||
stationCode,
|
stationCode,
|
||||||
deviceType,
|
deviceType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// 非录像机设备
|
|
||||||
return {
|
return {
|
||||||
label: `${DEVICE_TYPE_NAMES[deviceType]}`,
|
label: `${DEVICE_TYPE_NAMES[deviceType]}`,
|
||||||
key: deviceType,
|
key: deviceType,
|
||||||
@@ -520,13 +488,9 @@ const onLocateDeviceTree = async () => {
|
|||||||
if (stationDevices) {
|
if (stationDevices) {
|
||||||
const selectedNvr = selectedDevice.value as NdmNvrResultVO;
|
const selectedNvr = selectedDevice.value as NdmNvrResultVO;
|
||||||
if (!isNvrCluster(selectedNvr)) {
|
if (!isNvrCluster(selectedNvr)) {
|
||||||
const nvrDevices = stationDevices[DEVICE_TYPE_LITERALS.ndmNvr];
|
const nvrs = stationDevices[DEVICE_TYPE_LITERALS.ndmNvr];
|
||||||
const clusters = nvrDevices.filter((device) => {
|
const clusters = nvrs.filter((nvr) => isNvrCluster(nvr) && nvr.clusterList?.includes(selectedNvr.clusterList ?? ''));
|
||||||
if (!isNvrCluster(device)) return false;
|
expandedKeys.value.push(...clusters.map((nvr) => createDeviceNodeKey(stationCode, nvr)));
|
||||||
const cluster = device;
|
|
||||||
return nvrInCluster(selectedNvr, cluster);
|
|
||||||
});
|
|
||||||
expandedKeys.value.push(...clusters.map((cluster) => createDeviceNodeKey(stationCode, cluster)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,27 +13,9 @@ import destr from 'destr';
|
|||||||
import { isFunction } from 'es-toolkit';
|
import { isFunction } from 'es-toolkit';
|
||||||
import localforage from 'localforage';
|
import localforage from 'localforage';
|
||||||
import { DownloadIcon, Trash2Icon, UploadIcon } from 'lucide-vue-next';
|
import { DownloadIcon, Trash2Icon, UploadIcon } from 'lucide-vue-next';
|
||||||
import {
|
import { NButton, NButtonGroup, NDivider, NDrawer, NDrawerContent, NDropdown, NFlex, NFormItem, NIcon, NInput, NInputNumber, NModal, NSwitch, NText, NTooltip, type DropdownOption } from 'naive-ui';
|
||||||
NButton,
|
|
||||||
NButtonGroup,
|
|
||||||
NDivider,
|
|
||||||
NDrawer,
|
|
||||||
NDrawerContent,
|
|
||||||
NDropdown,
|
|
||||||
NFlex,
|
|
||||||
NFormItem,
|
|
||||||
NIcon,
|
|
||||||
NInput,
|
|
||||||
NInputNumber,
|
|
||||||
NModal,
|
|
||||||
NSwitch,
|
|
||||||
NText,
|
|
||||||
NTooltip,
|
|
||||||
type DropdownOption,
|
|
||||||
type InputInst,
|
|
||||||
} from 'naive-ui';
|
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { computed, nextTick, ref, useTemplateRef, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -167,14 +149,8 @@ useEventListener('keydown', (event) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const expectToShowDebugCodeInput = ref(false);
|
const expectToShowDebugCodeInput = ref(false);
|
||||||
const debugCodeInputRef = useTemplateRef<InputInst>('debug-code-input-ref');
|
|
||||||
const onModalAfterEnter = () => {
|
const onModalAfterEnter = () => {
|
||||||
expectToShowDebugCodeInput.value = !debugMode.value;
|
expectToShowDebugCodeInput.value = !debugMode.value;
|
||||||
if (expectToShowDebugCodeInput.value) {
|
|
||||||
nextTick(() => {
|
|
||||||
debugCodeInputRef.value?.focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
const onModalAfterLeave = () => {
|
const onModalAfterLeave = () => {
|
||||||
expectToShowDebugCodeInput.value = false;
|
expectToShowDebugCodeInput.value = false;
|
||||||
@@ -436,7 +412,7 @@ const onClickVersion = () => {
|
|||||||
<NText v-else>确认关闭调试模式</NText>
|
<NText v-else>确认关闭调试模式</NText>
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<NInput ref="debug-code-input-ref" v-if="expectToShowDebugCodeInput" v-model:value="debugCode" placeholder="输入调试码" @keyup.enter="enableDebugMode" />
|
<NInput v-if="expectToShowDebugCodeInput" v-model:value="debugCode" placeholder="输入调试码" @keyup.enter="enableDebugMode" />
|
||||||
</template>
|
</template>
|
||||||
<template #action>
|
<template #action>
|
||||||
<NButton @click="showDebugCodeModal = false">取消</NButton>
|
<NButton @click="showDebugCodeModal = false">取消</NButton>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// 设备参数配置在系统中的key前缀
|
// 设备参数配置在系统中的key前缀
|
||||||
const DEVICE_PARAM_PREFIXES = {
|
const DEVICE_PARAM_PREFIXES = {
|
||||||
Camera: 'CAMERA_',
|
|
||||||
Switch: 'SWITCH_',
|
Switch: 'SWITCH_',
|
||||||
Server: 'SERVER_',
|
Server: 'SERVER_',
|
||||||
Decoder: 'DECODER_',
|
Decoder: 'DECODER_',
|
||||||
@@ -68,10 +67,6 @@ const getItemSuffix = (name: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const tabPanes = [
|
const tabPanes = [
|
||||||
{
|
|
||||||
tab: '摄像机阈值',
|
|
||||||
name: DEVICE_PARAM_PREFIXES.Camera,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
tab: '交换机阈值',
|
tab: '交换机阈值',
|
||||||
name: DEVICE_PARAM_PREFIXES.Switch,
|
name: DEVICE_PARAM_PREFIXES.Switch,
|
||||||
@@ -114,7 +109,7 @@ const show = defineModel<boolean>('show', { required: true });
|
|||||||
|
|
||||||
const { station } = toRefs(props);
|
const { station } = toRefs(props);
|
||||||
|
|
||||||
const activeTabName = ref<DeviceParamPrefix>(DEVICE_PARAM_PREFIXES.Camera);
|
const activeTabName = ref<DeviceParamPrefix>(DEVICE_PARAM_PREFIXES.Switch);
|
||||||
|
|
||||||
const deviceParams = ref<DeviceParamItem[]>([]);
|
const deviceParams = ref<DeviceParamItem[]>([]);
|
||||||
|
|
||||||
@@ -216,7 +211,7 @@ const onAfterModalEnter = () => {
|
|||||||
|
|
||||||
const onBeforeModalLeave = () => {
|
const onBeforeModalLeave = () => {
|
||||||
saveDeviceParams({ tabName: activeTabName.value, items: deviceParams.value });
|
saveDeviceParams({ tabName: activeTabName.value, items: deviceParams.value });
|
||||||
activeTabName.value = DEVICE_PARAM_PREFIXES.Camera;
|
activeTabName.value = DEVICE_PARAM_PREFIXES.Switch;
|
||||||
deviceParams.value = [];
|
deviceParams.value = [];
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from './nvr';
|
|
||||||
export * from './security-box';
|
|
||||||
export * from './switch';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './util';
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import type { NdmNvrResultVO } from '@/apis';
|
|
||||||
|
|
||||||
// 解析 clusterList 字段
|
|
||||||
export const parseIpListFromClusterList = (nvr: NdmNvrResultVO) => {
|
|
||||||
const ipList = (nvr.clusterList ?? '').split(';');
|
|
||||||
return ipList.map((ip) => ip.trim()).filter((ip) => !!ip);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isNvrCluster = (maybeNvrCluster: NdmNvrResultVO) => {
|
|
||||||
const { ipAddress } = maybeNvrCluster;
|
|
||||||
const ipList = parseIpListFromClusterList(maybeNvrCluster);
|
|
||||||
if (ipList.length === 0) return false;
|
|
||||||
if (ipList.length === 1 && ipList.at(0) === ipAddress) return false;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const nvrInCluster = (nvr: NdmNvrResultVO, cluster: NdmNvrResultVO) => {
|
|
||||||
const { ipAddress } = nvr;
|
|
||||||
if (!ipAddress) return false;
|
|
||||||
const ipList = parseIpListFromClusterList(cluster);
|
|
||||||
return ipList.includes(ipAddress);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createNvrClusterRelationship = (nvrDevices: NdmNvrResultVO[]) => {
|
|
||||||
const nvrClusters = nvrDevices.filter((nvr) => isNvrCluster(nvr));
|
|
||||||
const nvrNotClusters = nvrDevices.filter((nvr) => !isNvrCluster(nvr));
|
|
||||||
const nodedNvrIpAddressSet = new Set<string | null>();
|
|
||||||
const nvrStandalones: NdmNvrResultVO[] = [];
|
|
||||||
const nvrTreeMap = new Map<string, NdmNvrResultVO[]>();
|
|
||||||
// 遍历所有非集群录像机,将它们分配到对应的录像机集群中
|
|
||||||
for (const nvr of nvrNotClusters) {
|
|
||||||
for (const cluster of nvrClusters) {
|
|
||||||
if (nvrInCluster(nvr, cluster)) {
|
|
||||||
if (!!cluster.ipAddress) {
|
|
||||||
// 写入录像机与集群的关系
|
|
||||||
nvrTreeMap.set(cluster.ipAddress, [...(nvrTreeMap.get(cluster.ipAddress) ?? []), nvr]);
|
|
||||||
// 记录已分配的录像机IP地址
|
|
||||||
nodedNvrIpAddressSet.add(nvr.ipAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 分配完成后,过滤出未分配的录像机,形成录像机单机列表
|
|
||||||
nvrNotClusters.forEach((device) => {
|
|
||||||
if (!nodedNvrIpAddressSet.has(device.ipAddress)) {
|
|
||||||
nvrStandalones.push(device);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
nvrClusters,
|
|
||||||
// nvrNotClusters,
|
|
||||||
nvrTreeMap,
|
|
||||||
nvrStandalones,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import { rebootSecurityBoxBeidianApi, rebootSecurityBoxNingTechApi, turnCircuitStatusBeidianApi, turnCircuitStatusNingTechApi, type NdmSecurityBoxResultVO } from '@/apis';
|
|
||||||
import { objectEntries } from '@vueuse/core';
|
|
||||||
|
|
||||||
const UNSUPPORTED_SECURITY_BOX_VENDOR = '不支持的安防箱厂商';
|
|
||||||
|
|
||||||
export const SECURITY_BOX_VENDOR_LITERALS = {
|
|
||||||
beidian: 'beidian',
|
|
||||||
ningtech: 'ningtech',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export type SecurityBoxVendor = keyof typeof SECURITY_BOX_VENDOR_LITERALS;
|
|
||||||
|
|
||||||
export const resolveSecurityBoxVendor = (vendor?: string) => {
|
|
||||||
const entry = objectEntries(SECURITY_BOX_VENDOR_LITERALS).find(([, value]) => value === vendor);
|
|
||||||
return entry?.at(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const normalizeSecurityBoxWriteCommunity = (ndmDevice: NdmSecurityBoxResultVO, vendor?: string) => {
|
|
||||||
const resolved = resolveSecurityBoxVendor(vendor);
|
|
||||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.beidian) {
|
|
||||||
const community = ndmDevice.community;
|
|
||||||
if (!community) throw new Error('团体字符串不存在');
|
|
||||||
return community;
|
|
||||||
}
|
|
||||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.ningtech) {
|
|
||||||
const community = ndmDevice.writeCommunity;
|
|
||||||
if (!community) throw new Error('团体字符串(写)不存在');
|
|
||||||
return community;
|
|
||||||
}
|
|
||||||
throw new Error(UNSUPPORTED_SECURITY_BOX_VENDOR);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const normalizeSecurityBoxCircuitIndex = (index: number, vendor?: string) => {
|
|
||||||
const resolved = resolveSecurityBoxVendor(vendor);
|
|
||||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.beidian) {
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.ningtech) {
|
|
||||||
return index + 1;
|
|
||||||
}
|
|
||||||
throw new Error(UNSUPPORTED_SECURITY_BOX_VENDOR);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const dispatchTurnCircuitStatusApi = (vendor?: string) => {
|
|
||||||
const resolved = resolveSecurityBoxVendor(vendor);
|
|
||||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.beidian) {
|
|
||||||
return turnCircuitStatusBeidianApi;
|
|
||||||
}
|
|
||||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.ningtech) {
|
|
||||||
return turnCircuitStatusNingTechApi;
|
|
||||||
}
|
|
||||||
throw new Error(UNSUPPORTED_SECURITY_BOX_VENDOR);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const dispatchRebootSecurityBoxApi = (vendor?: string) => {
|
|
||||||
const resolved = resolveSecurityBoxVendor(vendor);
|
|
||||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.beidian) {
|
|
||||||
return rebootSecurityBoxBeidianApi;
|
|
||||||
}
|
|
||||||
if (resolved === SECURITY_BOX_VENDOR_LITERALS.ningtech) {
|
|
||||||
return rebootSecurityBoxNingTechApi;
|
|
||||||
}
|
|
||||||
throw new Error(UNSUPPORTED_SECURITY_BOX_VENDOR);
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './adapter';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './util';
|
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './device';
|
export * from './device-alarm';
|
||||||
export * from './log';
|
export * from './nvr-cluster';
|
||||||
|
export * from './switch-port';
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from './render';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './alarm-log';
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import type { NdmNvrResultVO } from '@/apis';
|
||||||
|
|
||||||
|
export const isNvrCluster = (maybeNvrCluster: NdmNvrResultVO) => {
|
||||||
|
const { ipAddress, clusterList } = maybeNvrCluster;
|
||||||
|
if (!clusterList?.trim()) return false;
|
||||||
|
if (clusterList === ipAddress) return false;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
@@ -4,7 +4,7 @@ import { useLineStationsQuery, useStompClient, useUserPermissionQuery, useVerify
|
|||||||
import { LINE_ALARMS_QUERY_KEY, LINE_DEVICES_QUERY_KEY, LINE_STATIONS_MUTATION_KEY, LINE_STATIONS_QUERY_KEY, STATION_ALARMS_MUTATION_KEY, STATION_DEVICES_MUTATION_KEY } from '@/constants';
|
import { LINE_ALARMS_QUERY_KEY, LINE_DEVICES_QUERY_KEY, LINE_STATIONS_MUTATION_KEY, LINE_STATIONS_QUERY_KEY, STATION_ALARMS_MUTATION_KEY, STATION_DEVICES_MUTATION_KEY } from '@/constants';
|
||||||
import { useSettingStore, useUnreadStore, useUserStore } from '@/stores';
|
import { useSettingStore, useUnreadStore, useUserStore } from '@/stores';
|
||||||
import { useIsFetching, useIsMutating } from '@tanstack/vue-query';
|
import { useIsFetching, useIsMutating } from '@tanstack/vue-query';
|
||||||
import { ChevronDownIcon, ChevronsLeftIcon, ChevronsRightIcon, ComputerIcon, KeyRoundIcon, LogOutIcon, LogsIcon, MapPinIcon, MonitorPlayIcon, SettingsIcon, SirenIcon } from 'lucide-vue-next';
|
import { ChevronDownIcon, ChevronsLeftIcon, ChevronsRightIcon, ComputerIcon, KeyRoundIcon, LogOutIcon, LogsIcon, MapPinIcon, SettingsIcon, SirenIcon } from 'lucide-vue-next';
|
||||||
import {
|
import {
|
||||||
NBadge,
|
NBadge,
|
||||||
NButton,
|
NButton,
|
||||||
@@ -111,11 +111,6 @@ const menuOptions = computed<MenuOption[]>(() => [
|
|||||||
show: isLamp.value,
|
show: isLamp.value,
|
||||||
icon: renderIcon(KeyRoundIcon),
|
icon: renderIcon(KeyRoundIcon),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: () => h(RouterLink, { to: '/vimp' }, { default: () => '视频综合管理平台' }),
|
|
||||||
key: '/vimp',
|
|
||||||
icon: renderIcon(MonitorPlayIcon),
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const dropdownOptions: DropdownOption[] = [
|
const dropdownOptions: DropdownOption[] = [
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from './vimp-client';
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
import type { AxiosError, AxiosRequestConfig, AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
|
|
||||||
import axios, { isAxiosError } from 'axios';
|
|
||||||
import type { VimpResponse, VimpResult } from '../../types';
|
|
||||||
import { useUserStore } from '@/stores';
|
|
||||||
import { getAppEnvConfig } from '@/utils';
|
|
||||||
import router from '@/router';
|
|
||||||
|
|
||||||
export interface VimpRequestOptions extends CreateAxiosDefaults {
|
|
||||||
requestInterceptor?: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
|
|
||||||
responseInterceptor?: (resp: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;
|
|
||||||
responseErrorInterceptor?: (error: any) => any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createVimpClient = (config?: VimpRequestOptions) => {
|
|
||||||
const defaultRequestInterceptor = (config: InternalAxiosRequestConfig) => config;
|
|
||||||
const defaultResponseInterceptor = (response: AxiosResponse) => response;
|
|
||||||
const defaultResponseErrorInterceptor = (error: any) => {
|
|
||||||
if (isAxiosError(error)) {
|
|
||||||
if (error.status === 401) {
|
|
||||||
// 处理 401 错误
|
|
||||||
}
|
|
||||||
if (error.status === 404) {
|
|
||||||
// 处理 404 错误
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Promise.reject(error);
|
|
||||||
};
|
|
||||||
|
|
||||||
const requestInterceptor = config?.requestInterceptor ?? defaultRequestInterceptor;
|
|
||||||
const responseInterceptor = config?.responseInterceptor ?? defaultResponseInterceptor;
|
|
||||||
const responseErrorInterceptor = config?.responseErrorInterceptor ?? defaultResponseErrorInterceptor;
|
|
||||||
|
|
||||||
const instance = axios.create(config);
|
|
||||||
instance.interceptors.request.use(requestInterceptor);
|
|
||||||
instance.interceptors.response.use(responseInterceptor, responseErrorInterceptor);
|
|
||||||
|
|
||||||
const vimpGet = <T>(url: string, options?: AxiosRequestConfig & { retRaw?: boolean }): Promise<VimpResponse<T>> => {
|
|
||||||
const { retRaw, ...reqConfig } = options ?? {};
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
instance
|
|
||||||
.get(url, {
|
|
||||||
...reqConfig,
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (retRaw) {
|
|
||||||
resolve([null, res.data as T, null]);
|
|
||||||
} else {
|
|
||||||
const resData = res.data as VimpResult<T>;
|
|
||||||
resolve([null, resData.data, resData]);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
resolve([err as AxiosError, null, null]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const vimpPost = <T>(url: string, data?: AxiosRequestConfig['data'], options?: Partial<Omit<AxiosRequestConfig, 'data'>> & { retRaw?: boolean; upload?: boolean }): Promise<VimpResponse<T>> => {
|
|
||||||
const { retRaw, upload, ...reqConfig } = options ?? {};
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
instance
|
|
||||||
.post(url, data, { headers: { 'content-type': upload ? 'multipart/form-data' : 'application/json' }, ...reqConfig })
|
|
||||||
.then((res) => {
|
|
||||||
const resData = res.data;
|
|
||||||
if (retRaw) {
|
|
||||||
resolve([null, resData as T, null]);
|
|
||||||
} else {
|
|
||||||
resolve([null, resData.data as T, resData as VimpResult<T>]);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
resolve([err as AxiosError, null, null]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const httpPut = <T>(url: string, data?: AxiosRequestConfig['data'], options?: Partial<Omit<AxiosRequestConfig, 'data'>>): Promise<VimpResponse<T>> => {
|
|
||||||
const reqConfig = options ?? {};
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
instance
|
|
||||||
.put<VimpResult<T>>(url, data, { ...reqConfig })
|
|
||||||
.then((res) => {
|
|
||||||
resolve([null, res.data.data, res.data]);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
resolve([err as AxiosError, null, null]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const httpDelete = <T>(url: string, idList: string[], options?: Partial<Omit<AxiosRequestConfig, 'data'>>): Promise<VimpResponse<T>> => {
|
|
||||||
const reqConfig = options ?? {};
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
instance
|
|
||||||
.delete<VimpResult<T>>(url, { ...reqConfig, data: idList })
|
|
||||||
.then((res) => {
|
|
||||||
resolve([null, res.data.data, res.data]);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
resolve([err as AxiosError, null, null]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
instance,
|
|
||||||
get: vimpGet,
|
|
||||||
post: vimpPost,
|
|
||||||
put: httpPut,
|
|
||||||
delete: httpDelete,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const unwrapVimpResponse = <T>(resp: VimpResponse<T>) => {
|
|
||||||
const [err, data, result] = resp;
|
|
||||||
if (err) throw err;
|
|
||||||
if (result) {
|
|
||||||
const { code, msg } = result;
|
|
||||||
if (code !== 0 && code !== 200) throw new Error(`${msg || '请求失败'}`);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 从错误中解析出错误信息
|
|
||||||
export const parseVimpErrorFeedback = (error: Error) => {
|
|
||||||
// 当发生 http 错误时,unwrapVimpResponse 会直接抛出错误,
|
|
||||||
// 所以如果不是 AxiosError,说明是业务错误,直接返回错误信息。
|
|
||||||
if (!isAxiosError(error)) {
|
|
||||||
return error.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是 AxiosError,说明是 http 错误,尝试获取 result,
|
|
||||||
const result = error.response?.data;
|
|
||||||
|
|
||||||
// 如果 result 不存在,返回 error 中原生的 code + message
|
|
||||||
if (!result) {
|
|
||||||
const { code, message } = error;
|
|
||||||
return `${code ? `code: ${code}, ` : ''}${message ? `message: ${message}` : ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果 result 存在,判断是否是字符串,
|
|
||||||
// 如果是字符串,说明是一些奇怪的、直接返回的错误信息,直接返回;
|
|
||||||
// 如果不是字符串,说明是 Result 类型,返回其中的 msg。
|
|
||||||
if (typeof result === 'string') {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
const { msg } = result as VimpResult;
|
|
||||||
return `${msg || '请求失败'}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const vimpClient = createVimpClient({
|
|
||||||
baseURL: `/vimp/api`,
|
|
||||||
requestInterceptor: (config) => {
|
|
||||||
const userStore = useUserStore();
|
|
||||||
const { lampAuthorization, lampClientId, lampClientSecret } = getAppEnvConfig();
|
|
||||||
const newAuthorization = window.btoa(`${lampClientId}:${lampClientSecret}`);
|
|
||||||
const authorization = lampAuthorization.trim() !== '' ? lampAuthorization : newAuthorization;
|
|
||||||
config.headers.set('accept-language', 'zh-CN,zh;q=0.9');
|
|
||||||
config.headers.set('accept', 'application/json, text/plain, */*');
|
|
||||||
config.headers.set('Applicationid', '');
|
|
||||||
config.headers.set('Tenantid', '1');
|
|
||||||
config.headers.set('Authorization', authorization);
|
|
||||||
config.headers.set('token', userStore.userLoginResult?.token ?? '');
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
responseInterceptor: (response) => {
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
responseErrorInterceptor: (error) => {
|
|
||||||
const err = error as AxiosError;
|
|
||||||
if (err.response?.status === 401) {
|
|
||||||
window.$message.error('登录超时,请重新登录');
|
|
||||||
const userStore = useUserStore();
|
|
||||||
userStore.resetStore();
|
|
||||||
router.push({ path: '/login' });
|
|
||||||
}
|
|
||||||
return Promise.reject(error);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export * from './client';
|
|
||||||
export * from './model';
|
|
||||||
export * from './request';
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './vimp-channel';
|
|
||||||
export * from './vimp-site';
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
export interface VimpChannel {
|
|
||||||
address: string;
|
|
||||||
block: string;
|
|
||||||
civilCode: string;
|
|
||||||
code: string;
|
|
||||||
latitude: number;
|
|
||||||
longitude: number;
|
|
||||||
manufacture: string;
|
|
||||||
model: string;
|
|
||||||
name: string;
|
|
||||||
owner: string;
|
|
||||||
parentId: string;
|
|
||||||
parental: number;
|
|
||||||
status: number;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
export interface VimpRawSite {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
online?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VimpSite {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
online: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const normalizeVimpSite = (site: VimpRawSite): VimpSite => ({
|
|
||||||
code: site.code,
|
|
||||||
name: site.name,
|
|
||||||
online: site.online ?? true,
|
|
||||||
});
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { unwrapVimpResponse, vimpClient } from '../client';
|
|
||||||
import { normalizeVimpSite, type VimpRawSite } from '../model';
|
|
||||||
|
|
||||||
export const clientCatalogAllDeviceApi = async (options?: { signal?: AbortSignal }) => {
|
|
||||||
const { signal } = options ?? {};
|
|
||||||
const client = vimpClient;
|
|
||||||
const endpoint = `/client/catalog/allDevice`;
|
|
||||||
const resp = await client.post<VimpRawSite[]>(endpoint, {}, { signal });
|
|
||||||
const data = unwrapVimpResponse(resp);
|
|
||||||
return data?.map(normalizeVimpSite) ?? null;
|
|
||||||
};
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { unwrapVimpResponse, vimpClient } from '../client';
|
|
||||||
import type { VimpChannel } from '../model';
|
|
||||||
|
|
||||||
export const clientCatalogChannelApi = async (code: string, options?: { signal?: AbortSignal }) => {
|
|
||||||
const { signal } = options ?? {};
|
|
||||||
const client = vimpClient;
|
|
||||||
const endpoint = `/client/catalog/channel`;
|
|
||||||
const resp = await client.post<VimpChannel[]>(endpoint, { code, time: '' }, { signal });
|
|
||||||
const data = unwrapVimpResponse(resp);
|
|
||||||
return data;
|
|
||||||
};
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './client.catalog.channel';
|
|
||||||
export * from './client.catalog.all-device';
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { NEmpty, NIcon, NTabPane, NTabs, NTree, type TreeOverrideNodeClickBehavior, type TreeProps } from 'naive-ui';
|
|
||||||
import { computed, h, type CSSProperties } from 'vue';
|
|
||||||
import MarqueeText from './marquee-text.vue';
|
|
||||||
import { useAlarmStore, useResourcePanelStore } from '../stores';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import { isAlarmNode, isAlarmSiteNode, isAlarmAreaNode } from '../types';
|
|
||||||
import { SirenIcon } from 'lucide-vue-next';
|
|
||||||
import { useIsFetching } from '@tanstack/vue-query';
|
|
||||||
import { VIMP_CHANNELS_QUERY_KEY, LINE_TAB_CONTENT_WIDTH } from '../constants';
|
|
||||||
|
|
||||||
const alarmStore = useAlarmStore();
|
|
||||||
const { lineTabPanes } = storeToRefs(alarmStore);
|
|
||||||
|
|
||||||
// 不要直接从 query 中获取 isLoading状态,因为 query 执行完毕并不代表 store 也同步完毕
|
|
||||||
const isFetching = useIsFetching({ queryKey: [VIMP_CHANNELS_QUERY_KEY] });
|
|
||||||
const isLoading = computed(() => isFetching.value > 0 && !lineTabPanes.value);
|
|
||||||
|
|
||||||
const overrideNodeClickBehavior: TreeOverrideNodeClickBehavior = ({ option }) => {
|
|
||||||
const hasChildren = (option.children?.length ?? 0) > 0;
|
|
||||||
if (hasChildren) {
|
|
||||||
return 'toggleExpand';
|
|
||||||
} else {
|
|
||||||
return 'none';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderNodeLabel: TreeProps['renderLabel'] = ({ option }) => {
|
|
||||||
// 是车站节点
|
|
||||||
if (isAlarmSiteNode(option)) {
|
|
||||||
const siteOnline = option.online;
|
|
||||||
const siteNodeStyle: CSSProperties = {
|
|
||||||
opacity: siteOnline ? 1 : 0.5,
|
|
||||||
};
|
|
||||||
return h('div', { style: siteNodeStyle }, option.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是中间节点(一级/二级区域)
|
|
||||||
if (isAlarmAreaNode(option)) {
|
|
||||||
const site = option.site;
|
|
||||||
const nodeStyle: CSSProperties = {
|
|
||||||
opacity: site.online ? 1 : 0.5,
|
|
||||||
};
|
|
||||||
return h('div', { style: nodeStyle }, option.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是警报器节点
|
|
||||||
if (isAlarmNode(option)) {
|
|
||||||
const alarm = option.alarm;
|
|
||||||
const site = option.site;
|
|
||||||
|
|
||||||
const alarmOnline = () => {
|
|
||||||
return alarm.status === 1 && site.online;
|
|
||||||
};
|
|
||||||
|
|
||||||
const alarmNodeStyle: CSSProperties = {
|
|
||||||
opacity: alarmOnline() ? 1 : 0.5,
|
|
||||||
cursor: alarmOnline() ? 'pointer' : 'not-allowed',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '6px',
|
|
||||||
};
|
|
||||||
return h(
|
|
||||||
'div',
|
|
||||||
{
|
|
||||||
style: alarmNodeStyle,
|
|
||||||
draggable: alarmOnline(),
|
|
||||||
onDblclick() {
|
|
||||||
if (!alarmOnline()) return;
|
|
||||||
window.$message.info(`查看警报器:${JSON.stringify({ code: alarm.code, name: alarm.name })}`);
|
|
||||||
},
|
|
||||||
onDragstart(event) {
|
|
||||||
if (!alarmOnline()) return;
|
|
||||||
console.log(event);
|
|
||||||
event.dataTransfer?.setData('type', 'alarm');
|
|
||||||
event.dataTransfer?.setData('code', alarm.code);
|
|
||||||
event.dataTransfer?.setData('name', alarm.name);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[h(NIcon, () => h(SirenIcon)), h('span', alarm.name)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 其他节点(兜底,理论上不会走到这里)
|
|
||||||
return option.label;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderNodeSuffix: TreeProps['renderSuffix'] = ({ option }) => {
|
|
||||||
if (isAlarmSiteNode(option)) {
|
|
||||||
const { online, offline, total } = option.stats;
|
|
||||||
return `(${online}/${offline}/${total})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAlarmAreaNode(option)) {
|
|
||||||
const { online, offline, total } = option.stats;
|
|
||||||
const suffixStyle: CSSProperties = option.areaLevel === 1 ? { marginRight: '8px', opacity: 0.6 } : { marginRight: '16px', opacity: 0.4 };
|
|
||||||
return h('div', { style: suffixStyle }, `(${online}/${offline}/${total})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resourcePanelStore = useResourcePanelStore();
|
|
||||||
const { searchPattern } = storeToRefs(resourcePanelStore);
|
|
||||||
|
|
||||||
const searchFilter: TreeProps['filter'] = (pattern, node) => {
|
|
||||||
if (!isAlarmNode(node)) return false;
|
|
||||||
return node.alarm.name.includes(pattern);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<template v-if="isLoading">
|
|
||||||
<div>loading...</div>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="!lineTabPanes || lineTabPanes.length === 0">
|
|
||||||
<NEmpty :description="'无数据'" />
|
|
||||||
</template>
|
|
||||||
<template v-else-if="lineTabPanes.length === 1">
|
|
||||||
<!--
|
|
||||||
为 NTree 设置 default-expand-all。
|
|
||||||
|
|
||||||
背景:Naive UI NTree 的 filter 自动展开逻辑仅监听 pattern 属性变更,不监听 data 属性变更
|
|
||||||
(详见 node_modules/naive-ui/es/tree/src/Tree.mjs:404 和 :385)。
|
|
||||||
当用户先输入搜索内容、再切换到其他线路/设备类型 Tab 时,NTabPane 的 v-if 会销毁旧
|
|
||||||
NTree 并创建新实例,新实例挂载时 pattern 已有值但未发生“变更”,因此内置 filter 不会
|
|
||||||
自动展开匹配节点,用户看到的是折叠的树。
|
|
||||||
|
|
||||||
解决:利用 NTabPane v-if 重建 NTree 的时机,通过 default-expand-all 在挂载瞬间检查
|
|
||||||
当前 searchPattern 是否有值 —— 有值则全展开,后续 NTree 的 filter 会自动隐藏不匹配
|
|
||||||
的节点(show-irrelevant-nodes="false"),最终呈现筛选后的展开树。
|
|
||||||
-->
|
|
||||||
<NTree
|
|
||||||
block-line
|
|
||||||
block-node
|
|
||||||
show-line
|
|
||||||
virtual-scroll
|
|
||||||
style="height: 100%"
|
|
||||||
:default-expand-all="searchPattern.trim().length > 0"
|
|
||||||
:override-default-node-click-behavior="overrideNodeClickBehavior"
|
|
||||||
:render-label="renderNodeLabel"
|
|
||||||
:render-suffix="renderNodeSuffix"
|
|
||||||
:data="lineTabPanes.at(0)?.alarmTree"
|
|
||||||
:pattern="searchPattern"
|
|
||||||
:filter="searchFilter"
|
|
||||||
:show-irrelevant-nodes="false"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<NTabs
|
|
||||||
:type="'card'"
|
|
||||||
:placement="'left'"
|
|
||||||
style="height: 100%"
|
|
||||||
:tab-style="{
|
|
||||||
boxSizing: 'content-box',
|
|
||||||
// 内容区宽度,与 MarqueeText max-width 保持一致
|
|
||||||
width: `${LINE_TAB_CONTENT_WIDTH}px`,
|
|
||||||
padding: '10px 8px',
|
|
||||||
}"
|
|
||||||
:style="{
|
|
||||||
'--n-bar-color': '#0000',
|
|
||||||
'--n-pane-padding-top': '0',
|
|
||||||
'--n-tab-gap-vertical': '0',
|
|
||||||
// '--n-tab-padding-vertical': '14px 12px'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<NTabPane v-for="{ lineCode, lineName, alarmTree } in lineTabPanes" :key="lineCode" :name="lineCode">
|
|
||||||
<template #tab>
|
|
||||||
<!-- max-width 与 tab-style width 保持同步 -->
|
|
||||||
<MarqueeText :text="lineName" :max-width="LINE_TAB_CONTENT_WIDTH" />
|
|
||||||
</template>
|
|
||||||
<template #default>
|
|
||||||
<!-- 同上:搜索后切换线路 Tab 时 NTabPane 重建 NTree,利用 default-expand-all 在 mount 时全展开 -->
|
|
||||||
<NTree
|
|
||||||
block-line
|
|
||||||
block-node
|
|
||||||
show-line
|
|
||||||
virtual-scroll
|
|
||||||
style="height: 100%"
|
|
||||||
:default-expand-all="searchPattern.trim().length > 0"
|
|
||||||
:override-default-node-click-behavior="overrideNodeClickBehavior"
|
|
||||||
:render-label="renderNodeLabel"
|
|
||||||
:render-suffix="renderNodeSuffix"
|
|
||||||
:data="alarmTree"
|
|
||||||
:pattern="searchPattern"
|
|
||||||
:filter="searchFilter"
|
|
||||||
:show-irrelevant-nodes="false"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</NTabPane>
|
|
||||||
</NTabs>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { NEmpty, NIcon, NTabPane, NTabs, NTree, type TreeOverrideNodeClickBehavior, type TreeProps } from 'naive-ui';
|
|
||||||
import { computed, h, type CSSProperties } from 'vue';
|
|
||||||
import MarqueeText from './marquee-text.vue';
|
|
||||||
import { useCameraStore, useResourcePanelStore } from '../stores';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import { isCameraNode, isCameraSiteNode, isCameraAreaNode } from '../types';
|
|
||||||
import PtzCamera from './icon/ptz-camera.vue';
|
|
||||||
import HemiPtzCamera from './icon/hemi-ptz-camera.vue';
|
|
||||||
import BulletCamera from './icon/bullet-camera.vue';
|
|
||||||
import { useIsFetching } from '@tanstack/vue-query';
|
|
||||||
import { VIMP_CHANNELS_QUERY_KEY, LINE_TAB_CONTENT_WIDTH } from '../constants';
|
|
||||||
|
|
||||||
const cameraStore = useCameraStore();
|
|
||||||
const { lineTabPanes } = storeToRefs(cameraStore);
|
|
||||||
|
|
||||||
// 不要直接从 query 中获取 isLoading状态,因为 query 执行完毕并不代表 store 也同步完毕
|
|
||||||
const isFetching = useIsFetching({ queryKey: [VIMP_CHANNELS_QUERY_KEY] });
|
|
||||||
const isLoading = computed(() => isFetching.value > 0 && !lineTabPanes.value);
|
|
||||||
|
|
||||||
const overrideNodeClickBehavior: TreeOverrideNodeClickBehavior = ({ option }) => {
|
|
||||||
const hasChildren = (option.children?.length ?? 0) > 0;
|
|
||||||
if (hasChildren) {
|
|
||||||
return 'toggleExpand';
|
|
||||||
} else {
|
|
||||||
return 'none';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderNodeLabel: TreeProps['renderLabel'] = ({ option }) => {
|
|
||||||
// 是车站节点
|
|
||||||
if (isCameraSiteNode(option)) {
|
|
||||||
const siteOnline = option.online;
|
|
||||||
const siteNodeStyle: CSSProperties = {
|
|
||||||
opacity: siteOnline ? 1 : 0.5,
|
|
||||||
};
|
|
||||||
return h('div', { style: siteNodeStyle }, option.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是中间节点(一级/二级区域)
|
|
||||||
if (isCameraAreaNode(option)) {
|
|
||||||
const site = option.site;
|
|
||||||
const nodeStyle: CSSProperties = {
|
|
||||||
opacity: site.online ? 1 : 0.5,
|
|
||||||
};
|
|
||||||
return h('div', { style: nodeStyle }, option.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是摄像机节点
|
|
||||||
if (isCameraNode(option)) {
|
|
||||||
const camera = option.camera;
|
|
||||||
const site = option.site;
|
|
||||||
|
|
||||||
const cameraOnline = () => {
|
|
||||||
return camera.status === 1 && site.online;
|
|
||||||
};
|
|
||||||
|
|
||||||
const cameraNodeStyle: CSSProperties = {
|
|
||||||
opacity: cameraOnline() ? 1 : 0.5,
|
|
||||||
cursor: cameraOnline() ? 'pointer' : 'not-allowed',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '6px',
|
|
||||||
};
|
|
||||||
const cameraIcon = option.type === '004' ? h(NIcon, () => h(PtzCamera)) : option.type === '005' ? h(NIcon, () => h(HemiPtzCamera)) : option.type === '006' ? h(NIcon, () => h(BulletCamera)) : null;
|
|
||||||
return h(
|
|
||||||
'div',
|
|
||||||
{
|
|
||||||
style: cameraNodeStyle,
|
|
||||||
draggable: cameraOnline(),
|
|
||||||
onDblclick() {
|
|
||||||
if (!cameraOnline()) return;
|
|
||||||
window.$message.info(`播放:${JSON.stringify({ code: camera.code, name: camera.name })}`);
|
|
||||||
},
|
|
||||||
onDragstart(event) {
|
|
||||||
if (!cameraOnline()) return;
|
|
||||||
console.log(event);
|
|
||||||
event.dataTransfer?.setData('type', 'camera');
|
|
||||||
event.dataTransfer?.setData('code', camera.code);
|
|
||||||
event.dataTransfer?.setData('name', camera.name);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[cameraIcon, h('span', camera.name)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 其他节点(兜底,理论上不会走到这里)
|
|
||||||
return option.label;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderNodeSuffix: TreeProps['renderSuffix'] = ({ option }) => {
|
|
||||||
if (isCameraSiteNode(option)) {
|
|
||||||
const { online, offline, total } = option.stats;
|
|
||||||
return `(${online}/${offline}/${total})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCameraAreaNode(option)) {
|
|
||||||
const { online, offline, total } = option.stats;
|
|
||||||
const suffixStyle: CSSProperties = option.areaLevel === 1 ? { marginRight: '8px', opacity: 0.6 } : { marginRight: '16px', opacity: 0.4 };
|
|
||||||
return h('div', { style: suffixStyle }, `(${online}/${offline}/${total})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resourcePanelStore = useResourcePanelStore();
|
|
||||||
const { searchPattern } = storeToRefs(resourcePanelStore);
|
|
||||||
|
|
||||||
const searchFilter: TreeProps['filter'] = (pattern, node) => {
|
|
||||||
if (!isCameraNode(node)) return false;
|
|
||||||
return node.camera.name.includes(pattern);
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<template v-if="isLoading">
|
|
||||||
<div>loading...</div>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="!lineTabPanes || lineTabPanes.length === 0">
|
|
||||||
<NEmpty :description="'无数据'" />
|
|
||||||
</template>
|
|
||||||
<template v-else-if="lineTabPanes.length === 1">
|
|
||||||
<!--
|
|
||||||
为 NTree 设置 default-expand-all。
|
|
||||||
|
|
||||||
背景:Naive UI NTree 的 filter 自动展开逻辑仅监听 pattern 属性变更,不监听 data 属性变更
|
|
||||||
(详见 node_modules/naive-ui/es/tree/src/Tree.mjs:404 和 :385)。
|
|
||||||
当用户先输入搜索内容、再切换到其他线路/设备类型 Tab 时,NTabPane 的 v-if 会销毁旧
|
|
||||||
NTree 并创建新实例,新实例挂载时 pattern 已有值但未发生“变更”,因此内置 filter 不会
|
|
||||||
自动展开匹配节点,用户看到的是折叠的树。
|
|
||||||
|
|
||||||
解决:利用 NTabPane v-if 重建 NTree 的时机,通过 default-expand-all 在挂载瞬间检查
|
|
||||||
当前 searchPattern 是否有值 —— 有值则全展开,后续 NTree 的 filter 会自动隐藏不匹配
|
|
||||||
的节点(show-irrelevant-nodes="false"),最终呈现筛选后的展开树。
|
|
||||||
-->
|
|
||||||
<NTree
|
|
||||||
block-line
|
|
||||||
block-node
|
|
||||||
show-line
|
|
||||||
virtual-scroll
|
|
||||||
style="height: 100%"
|
|
||||||
:default-expand-all="searchPattern.trim().length > 0"
|
|
||||||
:override-default-node-click-behavior="overrideNodeClickBehavior"
|
|
||||||
:render-label="renderNodeLabel"
|
|
||||||
:render-suffix="renderNodeSuffix"
|
|
||||||
:data="lineTabPanes.at(0)?.cameraTree"
|
|
||||||
:pattern="searchPattern"
|
|
||||||
:filter="searchFilter"
|
|
||||||
:show-irrelevant-nodes="false"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<NTabs
|
|
||||||
:type="'card'"
|
|
||||||
:placement="'left'"
|
|
||||||
style="height: 100%"
|
|
||||||
:tab-style="{
|
|
||||||
boxSizing: 'content-box',
|
|
||||||
// 内容区宽度,与 MarqueeText max-width 保持一致
|
|
||||||
width: `${LINE_TAB_CONTENT_WIDTH}px`,
|
|
||||||
padding: '10px 8px',
|
|
||||||
}"
|
|
||||||
:style="{
|
|
||||||
'--n-bar-color': '#0000',
|
|
||||||
'--n-pane-padding-top': '0',
|
|
||||||
'--n-tab-gap-vertical': '0',
|
|
||||||
// '--n-tab-padding-vertical': '14px 12px'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<NTabPane v-for="{ lineCode, lineName, cameraTree } in lineTabPanes" :key="lineCode" :name="lineCode">
|
|
||||||
<template #tab>
|
|
||||||
<!-- max-width 与 tab-style width 保持同步 -->
|
|
||||||
<MarqueeText :text="lineName" :max-width="LINE_TAB_CONTENT_WIDTH" />
|
|
||||||
</template>
|
|
||||||
<template #default>
|
|
||||||
<!-- 同上:搜索后切换线路 Tab 时 NTabPane 重建 NTree,利用 default-expand-all 在 mount 时全展开 -->
|
|
||||||
<NTree
|
|
||||||
block-line
|
|
||||||
block-node
|
|
||||||
show-line
|
|
||||||
virtual-scroll
|
|
||||||
style="height: 100%"
|
|
||||||
:default-expand-all="searchPattern.trim().length > 0"
|
|
||||||
:override-default-node-click-behavior="overrideNodeClickBehavior"
|
|
||||||
:render-label="renderNodeLabel"
|
|
||||||
:render-suffix="renderNodeSuffix"
|
|
||||||
:data="cameraTree"
|
|
||||||
:pattern="searchPattern"
|
|
||||||
:filter="searchFilter"
|
|
||||||
:show-irrelevant-nodes="false"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</NTabPane>
|
|
||||||
</NTabs>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { NButton, NEmpty, NModal, NPagination, NSpace, NTabPane, NTabs, NTag, NText } from 'naive-ui';
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import { useChannelsFalloutStore, type FalloutLog } from '../stores/channels-fallout';
|
|
||||||
|
|
||||||
const PAGE_SIZE = 10;
|
|
||||||
const TABS = ['LineMiss', 'AreaMiss', 'DupCode'] as const;
|
|
||||||
|
|
||||||
const fallout = useChannelsFalloutStore();
|
|
||||||
const show = ref(false);
|
|
||||||
const frozen = ref<FalloutLog[]>([]);
|
|
||||||
const activeTab = ref<FalloutLog['type']>('LineMiss');
|
|
||||||
const pages = ref({ LineMiss: 1, AreaMiss: 1, DupCode: 1 });
|
|
||||||
|
|
||||||
const tabLogs = (type: FalloutLog['type']) => frozen.value.filter((l) => l.type === type);
|
|
||||||
|
|
||||||
const pagedLogs = (type: FalloutLog['type']) => {
|
|
||||||
const p = pages.value[type];
|
|
||||||
const all = tabLogs(type);
|
|
||||||
const start = (p - 1) * PAGE_SIZE;
|
|
||||||
return all.slice(start, start + PAGE_SIZE);
|
|
||||||
};
|
|
||||||
|
|
||||||
const tabCount = (type: FalloutLog['type']) => tabLogs(type).length;
|
|
||||||
|
|
||||||
const tabLabel = (type: FalloutLog['type']) => {
|
|
||||||
const labels = { LineMiss: '线路缺失', AreaMiss: '区域缺失', DupCode: '重复编码' };
|
|
||||||
return `${labels[type]} (${tabCount(type)})`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const summaryCamera = computed(() => {
|
|
||||||
const items = frozen.value.filter((e) => e.deviceType === 'camera');
|
|
||||||
return {
|
|
||||||
lineMiss: items.filter((e) => e.type === 'LineMiss').length,
|
|
||||||
areaMiss: items.filter((e) => e.type === 'AreaMiss').length,
|
|
||||||
dupCode: items.filter((e) => e.type === 'DupCode').length,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const summaryAlarm = computed(() => {
|
|
||||||
const items = frozen.value.filter((e) => e.deviceType === 'alarm');
|
|
||||||
return {
|
|
||||||
lineMiss: items.filter((e) => e.type === 'LineMiss').length,
|
|
||||||
areaMiss: items.filter((e) => e.type === 'AreaMiss').length,
|
|
||||||
dupCode: items.filter((e) => e.type === 'DupCode').length,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const open = () => {
|
|
||||||
refresh();
|
|
||||||
show.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const refresh = () => {
|
|
||||||
frozen.value = JSON.parse(JSON.stringify(fallout.logs));
|
|
||||||
pages.value = { LineMiss: 1, AreaMiss: 1, DupCode: 1 };
|
|
||||||
window.$message.success(`已刷新,共 ${frozen.value.length} 条异常日志`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onTabChange = () => {
|
|
||||||
pages.value[activeTab.value] = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
const exportTxt = () => {
|
|
||||||
const lines: string[] = [];
|
|
||||||
const typeLabel = { LineMiss: '线路缺失', AreaMiss: '区域缺失', DupCode: '重复编码' };
|
|
||||||
const deviceLabel = { camera: '摄像机', alarm: '警报器' };
|
|
||||||
|
|
||||||
lines.push(`通道匹配异常日志 — ${new Date().toLocaleString()}`);
|
|
||||||
lines.push(`总计: ${frozen.value.length} 条`);
|
|
||||||
lines.push('');
|
|
||||||
|
|
||||||
for (const log of frozen.value) {
|
|
||||||
const tag = `[${typeLabel[log.type]}][${deviceLabel[log.deviceType]}]`;
|
|
||||||
if (log.type === 'LineMiss') {
|
|
||||||
lines.push(`${tag} 站点 ${log.siteCode} 前3位 ${log.lineCode} — 通道数 ${log.channels.length}: ${JSON.stringify(log.channels)}`);
|
|
||||||
} else if (log.type === 'AreaMiss') {
|
|
||||||
lines.push(`${tag} [${log.deviceCode}] ${log.deviceName} @ 站点 ${log.siteCode} — ${log.areaLevel === 1 ? '一级' : '二级'}区域码 ${log.areaCode} 未匹配`);
|
|
||||||
} else {
|
|
||||||
lines.push(`${tag} [${log.deviceCode}] ${log.deviceName} @ 站点 ${log.siteCode} — 编码重复`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const blob = new Blob([lines.join('\n')], { type: 'text/plain;charset=utf-8' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = `通道匹配异常日志-${Date.now()}.txt`;
|
|
||||||
a.click();
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
};
|
|
||||||
|
|
||||||
defineExpose({ open });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<NModal v-model:show="show" preset="card" title="通道匹配异常日志" style="max-width: 900px" :mask-closable="true">
|
|
||||||
<div v-if="frozen.length === 0" style="padding: 40px 0; text-align: center">
|
|
||||||
<NEmpty description="暂无异常日志" />
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<!-- 摘要 -->
|
|
||||||
<div style="margin-bottom: 16px; display: flex; gap: 24px">
|
|
||||||
<div>
|
|
||||||
<NText strong>摄像机</NText>
|
|
||||||
<NSpace style="margin-top: 4px">
|
|
||||||
<NTag size="small" type="warning">线路缺失 {{ summaryCamera.lineMiss }}</NTag>
|
|
||||||
<NTag size="small" type="error">区域缺失 {{ summaryCamera.areaMiss }}</NTag>
|
|
||||||
<NTag size="small" type="info">重复编码 {{ summaryCamera.dupCode }}</NTag>
|
|
||||||
</NSpace>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<NText strong>警报器</NText>
|
|
||||||
<NSpace style="margin-top: 4px">
|
|
||||||
<NTag size="small" type="warning">线路缺失 {{ summaryAlarm.lineMiss }}</NTag>
|
|
||||||
<NTag size="small" type="error">区域缺失 {{ summaryAlarm.areaMiss }}</NTag>
|
|
||||||
<NTag size="small" type="info">重复编码 {{ summaryAlarm.dupCode }}</NTag>
|
|
||||||
</NSpace>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tabs + 分页列表 -->
|
|
||||||
<NTabs v-model:value="activeTab" type="line" size="small" @update:value="onTabChange">
|
|
||||||
<NTabPane v-for="type in TABS" :key="type" :name="type" :tab="tabLabel(type)">
|
|
||||||
<div style="height: 350px; overflow-y: auto; margin: 8px 0">
|
|
||||||
<div
|
|
||||||
v-for="(log, i) in pagedLogs(type)"
|
|
||||||
:key="i"
|
|
||||||
style="padding: 6px 12px; font-size: 13px; font-family: monospace; border-bottom: 1px solid #f0f0f0; display: flex; align-items: flex-start; gap: 8px"
|
|
||||||
>
|
|
||||||
<NTag size="tiny" :type="log.deviceType === 'camera' ? 'info' : 'success'" style="flex-shrink: 0; margin-top: 1px">
|
|
||||||
{{ log.deviceType === 'camera' ? '摄像机' : '警报器' }}
|
|
||||||
</NTag>
|
|
||||||
<span style="word-break: break-all">
|
|
||||||
<template v-if="log.type === 'LineMiss'">
|
|
||||||
站点 {{ log.siteCode }} 前3位 {{ log.lineCode }} — 通道数 {{ log.channels.length }} :
|
|
||||||
{{ JSON.stringify(log.channels) }}
|
|
||||||
</template>
|
|
||||||
<template v-else-if="log.type === 'AreaMiss'">
|
|
||||||
[{{ log.deviceCode }}] {{ log.deviceName }} @ 站点 {{ log.siteCode }} — {{ log.areaLevel === 1 ? '一级' : '二级' }}区域码 {{ log.areaCode }} 未匹配
|
|
||||||
</template>
|
|
||||||
<template v-else> [{{ log.deviceCode }}] {{ log.deviceName }} @ 站点 {{ log.siteCode }} — 编码重复 </template>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<NEmpty v-if="pagedLogs(type).length === 0" description="该分类暂无日志" style="padding: 24px 0" />
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; justify-content: center; margin-top: 8px">
|
|
||||||
<NPagination v-if="tabLogs(type).length > PAGE_SIZE" size="small" :page="pages[type]" :page-size="PAGE_SIZE" :item-count="tabLogs(type).length" @update:page="pages[type] = $event" />
|
|
||||||
</div>
|
|
||||||
</NTabPane>
|
|
||||||
</NTabs>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<NSpace justify="end">
|
|
||||||
<NButton size="small" type="info" @click="exportTxt" :disabled="frozen.length === 0">导出 TXT</NButton>
|
|
||||||
<NButton size="small" type="info" @click="refresh">刷新</NButton>
|
|
||||||
</NSpace>
|
|
||||||
</template>
|
|
||||||
</NModal>
|
|
||||||
</template>
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { NButton, NIcon, NInputNumber, NTabPane, NTabs, NText } from 'naive-ui';
|
|
||||||
import { ChevronRightIcon, DatabaseIcon, LayoutGridIcon, SettingsIcon, SlidersHorizontalIcon, ZapIcon } from 'lucide-vue-next';
|
|
||||||
import { ref, type Component } from 'vue';
|
|
||||||
import { useConfigPanelStore, useQueryConfigStore } from '../stores';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
|
|
||||||
interface ControlTabPane {
|
|
||||||
name: string;
|
|
||||||
tab: string;
|
|
||||||
icon: Component;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabs: ControlTabPane[] = [
|
|
||||||
{ name: 'component', tab: '组件', icon: LayoutGridIcon },
|
|
||||||
{ name: 'config', tab: '属性', icon: SlidersHorizontalIcon },
|
|
||||||
{ name: 'data', tab: '数据', icon: DatabaseIcon },
|
|
||||||
{ name: 'interaction', tab: '事件', icon: ZapIcon },
|
|
||||||
];
|
|
||||||
|
|
||||||
const PANEL_WIDTH_EXPANDED = '320px';
|
|
||||||
const PANEL_WIDTH_COLLAPSED = '72px';
|
|
||||||
const TAB_WIDTH = '72px';
|
|
||||||
|
|
||||||
const activeTab = ref('settings');
|
|
||||||
|
|
||||||
const configPanelStore = useConfigPanelStore();
|
|
||||||
const { collapsed } = storeToRefs(configPanelStore);
|
|
||||||
const { refetchInterval } = storeToRefs(useQueryConfigStore());
|
|
||||||
|
|
||||||
const expandConfigPanel = () => {
|
|
||||||
if (collapsed.value) {
|
|
||||||
configPanelStore.toggleCollapsed();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
:style="{
|
|
||||||
width: collapsed ? PANEL_WIDTH_COLLAPSED : PANEL_WIDTH_EXPANDED,
|
|
||||||
flexShrink: 0,
|
|
||||||
height: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
overflow: 'hidden',
|
|
||||||
transition: 'width 0.2s',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
:style="{
|
|
||||||
width: PANEL_WIDTH_EXPANDED,
|
|
||||||
height: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
:style="{
|
|
||||||
height: '42px',
|
|
||||||
flexShrink: 0,
|
|
||||||
padding: '8px 0',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
:style="{
|
|
||||||
display: 'grid',
|
|
||||||
placeItems: 'center',
|
|
||||||
width: '32px',
|
|
||||||
marginRight: 'auto',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<NButton text @click="configPanelStore.toggleCollapsed()">
|
|
||||||
<NIcon :component="ChevronRightIcon" />
|
|
||||||
</NButton>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
:style="{
|
|
||||||
display: 'grid',
|
|
||||||
placeItems: 'center',
|
|
||||||
width: TAB_WIDTH,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<NText>控制</NText>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
:style="{
|
|
||||||
flex: 1,
|
|
||||||
minHeight: 0,
|
|
||||||
overflow: 'hidden',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<NTabs
|
|
||||||
v-model:value="activeTab"
|
|
||||||
:type="'bar'"
|
|
||||||
:placement="'right'"
|
|
||||||
:size="'small'"
|
|
||||||
:tab-style="{
|
|
||||||
width: TAB_WIDTH,
|
|
||||||
height: '64px',
|
|
||||||
}"
|
|
||||||
:style="{
|
|
||||||
height: '100%',
|
|
||||||
'--n-pane-padding-top': '0',
|
|
||||||
'--n-tab-gap-vertical': '0',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<NTabPane name="settings" :tab-props="{ onClick: () => expandConfigPanel() }">
|
|
||||||
<template #tab>
|
|
||||||
<div :style="{ width: '48px', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }">
|
|
||||||
<NIcon :size="18" :component="SettingsIcon" />
|
|
||||||
<div :style="{ fontSize: '12px' }">设置</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div style="padding: 16px; display: flex; flex-direction: column; gap: 12px;">
|
|
||||||
<div>
|
|
||||||
<NText depth="3" style="font-size: 12px;">轮询间隔</NText>
|
|
||||||
<NInputNumber v-model:value="refetchInterval" :min="3" :max="300" :step="1" size="small" style="width: 100%;" />
|
|
||||||
</div>
|
|
||||||
<NButton size="tiny" @click="refetchInterval = 10">恢复默认</NButton>
|
|
||||||
</div>
|
|
||||||
</NTabPane>
|
|
||||||
<NTabPane v-for="t in tabs" :key="t.name" :name="t.name" :tab="t.tab" :tab-props="{ onClick: () => expandConfigPanel() }">
|
|
||||||
<template #tab>
|
|
||||||
<div :style="{ width: '48px', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }">
|
|
||||||
<NIcon :size="18" :component="t.icon" />
|
|
||||||
<div :style="{ fontSize: '12px' }">{{ t.tab }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div :style="{ padding: '20px', textAlign: 'center', fontSize: '12px' }">{{ t.tab }}面板(占位)</div>
|
|
||||||
</NTabPane>
|
|
||||||
</NTabs>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg class="icon" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path
|
|
||||||
d="m6.03 12.03l2 3.47l-2.53 3.18L2 12.62l4.03-.59M17 18v-2.71c.88-.39 1.5-1.26 1.5-2.29c0-.57-.2-1.1-.53-1.5l1.97-1.15c1.01-.59 1.36-1.88.77-2.89l-1.38-2.4a2.125 2.125 0 0 0-2.89-.78L8.31 9c-.95.53-1.28 1.75-.73 2.71l1.5 2.6c.55.95 1.78 1.28 2.73.73l1.88-1.08c.25.59.72 1.07 1.31 1.33V18c0 1.1.9 2 2 2h5v-2h-5Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg class="icon" viewBox="0 0 48 48" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g fill="none" stroke="currentColor" stroke-width="4">
|
|
||||||
<path d="M8 10v14c0 8.837 7.163 16 16 16s16-7.163 16-16V10" />
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 10h40" />
|
|
||||||
<path stroke-linejoin="round" d="M24 30a6 6 0 1 0 0-12a6 6 0 0 0 0 12Z" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg class="icon" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path
|
|
||||||
d="M2 4.5A1.5 1.5 0 0 1 3.5 3h13a1.5 1.5 0 0 1 0 3h-13A1.5 1.5 0 0 1 2 4.5ZM10 9a3 3 0 1 0 0 6a3 3 0 0 0 0-6Zm-2 3a2 2 0 1 1 4 0a2 2 0 0 1-4 0ZM3 7h14v4a7 7 0 1 1-14 0V7Zm7 1a4 4 0 1 0 0 8a4 4 0 0 0 0-8Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, toRefs, useTemplateRef } from 'vue';
|
|
||||||
import { useResizeObserver } from '@vueuse/core';
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<{
|
|
||||||
text: string;
|
|
||||||
maxWidth: number;
|
|
||||||
mode?: 'swing' | 'continuous';
|
|
||||||
}>(),
|
|
||||||
{
|
|
||||||
mode: 'continuous',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const { text, maxWidth, mode } = toRefs(props);
|
|
||||||
|
|
||||||
const TEMPLATE_REF = 'marquee-text-ref';
|
|
||||||
const marqueeTextRef = useTemplateRef<HTMLDivElement>(TEMPLATE_REF);
|
|
||||||
const overflowing = ref<boolean>(false);
|
|
||||||
const scrollDistance = ref<number>(0);
|
|
||||||
const overflowDistance = ref<number>(0);
|
|
||||||
|
|
||||||
useResizeObserver(marqueeTextRef, () => {
|
|
||||||
const element = marqueeTextRef.value;
|
|
||||||
if (!element) return;
|
|
||||||
const { scrollWidth, clientWidth } = element;
|
|
||||||
overflowing.value = scrollWidth > clientWidth;
|
|
||||||
scrollDistance.value = scrollWidth;
|
|
||||||
overflowDistance.value = scrollWidth - clientWidth;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
:ref="TEMPLATE_REF"
|
|
||||||
class="marquee-text"
|
|
||||||
:style="{
|
|
||||||
maxWidth: `${maxWidth}px`,
|
|
||||||
'--scroll-distance': `${mode === 'swing' ? overflowDistance : scrollDistance}px`,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<!-- 不溢出 -->
|
|
||||||
<div v-if="!overflowing" class="marquee-text__text">{{ text }}</div>
|
|
||||||
|
|
||||||
<!-- 来回滚动 -->
|
|
||||||
<div v-else-if="mode === 'swing'" class="marquee-text__text overflowing">{{ text }}</div>
|
|
||||||
|
|
||||||
<!-- 单向连续 -->
|
|
||||||
<div v-else class="marquee-text__scroll">
|
|
||||||
<span>{{ text }}</span>
|
|
||||||
<span class="marquee-text__gap" />
|
|
||||||
<span>{{ text }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.marquee-text {
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
&__text {
|
|
||||||
&.overflowing {
|
|
||||||
display: inline-block;
|
|
||||||
animation: marquee-swing 4s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__scroll {
|
|
||||||
display: inline-block;
|
|
||||||
animation: marquee-continuous 3s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__gap {
|
|
||||||
display: inline-block;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes marquee-swing {
|
|
||||||
0%,
|
|
||||||
12.5% {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
40%,
|
|
||||||
60% {
|
|
||||||
transform: translateX(calc(-1 * var(--scroll-distance)));
|
|
||||||
}
|
|
||||||
87.5%,
|
|
||||||
100% {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes marquee-continuous {
|
|
||||||
0% {
|
|
||||||
transform: translateX(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: translateX(calc(-1 * var(--scroll-distance) - 24px));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { NButton, NIcon, NInput, NTabPane, NTabs, NText, type TabPaneProps } from 'naive-ui';
|
|
||||||
import { computed, ref, type Component } from 'vue';
|
|
||||||
import AlarmTree from './alarm-tree.vue';
|
|
||||||
import CameraTree from './camera-tree.vue';
|
|
||||||
import { ChevronLeftIcon, MapPinnedIcon, MonitorIcon, SirenIcon, WandSparklesIcon } from 'lucide-vue-next';
|
|
||||||
import { useResourcePanelStore } from '../stores';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import BulletCamera from './icon/bullet-camera.vue';
|
|
||||||
|
|
||||||
const PANEL_WIDTH_EXPANDED = '480px';
|
|
||||||
const PANEL_WIDTH_COLLAPSED = '72px';
|
|
||||||
|
|
||||||
interface ResourceTabPane extends TabPaneProps {
|
|
||||||
name: string;
|
|
||||||
tab: string;
|
|
||||||
icon?: Component;
|
|
||||||
component?: Component;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourceTabPanes: ResourceTabPane[] = [
|
|
||||||
{ name: 'camera', tab: '摄像头', icon: BulletCamera, component: CameraTree },
|
|
||||||
{ name: 'alarm', tab: '警报器', icon: SirenIcon, component: AlarmTree },
|
|
||||||
{ name: 'monitor', tab: '监视器', icon: MonitorIcon },
|
|
||||||
{ name: 'combine-tech', tab: '复合技', icon: WandSparklesIcon },
|
|
||||||
{ name: 'leaflet-map', tab: '地图', icon: MapPinnedIcon },
|
|
||||||
];
|
|
||||||
|
|
||||||
const activeTabName = ref(resourceTabPanes.at(0)?.name ?? '');
|
|
||||||
|
|
||||||
const showSearchInput = computed(() => {
|
|
||||||
return ['camera', 'alarm'].includes(activeTabName.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
const resourcePanelStore = useResourcePanelStore();
|
|
||||||
const { collapsed, searchPattern } = storeToRefs(resourcePanelStore);
|
|
||||||
|
|
||||||
const collapseResourcePanel = () => {
|
|
||||||
if (!collapsed.value) {
|
|
||||||
resourcePanelStore.toggleCollapsed();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const expandResourcePanel = () => {
|
|
||||||
if (collapsed.value) {
|
|
||||||
resourcePanelStore.toggleCollapsed();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="resource-panel__wrapper"
|
|
||||||
:style="{
|
|
||||||
width: collapsed ? PANEL_WIDTH_COLLAPSED : PANEL_WIDTH_EXPANDED,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="resource-panel"
|
|
||||||
:style="{
|
|
||||||
width: PANEL_WIDTH_EXPANDED,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div class="resource-panel__title">
|
|
||||||
<div style="display: grid; place-items: center" :style="{ width: PANEL_WIDTH_COLLAPSED }">
|
|
||||||
<NText>资源</NText>
|
|
||||||
</div>
|
|
||||||
<template v-if="showSearchInput">
|
|
||||||
<div style="width: 240px; margin-left: auto">
|
|
||||||
<NInput clearable :size="'tiny'" :placeholder="'搜索'" v-model:value="searchPattern" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<div style="margin: 0px 16px; display: grid; place-items: center" :style="{ marginLeft: showSearchInput ? '8px' : 'auto' }">
|
|
||||||
<NButton text @click="collapseResourcePanel">
|
|
||||||
<NIcon :component="ChevronLeftIcon"></NIcon>
|
|
||||||
</NButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="resource-panel__tabs-wrapper">
|
|
||||||
<NTabs
|
|
||||||
:type="'bar'"
|
|
||||||
:size="'small'"
|
|
||||||
:placement="'left'"
|
|
||||||
v-model:value="activeTabName"
|
|
||||||
:tab-style="{
|
|
||||||
height: '64px',
|
|
||||||
width: '72px',
|
|
||||||
}"
|
|
||||||
:style="{
|
|
||||||
height: '100%', // 为了确保 tabs 高度和 panel 高度一致,否则设备树会超出 panel 高度,导致虚拟滚动失效
|
|
||||||
'--n-pane-padding-top': '0',
|
|
||||||
'--n-tab-gap-vertical': '0',
|
|
||||||
// '--n-tab-padding-vertical': '14px 6px',
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<NTabPane
|
|
||||||
v-for="{ name: resourceName, tab: resourceTab, icon: resourceIcon, component } in resourceTabPanes"
|
|
||||||
:key="resourceName"
|
|
||||||
:name="resourceName"
|
|
||||||
:tab-props="{ onClick: () => expandResourcePanel() }"
|
|
||||||
>
|
|
||||||
<template #tab>
|
|
||||||
<div style="width: 48px; display: flex; flex-direction: column; justify-content: center; align-items: center">
|
|
||||||
<NIcon :size="18" :component="resourceIcon"></NIcon>
|
|
||||||
<div style="font-size: 12px">{{ resourceTab }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #default>
|
|
||||||
<template v-if="!!component">
|
|
||||||
<component :is="component" />
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</NTabPane>
|
|
||||||
</NTabs>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.resource-panel__wrapper {
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
|
|
||||||
.resource-panel {
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
min-height: 42px;
|
|
||||||
max-height: 42px;
|
|
||||||
padding: 8px 0px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__tabs-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { NInput, NTabs, NTab } from 'naive-ui';
|
|
||||||
import { h, ref } from 'vue';
|
|
||||||
import { useScreenStore } from '../stores';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
|
|
||||||
const screenStore = useScreenStore();
|
|
||||||
const { screens, activeScreenId } = storeToRefs(screenStore);
|
|
||||||
|
|
||||||
const onAdd = () => {
|
|
||||||
screenStore.addScreen();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClose = (id: string) => {
|
|
||||||
const screen = screens.value.find(s => s.id === id)
|
|
||||||
if (!screen) return
|
|
||||||
window.$dialog.warning({
|
|
||||||
title: '删除屏幕',
|
|
||||||
content: `确认删除屏幕"${screen.name}"吗?此操作无法撤销。`,
|
|
||||||
positiveText: '删除',
|
|
||||||
negativeText: '取消',
|
|
||||||
onPositiveClick: () => {
|
|
||||||
screenStore.removeScreen(id)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
const renameValue = ref('');
|
|
||||||
|
|
||||||
const onTabDblclick = (id: string) => {
|
|
||||||
const screen = screens.value.find(s => s.id === id)
|
|
||||||
if (!screen) return
|
|
||||||
renameValue.value = screen.name
|
|
||||||
window.$dialog.info({
|
|
||||||
title: '重命名屏幕',
|
|
||||||
positiveText: '确认',
|
|
||||||
negativeText: '取消',
|
|
||||||
content: () =>
|
|
||||||
h(NInput, {
|
|
||||||
value: renameValue.value,
|
|
||||||
'onUpdate:value': (v: string) => {
|
|
||||||
renameValue.value = v
|
|
||||||
},
|
|
||||||
placeholder: '请输入屏幕名称',
|
|
||||||
autofocus: true,
|
|
||||||
onKeyup: (e: KeyboardEvent) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
handleRenameConfirm(id)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
onPositiveClick: () => {
|
|
||||||
handleRenameConfirm(id)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRenameConfirm = (id: string) => {
|
|
||||||
screenStore.renameScreen(id, renameValue.value)
|
|
||||||
};
|
|
||||||
|
|
||||||
const onDragover = (e: DragEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy'
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDrop = (e: DragEvent) => {
|
|
||||||
e.preventDefault()
|
|
||||||
// TODO: 放置占位组件(等 grid-layout-plus 接入后实现)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :style="{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column' }">
|
|
||||||
<div :style="{ flexShrink: 0, padding: '0 8px', userSelect: 'none' }">
|
|
||||||
<NTabs v-model:value="activeScreenId" type="card" size="small" animated :addable="true" @add="onAdd" @close="onClose">
|
|
||||||
<NTab v-for="s in screens" :key="s.id" :name="s.id" :tab="s.name" closable @dblclick="onTabDblclick(s.id)" />
|
|
||||||
</NTabs>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
:style="{
|
|
||||||
flex: 1,
|
|
||||||
minHeight: 0,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
backgroundColor: '#f5f5f5',
|
|
||||||
backgroundImage:
|
|
||||||
'linear-gradient(45deg, #e5e5e5 25%, transparent 25%), linear-gradient(-45deg, #e5e5e5 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #e5e5e5 75%), linear-gradient(-45deg, transparent 75%, #e5e5e5 75%)',
|
|
||||||
backgroundSize: '16px 16px',
|
|
||||||
backgroundPosition: '0 0, 0 8px, 8px -8px, -8px 0',
|
|
||||||
}"
|
|
||||||
@dragover="onDragover"
|
|
||||||
@drop="onDrop"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './query';
|
|
||||||
export * from './sync';
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './use-channels-query';
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
import { CancelledError, useQuery } from '@tanstack/vue-query';
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import type { AxiosRequestConfig } from 'axios';
|
|
||||||
import axios, { isCancel } from 'axios';
|
|
||||||
import { compileCodeAreas, type CodeArea, type CodeLines, type CodeSites } from '../../types';
|
|
||||||
import { clientCatalogAllDeviceApi, clientCatalogChannelApi, parseVimpErrorFeedback, type VimpChannel, type VimpSite } from '../../apis';
|
|
||||||
import { VIMP_CHANNELS_QUERY_KEY } from '../../constants';
|
|
||||||
import { useQueryConfigStore } from '../../stores';
|
|
||||||
|
|
||||||
const config: AxiosRequestConfig = {
|
|
||||||
headers: {
|
|
||||||
'Cache-Control': 'no-store',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildTrainAreas = () => {
|
|
||||||
const codeTrainAreas: CodeArea[] = [];
|
|
||||||
for (let i = 0; i < 999; i++) {
|
|
||||||
const codeTrain = i.toString().padStart(3, '0');
|
|
||||||
// 市域线name为车组,改造线name为车次
|
|
||||||
const area: CodeArea = { code: codeTrain, name: '车次' + codeTrain, subs: [] };
|
|
||||||
for (let j = 0; j <= 99; j++) {
|
|
||||||
const codeCarriage = j.toString().padStart(2, '0');
|
|
||||||
const subArea: CodeArea['subs'][number] = { code: codeTrain + codeCarriage, name: '车厢' + codeCarriage };
|
|
||||||
area.subs.push(subArea);
|
|
||||||
}
|
|
||||||
// const areaPreserve: CodeArea['subs'][number] = { code: codeTrain + '51', name: '预留' };
|
|
||||||
// area.subs.push(areaPreserve);
|
|
||||||
codeTrainAreas.push(area);
|
|
||||||
}
|
|
||||||
return codeTrainAreas;
|
|
||||||
};
|
|
||||||
|
|
||||||
const compareByCode = <T extends { code: string }>(a: T, b: T) => {
|
|
||||||
if (a.code < b.code) return -1;
|
|
||||||
if (a.code > b.code) return 1;
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
const sortSitesByCode = (sites: VimpSite[]) => {
|
|
||||||
sites.sort(compareByCode);
|
|
||||||
};
|
|
||||||
const sortChannelsMapByCode = (siteCodeToChannelsMap: Map<string, VimpChannel[]>) => {
|
|
||||||
for (const channels of siteCodeToChannelsMap.values()) {
|
|
||||||
channels.sort(compareByCode);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useChannelsQuery = () => {
|
|
||||||
const queryConfigStore = useQueryConfigStore();
|
|
||||||
|
|
||||||
return useQuery({
|
|
||||||
queryKey: computed(() => [VIMP_CHANNELS_QUERY_KEY]),
|
|
||||||
refetchInterval: computed(() => queryConfigStore.refetchInterval * 1000),
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
queryFn: async ({ signal }) => {
|
|
||||||
// 请求所有码表
|
|
||||||
const codeLines = (await axios.get<CodeLines>('/cdn/vimp/codes/codeLines.json', config)).data;
|
|
||||||
const codeSites = (await axios.get<CodeSites>('/cdn/vimp/codes/codeStations.json', config)).data;
|
|
||||||
const codeStationAreas = (await axios.get<CodeArea[]>('/cdn/vimp/codes/codeStationAreas.json', config)).data;
|
|
||||||
const codeParkingAreas = (await axios.get<CodeArea[]>('/cdn/vimp/codes/codeParkingAreas.json', config)).data;
|
|
||||||
const codeOccAreas = (await axios.get<CodeArea[]>('/cdn/vimp/codes/codeOccAreas.json', config)).data;
|
|
||||||
const codeTrainAreas = buildTrainAreas();
|
|
||||||
// 预编译区域码表索引 (性能优化)
|
|
||||||
const compiledCodeAreas = compileCodeAreas({
|
|
||||||
codeStationAreas,
|
|
||||||
codeParkingAreas,
|
|
||||||
codeOccAreas,
|
|
||||||
codeTrainAreas,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sitesFromApi = await clientCatalogAllDeviceApi({ signal }).catch((error: Error) => {
|
|
||||||
if (isCancel(error) || error instanceof CancelledError) return;
|
|
||||||
console.error(error);
|
|
||||||
const errorFeedback = parseVimpErrorFeedback(error);
|
|
||||||
window.$message.error(errorFeedback);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 从 /allDevice 接口获取的站点信息并不保证真实性和完整性,
|
|
||||||
// 例如有一个站点的编码是 010699 开头,但是其下的通道是 010199 和 010599 开头,
|
|
||||||
// 而 010699 是一个不存在的站点编码,所以需要基于通道的编码来确定所有的站点。
|
|
||||||
|
|
||||||
// 摄像机站点列表 (按通道实际归属 siteCode 聚合)
|
|
||||||
const cameraSites: VimpSite[] = [];
|
|
||||||
// 警报器站点列表 (按通道实际归属 siteCode 聚合)
|
|
||||||
const alarmSites: VimpSite[] = [];
|
|
||||||
// 已索引站点的去重集合 (siteCode)
|
|
||||||
const cameraIndexedSitesSet = new Set<string>();
|
|
||||||
const alarmIndexedSitesSet = new Set<string>();
|
|
||||||
// 已索引设备的去重集合 (channel.code): 防止同一通道被多个 siteFromApi 接口反复带回
|
|
||||||
const cameraIndexedCodesSet = new Set<string>();
|
|
||||||
const alarmIndexedCodesSet = new Set<string>();
|
|
||||||
// 站点 → 设备列表: 按通道前6位 siteCode 分组, 供 store 构建树
|
|
||||||
const siteCodeToCamerasMap = new Map<string, VimpChannel[]>();
|
|
||||||
const siteCodeToAlarmsMap = new Map<string, VimpChannel[]>();
|
|
||||||
|
|
||||||
for (const siteFromApi of sitesFromApi ?? []) {
|
|
||||||
const channels = await clientCatalogChannelApi(siteFromApi.code, { signal }).catch((error: Error) => {
|
|
||||||
if (isCancel(error) || error instanceof CancelledError) return;
|
|
||||||
console.error(error);
|
|
||||||
const errorFeedback = parseVimpErrorFeedback(error);
|
|
||||||
window.$message.error(errorFeedback);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
if (!channels) continue;
|
|
||||||
|
|
||||||
channels.forEach((channel) => {
|
|
||||||
const siteCode = channel.code.substring(0, 6);
|
|
||||||
|
|
||||||
// const siteCodeFromApi = siteFromApi.code.substring(0, 6);
|
|
||||||
// if (siteCode !== siteCodeFromApi) {
|
|
||||||
// console.log(`[Vimp:CodeLeak] 站点 ${siteCodeFromApi} 的接口返回了属于站点 ${siteCode} 的通道: ${channel.code}`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
const typeCode = Number(channel.code.substring(11, 14));
|
|
||||||
const isCamera = typeCode >= 4 && typeCode <= 6;
|
|
||||||
const isAlarm = (typeCode >= 101 && typeCode <= 108) || (typeCode >= 810 && typeCode <= 815);
|
|
||||||
if (isCamera) {
|
|
||||||
if (cameraIndexedCodesSet.has(channel.code)) return;
|
|
||||||
cameraIndexedCodesSet.add(channel.code);
|
|
||||||
if (!cameraIndexedSitesSet.has(siteCode)) {
|
|
||||||
cameraSites.push({
|
|
||||||
code: siteCode,
|
|
||||||
name: codeSites[siteCode]?.name ?? '',
|
|
||||||
online: siteFromApi.online,
|
|
||||||
});
|
|
||||||
cameraIndexedSitesSet.add(siteCode);
|
|
||||||
}
|
|
||||||
if (!siteCodeToCamerasMap.has(siteCode)) {
|
|
||||||
siteCodeToCamerasMap.set(siteCode, []);
|
|
||||||
}
|
|
||||||
siteCodeToCamerasMap.get(siteCode)!.push(channel);
|
|
||||||
} else if (isAlarm) {
|
|
||||||
if (alarmIndexedCodesSet.has(channel.code)) return;
|
|
||||||
alarmIndexedCodesSet.add(channel.code);
|
|
||||||
if (!alarmIndexedSitesSet.has(siteCode)) {
|
|
||||||
alarmSites.push({
|
|
||||||
code: siteCode,
|
|
||||||
name: codeSites[siteCode]?.name ?? '',
|
|
||||||
online: siteFromApi.online,
|
|
||||||
});
|
|
||||||
alarmIndexedSitesSet.add(siteCode);
|
|
||||||
}
|
|
||||||
if (!siteCodeToAlarmsMap.has(siteCode)) {
|
|
||||||
siteCodeToAlarmsMap.set(siteCode, []);
|
|
||||||
}
|
|
||||||
siteCodeToAlarmsMap.get(siteCode)!.push(channel);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 站点数组排序:稳定线路面板顺序和站点节点顺序
|
|
||||||
sortSitesByCode(cameraSites);
|
|
||||||
sortSitesByCode(alarmSites);
|
|
||||||
// 2. 每站通道数组排序:稳定区域节点顺序和通道节点顺序
|
|
||||||
sortChannelsMapByCode(siteCodeToCamerasMap);
|
|
||||||
sortChannelsMapByCode(siteCodeToAlarmsMap);
|
|
||||||
|
|
||||||
return {
|
|
||||||
cameraSites,
|
|
||||||
siteCodeToCamerasMap,
|
|
||||||
|
|
||||||
alarmSites,
|
|
||||||
siteCodeToAlarmsMap,
|
|
||||||
|
|
||||||
codeLines,
|
|
||||||
codeSites,
|
|
||||||
compiledCodeAreas,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './use-channels-sync';
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { watch } from 'vue';
|
|
||||||
import { useChannelsQuery } from '../query';
|
|
||||||
import { useAlarmStore, useCameraStore, useChannelsFalloutStore } from '../../stores';
|
|
||||||
|
|
||||||
export const useChannelsSync = () => {
|
|
||||||
const cameraStore = useCameraStore();
|
|
||||||
const alarmStore = useAlarmStore();
|
|
||||||
const falloutStore = useChannelsFalloutStore();
|
|
||||||
|
|
||||||
const { data } = useChannelsQuery();
|
|
||||||
|
|
||||||
watch(data, (newData) => {
|
|
||||||
if (!newData) return;
|
|
||||||
|
|
||||||
falloutStore.clear();
|
|
||||||
|
|
||||||
const {
|
|
||||||
cameraSites,
|
|
||||||
siteCodeToCamerasMap,
|
|
||||||
|
|
||||||
alarmSites,
|
|
||||||
siteCodeToAlarmsMap,
|
|
||||||
|
|
||||||
codeLines,
|
|
||||||
codeSites,
|
|
||||||
compiledCodeAreas,
|
|
||||||
} = newData;
|
|
||||||
|
|
||||||
cameraStore.buildLineTabPanes({
|
|
||||||
sites: cameraSites,
|
|
||||||
siteCodeToCamerasMap,
|
|
||||||
codeLines,
|
|
||||||
codeSites,
|
|
||||||
compiledCodeAreas,
|
|
||||||
});
|
|
||||||
cameraStore.buildCameraRecord(siteCodeToCamerasMap);
|
|
||||||
|
|
||||||
alarmStore.buildLineTabPanes({
|
|
||||||
sites: alarmSites,
|
|
||||||
siteCodeToAlarmsMap,
|
|
||||||
codeLines,
|
|
||||||
codeSites,
|
|
||||||
compiledCodeAreas,
|
|
||||||
});
|
|
||||||
alarmStore.buildAlarmRecord(siteCodeToAlarmsMap);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './layout';
|
|
||||||
export * from './query';
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
/** 线路 Tab 内容区宽度(px),与 NTabs tab-style 的 width 保持一致 */
|
|
||||||
export const LINE_TAB_CONTENT_WIDTH = 48;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const VIMP_CHANNELS_QUERY_KEY = 'vimp-channels';
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import type { VimpChannel, VimpSite } from '../apis';
|
|
||||||
import { shallowRef } from 'vue';
|
|
||||||
import type { AlarmMainAreaNodeOption, AlarmNodeOption, CodeLines, CodeSites, AlarmLineTabPane, AlarmSiteNodeOption, AlarmSubAreaNodeOption, CompiledCodeAreas } from '../types';
|
|
||||||
import { useChannelsFalloutStore } from './channels-fallout';
|
|
||||||
|
|
||||||
interface BuildLineTabPanesParams {
|
|
||||||
sites: VimpSite[];
|
|
||||||
siteCodeToAlarmsMap: Map<string, VimpChannel[]>;
|
|
||||||
codeLines: CodeLines;
|
|
||||||
codeSites: CodeSites;
|
|
||||||
compiledCodeAreas: CompiledCodeAreas;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildMainAreaNodeKey = (siteCode: string, mainAreaCode: string) => `${siteCode}${mainAreaCode}`;
|
|
||||||
const buildSubAreaNodeKey = (siteCode: string, areaCode: string) => `${siteCode}${areaCode}`;
|
|
||||||
|
|
||||||
export const useAlarmStore = defineStore('vimp-alarm-store', () => {
|
|
||||||
const lineTabPanes = shallowRef<AlarmLineTabPane[]>();
|
|
||||||
const alarmRecord = shallowRef<Record<string, VimpChannel>>({});
|
|
||||||
|
|
||||||
const buildLineTabPanes = (params: BuildLineTabPanesParams) => {
|
|
||||||
const { sites, siteCodeToAlarmsMap, codeLines, codeSites, compiledCodeAreas } = params;
|
|
||||||
|
|
||||||
const result: AlarmLineTabPane[] = [];
|
|
||||||
|
|
||||||
// 1. 线路索引 lineCode -> AlarmLineTabPane
|
|
||||||
const linePaneMap = new Map<string, AlarmLineTabPane>();
|
|
||||||
|
|
||||||
// 遍历所有站点
|
|
||||||
for (const site of sites) {
|
|
||||||
// 2. 站点节点 siteNode 只在当前轮次中顺序创建,不需要建立索引
|
|
||||||
|
|
||||||
const lineCode = site.code.substring(0, 3);
|
|
||||||
|
|
||||||
// 如果线路不在码表中,说明码表中没有配置该站点对应的线路,跳过该站点
|
|
||||||
if (!(lineCode in codeLines)) {
|
|
||||||
const alarms = siteCodeToAlarmsMap.get(site.code);
|
|
||||||
useChannelsFalloutStore().logs.push({
|
|
||||||
type: 'LineMiss',
|
|
||||||
deviceType: 'alarm',
|
|
||||||
siteCode: site.code,
|
|
||||||
lineCode,
|
|
||||||
channels: alarms ?? [],
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lineName = codeLines[lineCode]?.name ?? '';
|
|
||||||
|
|
||||||
let linePane = linePaneMap.get(lineCode);
|
|
||||||
if (!linePane) {
|
|
||||||
linePane = { lineCode, lineName, alarmTree: [] };
|
|
||||||
linePaneMap.set(lineCode, linePane);
|
|
||||||
result.push(linePane);
|
|
||||||
}
|
|
||||||
|
|
||||||
const siteCode = site.code;
|
|
||||||
const siteMeta = codeSites[siteCode];
|
|
||||||
if (!siteMeta) continue;
|
|
||||||
const siteName = siteMeta.name;
|
|
||||||
const siteType = siteMeta.type;
|
|
||||||
|
|
||||||
const compiledCodeAreaMaps = compiledCodeAreas[siteType];
|
|
||||||
const mainAreaCodeLength = siteType === 'train' ? 3 : 2;
|
|
||||||
|
|
||||||
// 构造站点节点
|
|
||||||
const siteNode: AlarmSiteNodeOption = {
|
|
||||||
key: siteCode,
|
|
||||||
label: siteName,
|
|
||||||
children: [],
|
|
||||||
stats: { online: 0, offline: 0, total: 0 },
|
|
||||||
online: site.online,
|
|
||||||
};
|
|
||||||
linePane.alarmTree.push(siteNode);
|
|
||||||
|
|
||||||
// 获取所有警报器
|
|
||||||
const alarms = siteCodeToAlarmsMap.get(siteCode);
|
|
||||||
if (!alarms) continue;
|
|
||||||
|
|
||||||
// 3. 1级区域索引 mainAreaNodeKey -> AlarmMainAreaNodeOption
|
|
||||||
// mainAreaNodeKey = ${siteCode}${alarmMainAreaCode}
|
|
||||||
const mainAreaNodeMap = new Map<string, AlarmMainAreaNodeOption>();
|
|
||||||
// 4. 2级区域索引 subAreaNodeKey -> AlarmSubAreaNodeOption
|
|
||||||
// subAreaNodeKey = ${siteCode}${alarmAreaCode}
|
|
||||||
const subAreaNodeMap = new Map<string, AlarmSubAreaNodeOption>();
|
|
||||||
// 5. 警报器索引 subAreaNodeKey -> Set<AlarmGbCode>
|
|
||||||
const subAreaNodeKeyToAlarmGbCodeSetMap = new Map<string, Set<string>>();
|
|
||||||
|
|
||||||
// 遍历警报器
|
|
||||||
for (const alarm of alarms) {
|
|
||||||
// 计算相关编码
|
|
||||||
const { code: alarmGbCode, name: alarmName } = alarm;
|
|
||||||
const alarmAreaCode = alarmGbCode.substring(6, 11);
|
|
||||||
const alarmMainAreaCode = alarmAreaCode.slice(0, mainAreaCodeLength);
|
|
||||||
|
|
||||||
// 查找1级区域,如果未找到则跳过该警报器
|
|
||||||
const mainArea = compiledCodeAreaMaps.mainAreaMap.get(alarmMainAreaCode);
|
|
||||||
if (!mainArea) {
|
|
||||||
useChannelsFalloutStore().logs.push({
|
|
||||||
type: 'AreaMiss',
|
|
||||||
deviceType: 'alarm',
|
|
||||||
siteCode,
|
|
||||||
deviceCode: alarmGbCode,
|
|
||||||
deviceName: alarmName,
|
|
||||||
areaLevel: 1,
|
|
||||||
areaCode: alarmMainAreaCode,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试从索引中获取1级区域节点,若不存在则创建
|
|
||||||
const mainAreaNodeKey = buildMainAreaNodeKey(siteCode, alarmMainAreaCode);
|
|
||||||
let mainAreaNode = mainAreaNodeMap.get(mainAreaNodeKey);
|
|
||||||
if (!mainAreaNode) {
|
|
||||||
mainAreaNode = {
|
|
||||||
key: mainAreaNodeKey,
|
|
||||||
label: mainArea.name,
|
|
||||||
children: [],
|
|
||||||
stats: { online: 0, offline: 0, total: 0 },
|
|
||||||
site: site,
|
|
||||||
areaLevel: 1,
|
|
||||||
};
|
|
||||||
mainAreaNodeMap.set(mainAreaNodeKey, mainAreaNode);
|
|
||||||
siteNode.children?.push(mainAreaNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找2级区域,如果未找到则跳过该警报器
|
|
||||||
const subArea = compiledCodeAreaMaps.subAreaMap.get(alarmAreaCode);
|
|
||||||
if (!subArea) {
|
|
||||||
useChannelsFalloutStore().logs.push({
|
|
||||||
type: 'AreaMiss',
|
|
||||||
deviceType: 'alarm',
|
|
||||||
siteCode,
|
|
||||||
deviceCode: alarmGbCode,
|
|
||||||
deviceName: alarmName,
|
|
||||||
areaLevel: 2,
|
|
||||||
areaCode: alarmAreaCode,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试从索引中获取2级区域节点,若不存在则创建
|
|
||||||
const subAreaNodeKey = buildSubAreaNodeKey(siteCode, alarmAreaCode);
|
|
||||||
let subAreaNode = subAreaNodeMap.get(subAreaNodeKey);
|
|
||||||
if (!subAreaNode) {
|
|
||||||
subAreaNode = {
|
|
||||||
key: subAreaNodeKey,
|
|
||||||
label: subArea.name,
|
|
||||||
children: [],
|
|
||||||
stats: { online: 0, offline: 0, total: 0 },
|
|
||||||
site: site,
|
|
||||||
areaLevel: 2,
|
|
||||||
};
|
|
||||||
subAreaNodeMap.set(subAreaNodeKey, subAreaNode);
|
|
||||||
mainAreaNode.children?.push(subAreaNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构造警报器节点
|
|
||||||
let alarmGbCodeSet = subAreaNodeKeyToAlarmGbCodeSetMap.get(subAreaNodeKey);
|
|
||||||
if (!alarmGbCodeSet) {
|
|
||||||
alarmGbCodeSet = new Set<string>();
|
|
||||||
subAreaNodeKeyToAlarmGbCodeSetMap.set(subAreaNodeKey, alarmGbCodeSet);
|
|
||||||
}
|
|
||||||
if (alarmGbCodeSet.has(alarmGbCode)) {
|
|
||||||
useChannelsFalloutStore().logs.push({
|
|
||||||
type: 'DupCode',
|
|
||||||
deviceType: 'alarm',
|
|
||||||
siteCode,
|
|
||||||
deviceCode: alarmGbCode,
|
|
||||||
deviceName: alarmName,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
alarmGbCodeSet.add(alarmGbCode);
|
|
||||||
const alarmType = alarm.code.substring(11, 14);
|
|
||||||
const alarmNode: AlarmNodeOption = {
|
|
||||||
key: alarmGbCode,
|
|
||||||
label: alarmName,
|
|
||||||
type: alarmType,
|
|
||||||
alarm: alarm,
|
|
||||||
site: site,
|
|
||||||
};
|
|
||||||
subAreaNode.children?.push(alarmNode);
|
|
||||||
|
|
||||||
// 统计站点、区域、子区域的在线/离线/总警报器数量
|
|
||||||
siteNode.stats.total++;
|
|
||||||
mainAreaNode.stats.total++;
|
|
||||||
subAreaNode.stats.total++;
|
|
||||||
if (alarm.status === 1) {
|
|
||||||
siteNode.stats.online++;
|
|
||||||
mainAreaNode.stats.online++;
|
|
||||||
subAreaNode.stats.online++;
|
|
||||||
} else if (alarm.status === 0) {
|
|
||||||
siteNode.stats.offline++;
|
|
||||||
mainAreaNode.stats.offline++;
|
|
||||||
subAreaNode.stats.offline++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lineTabPanes.value = result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildAlarmRecord = (siteCodeToAlarmsMap: Map<string, VimpChannel[]>) => {
|
|
||||||
const record: Record<string, VimpChannel> = {};
|
|
||||||
for (const [, alarms] of siteCodeToAlarmsMap) {
|
|
||||||
for (const alarm of alarms) {
|
|
||||||
record[alarm.code] = alarm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
alarmRecord.value = record;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
lineTabPanes,
|
|
||||||
alarmRecord,
|
|
||||||
|
|
||||||
buildLineTabPanes,
|
|
||||||
buildAlarmRecord,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import type { VimpChannel, VimpSite } from '../apis';
|
|
||||||
import { shallowRef } from 'vue';
|
|
||||||
import type { CameraMainAreaNodeOption, CameraNodeOption, CodeLines, CodeSites, CameraLineTabPane, CameraSiteNodeOption, CameraSubAreaNodeOption, CompiledCodeAreas } from '../types';
|
|
||||||
import { useChannelsFalloutStore } from './channels-fallout';
|
|
||||||
|
|
||||||
interface BuildLineTabPanesParams {
|
|
||||||
sites: VimpSite[];
|
|
||||||
siteCodeToCamerasMap: Map<string, VimpChannel[]>;
|
|
||||||
codeLines: CodeLines;
|
|
||||||
codeSites: CodeSites;
|
|
||||||
compiledCodeAreas: CompiledCodeAreas;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildMainAreaNodeKey = (siteCode: string, mainAreaCode: string) => `${siteCode}${mainAreaCode}`;
|
|
||||||
const buildSubAreaNodeKey = (siteCode: string, areaCode: string) => `${siteCode}${areaCode}`;
|
|
||||||
|
|
||||||
export const useCameraStore = defineStore('vimp-camera-store', () => {
|
|
||||||
const lineTabPanes = shallowRef<CameraLineTabPane[]>();
|
|
||||||
const cameraRecord = shallowRef<Record<string, VimpChannel>>({});
|
|
||||||
|
|
||||||
const buildLineTabPanes = (params: BuildLineTabPanesParams) => {
|
|
||||||
const { sites, siteCodeToCamerasMap, codeLines, codeSites, compiledCodeAreas } = params;
|
|
||||||
|
|
||||||
const result: CameraLineTabPane[] = [];
|
|
||||||
|
|
||||||
// 1. 线路索引 lineCode -> CameraLineTabPane
|
|
||||||
const linePaneMap = new Map<string, CameraLineTabPane>();
|
|
||||||
|
|
||||||
// 遍历所有站点
|
|
||||||
for (const site of sites) {
|
|
||||||
// 2. 站点节点 siteNode 只在当前轮次中顺序创建,不需要建立索引
|
|
||||||
|
|
||||||
const lineCode = site.code.substring(0, 3);
|
|
||||||
|
|
||||||
// 如果线路不在码表中,说明码表中没有配置该站点对应的线路,跳过该站点
|
|
||||||
if (!(lineCode in codeLines)) {
|
|
||||||
const channels = siteCodeToCamerasMap.get(site.code);
|
|
||||||
useChannelsFalloutStore().logs.push({
|
|
||||||
type: 'LineMiss',
|
|
||||||
deviceType: 'camera',
|
|
||||||
siteCode: site.code,
|
|
||||||
lineCode,
|
|
||||||
channels: channels ?? [],
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lineName = codeLines[lineCode]?.name ?? '';
|
|
||||||
|
|
||||||
let linePane = linePaneMap.get(lineCode);
|
|
||||||
if (!linePane) {
|
|
||||||
linePane = { lineCode, lineName, cameraTree: [] };
|
|
||||||
linePaneMap.set(lineCode, linePane);
|
|
||||||
result.push(linePane);
|
|
||||||
}
|
|
||||||
|
|
||||||
const siteCode = site.code;
|
|
||||||
const siteMeta = codeSites[siteCode];
|
|
||||||
if (!siteMeta) continue;
|
|
||||||
const siteName = siteMeta.name;
|
|
||||||
const siteType = siteMeta.type;
|
|
||||||
|
|
||||||
const compiledCodeAreaMaps = compiledCodeAreas[siteType];
|
|
||||||
const mainAreaCodeLength = siteType === 'train' ? 3 : 2;
|
|
||||||
|
|
||||||
// 构造站点节点
|
|
||||||
const siteNode: CameraSiteNodeOption = {
|
|
||||||
key: siteCode,
|
|
||||||
label: siteName,
|
|
||||||
children: [],
|
|
||||||
stats: { online: 0, offline: 0, total: 0 },
|
|
||||||
online: site.online,
|
|
||||||
};
|
|
||||||
linePane.cameraTree.push(siteNode);
|
|
||||||
|
|
||||||
// 获取所有摄像机
|
|
||||||
const cameras = siteCodeToCamerasMap.get(siteCode);
|
|
||||||
if (!cameras) continue;
|
|
||||||
|
|
||||||
// 3. 1级区域索引 mainAreaNodeKey -> CameraMainAreaNodeOption
|
|
||||||
// mainAreaNodeKey = ${siteCode}${cameraMainAreaCode}
|
|
||||||
const mainAreaNodeMap = new Map<string, CameraMainAreaNodeOption>();
|
|
||||||
// 4. 2级区域索引 subAreaNodeKey -> CameraSubAreaNodeOption
|
|
||||||
// subAreaNodeKey = ${siteCode}${cameraAreaCode}
|
|
||||||
const subAreaNodeMap = new Map<string, CameraSubAreaNodeOption>();
|
|
||||||
// 5. 摄像机索引 subAreaNodeKey -> Set<CameraGbCode>
|
|
||||||
const subAreaNodeKeyToCameraGbCodeSetMap = new Map<string, Set<string>>();
|
|
||||||
|
|
||||||
// 遍历摄像机
|
|
||||||
for (const camera of cameras) {
|
|
||||||
// 计算相关编码
|
|
||||||
const { code: cameraGbCode, name: cameraName } = camera;
|
|
||||||
const cameraAreaCode = cameraGbCode.substring(6, 11);
|
|
||||||
const cameraMainAreaCode = cameraAreaCode.slice(0, mainAreaCodeLength);
|
|
||||||
|
|
||||||
// 查找1级区域,如果未找到则跳过该摄像机
|
|
||||||
const mainArea = compiledCodeAreaMaps.mainAreaMap.get(cameraMainAreaCode);
|
|
||||||
if (!mainArea) {
|
|
||||||
useChannelsFalloutStore().logs.push({
|
|
||||||
type: 'AreaMiss',
|
|
||||||
deviceType: 'camera',
|
|
||||||
siteCode,
|
|
||||||
deviceCode: cameraGbCode,
|
|
||||||
deviceName: cameraName,
|
|
||||||
areaLevel: 1,
|
|
||||||
areaCode: cameraMainAreaCode,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 尝试从索引中获取1级区域节点,若不存在则创建
|
|
||||||
const mainAreaNodeKey = buildMainAreaNodeKey(siteCode, cameraMainAreaCode);
|
|
||||||
let mainAreaNode = mainAreaNodeMap.get(mainAreaNodeKey);
|
|
||||||
if (!mainAreaNode) {
|
|
||||||
mainAreaNode = {
|
|
||||||
key: mainAreaNodeKey,
|
|
||||||
label: mainArea.name,
|
|
||||||
children: [],
|
|
||||||
stats: { online: 0, offline: 0, total: 0 },
|
|
||||||
site: site,
|
|
||||||
areaLevel: 1,
|
|
||||||
};
|
|
||||||
mainAreaNodeMap.set(mainAreaNodeKey, mainAreaNode);
|
|
||||||
siteNode.children?.push(mainAreaNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找2级区域,如果未找到则跳过该摄像机
|
|
||||||
const subArea = compiledCodeAreaMaps.subAreaMap.get(cameraAreaCode);
|
|
||||||
if (!subArea) {
|
|
||||||
useChannelsFalloutStore().logs.push({
|
|
||||||
type: 'AreaMiss',
|
|
||||||
deviceType: 'camera',
|
|
||||||
siteCode,
|
|
||||||
deviceCode: cameraGbCode,
|
|
||||||
deviceName: cameraName,
|
|
||||||
areaLevel: 2,
|
|
||||||
areaCode: cameraAreaCode,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// 尝试从索引中获取2级区域节点,若不存在则创建
|
|
||||||
const subAreaNodeKey = buildSubAreaNodeKey(siteCode, cameraAreaCode);
|
|
||||||
let subAreaNode = subAreaNodeMap.get(subAreaNodeKey);
|
|
||||||
if (!subAreaNode) {
|
|
||||||
subAreaNode = {
|
|
||||||
key: subAreaNodeKey,
|
|
||||||
label: subArea.name,
|
|
||||||
children: [],
|
|
||||||
stats: { online: 0, offline: 0, total: 0 },
|
|
||||||
site: site,
|
|
||||||
areaLevel: 2,
|
|
||||||
};
|
|
||||||
subAreaNodeMap.set(subAreaNodeKey, subAreaNode);
|
|
||||||
mainAreaNode.children?.push(subAreaNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构造摄像机节点
|
|
||||||
let cameraGbCodeSet = subAreaNodeKeyToCameraGbCodeSetMap.get(subAreaNodeKey);
|
|
||||||
if (!cameraGbCodeSet) {
|
|
||||||
cameraGbCodeSet = new Set<string>();
|
|
||||||
subAreaNodeKeyToCameraGbCodeSetMap.set(subAreaNodeKey, cameraGbCodeSet);
|
|
||||||
}
|
|
||||||
if (cameraGbCodeSet.has(cameraGbCode)) {
|
|
||||||
useChannelsFalloutStore().logs.push({
|
|
||||||
type: 'DupCode',
|
|
||||||
deviceType: 'camera',
|
|
||||||
siteCode,
|
|
||||||
deviceCode: cameraGbCode,
|
|
||||||
deviceName: cameraName,
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
cameraGbCodeSet.add(cameraGbCode);
|
|
||||||
const cameraType = cameraGbCode.substring(11, 14);
|
|
||||||
const cameraNode: CameraNodeOption = {
|
|
||||||
key: cameraGbCode,
|
|
||||||
label: cameraName,
|
|
||||||
type: cameraType,
|
|
||||||
camera: camera,
|
|
||||||
site: site,
|
|
||||||
};
|
|
||||||
subAreaNode.children?.push(cameraNode);
|
|
||||||
|
|
||||||
// 统计站点、区域、子区域的在线/离线/总摄像机数量
|
|
||||||
siteNode.stats.total++;
|
|
||||||
mainAreaNode.stats.total++;
|
|
||||||
subAreaNode.stats.total++;
|
|
||||||
if (camera.status === 1) {
|
|
||||||
siteNode.stats.online++;
|
|
||||||
mainAreaNode.stats.online++;
|
|
||||||
subAreaNode.stats.online++;
|
|
||||||
} else if (camera.status === 0) {
|
|
||||||
siteNode.stats.offline++;
|
|
||||||
mainAreaNode.stats.offline++;
|
|
||||||
subAreaNode.stats.offline++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lineTabPanes.value = result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildCameraRecord = (siteCodeToCamerasMap: Map<string, VimpChannel[]>) => {
|
|
||||||
const record: Record<string, VimpChannel> = {};
|
|
||||||
for (const [, cameras] of siteCodeToCamerasMap) {
|
|
||||||
for (const camera of cameras) {
|
|
||||||
record[camera.code] = camera;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cameraRecord.value = record;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
lineTabPanes,
|
|
||||||
cameraRecord,
|
|
||||||
|
|
||||||
buildLineTabPanes,
|
|
||||||
buildCameraRecord,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import type { VimpChannel } from '../apis';
|
|
||||||
|
|
||||||
export interface LineMissLog {
|
|
||||||
type: 'LineMiss';
|
|
||||||
deviceType: 'camera' | 'alarm';
|
|
||||||
siteCode: string;
|
|
||||||
lineCode: string;
|
|
||||||
channels: VimpChannel[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AreaMissLog {
|
|
||||||
type: 'AreaMiss';
|
|
||||||
deviceType: 'camera' | 'alarm';
|
|
||||||
siteCode: string;
|
|
||||||
deviceCode: string;
|
|
||||||
deviceName: string;
|
|
||||||
areaLevel: 1 | 2;
|
|
||||||
areaCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DupCodeLog {
|
|
||||||
type: 'DupCode';
|
|
||||||
deviceType: 'camera' | 'alarm';
|
|
||||||
siteCode: string;
|
|
||||||
deviceCode: string;
|
|
||||||
deviceName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type FalloutLog = LineMissLog | AreaMissLog | DupCodeLog;
|
|
||||||
|
|
||||||
export const useChannelsFalloutStore = defineStore('vimp-channels-fallout-store', () => {
|
|
||||||
const logs = ref<FalloutLog[]>([]);
|
|
||||||
|
|
||||||
const clear = () => {
|
|
||||||
logs.value = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
logs,
|
|
||||||
clear,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
export const useConfigPanelStore = defineStore('vimp-config-panel-store', () => {
|
|
||||||
const collapsed = ref<boolean>(false);
|
|
||||||
|
|
||||||
const toggleCollapsed = () => {
|
|
||||||
collapsed.value = !collapsed.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
collapsed,
|
|
||||||
toggleCollapsed,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export * from './alarm';
|
|
||||||
export * from './camera';
|
|
||||||
export * from './channels-fallout';
|
|
||||||
export * from './config-panel';
|
|
||||||
export * from './query-config';
|
|
||||||
export * from './resource-panel';
|
|
||||||
export * from './screen';
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
export const useQueryConfigStore = defineStore('vimp-query-config-store', () => {
|
|
||||||
const refetchInterval = ref<number>(10); // 轮询间隔,单位:秒
|
|
||||||
|
|
||||||
return {
|
|
||||||
refetchInterval,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
export const useResourcePanelStore = defineStore('vimp-resource-panel-store', () => {
|
|
||||||
const collapsed = ref<boolean>(false);
|
|
||||||
const searchPattern = ref<string>('');
|
|
||||||
|
|
||||||
const toggleCollapsed = () => {
|
|
||||||
collapsed.value = !collapsed.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
collapsed,
|
|
||||||
searchPattern,
|
|
||||||
|
|
||||||
toggleCollapsed,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { defineStore } from 'pinia'
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
export interface Screen {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
let counter = 4
|
|
||||||
|
|
||||||
const genId = (): string => `screen-${crypto.randomUUID()}`
|
|
||||||
|
|
||||||
const defaultName = (n: number): string => `屏幕 ${n}`
|
|
||||||
|
|
||||||
export const useScreenStore = defineStore('vimp-screen', () => {
|
|
||||||
const screens = ref<Screen[]>([
|
|
||||||
{ id: 'screen-1', name: '屏幕 1' },
|
|
||||||
{ id: 'screen-2', name: '屏幕 2' },
|
|
||||||
{ id: 'screen-3', name: '屏幕 3' },
|
|
||||||
{ id: 'screen-4', name: '屏幕 4' },
|
|
||||||
])
|
|
||||||
|
|
||||||
const activeScreenId = ref<string>(screens.value[0]?.id ?? '')
|
|
||||||
|
|
||||||
const addScreen = () => {
|
|
||||||
const id = genId()
|
|
||||||
screens.value.push({ id, name: defaultName(screens.value.length + 1) })
|
|
||||||
activeScreenId.value = id
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeScreen = (id: string) => {
|
|
||||||
if (screens.value.length <= 1) return
|
|
||||||
const index = screens.value.findIndex(s => s.id === id)
|
|
||||||
if (index === -1) return
|
|
||||||
screens.value.splice(index, 1)
|
|
||||||
if (activeScreenId.value === id) {
|
|
||||||
const fallback = screens.value[index] ?? screens.value[index - 1]
|
|
||||||
if (fallback) activeScreenId.value = fallback.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const renameScreen = (id: string, name: string) => {
|
|
||||||
const target = screens.value.find(s => s.id === id)
|
|
||||||
if (!target) return
|
|
||||||
const trimmed = name.trim()
|
|
||||||
if (trimmed.length === 0) return
|
|
||||||
if (trimmed === target.name) return
|
|
||||||
target.name = trimmed
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
screens,
|
|
||||||
activeScreenId,
|
|
||||||
addScreen,
|
|
||||||
removeScreen,
|
|
||||||
renameScreen,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import type { AxiosError } from 'axios';
|
|
||||||
|
|
||||||
export interface VimpResult<T = unknown> {
|
|
||||||
code: number;
|
|
||||||
data: T;
|
|
||||||
msg: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type VimpResponse<T> = [err: AxiosError | null, data: T | null, resp: VimpResult<T> | null];
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
import type { TabPaneProps, TreeOption } from 'naive-ui';
|
|
||||||
import type { VimpChannel, VimpSite } from '../apis/model';
|
|
||||||
|
|
||||||
export type SiteType = 'station' | 'parking' | 'occ' | 'train';
|
|
||||||
export type CodeLines = Record<string, { name: string; color: string }>;
|
|
||||||
export type CodeSites = Record<string, { name: string; type: SiteType }>;
|
|
||||||
export type CodeArea = { code: string; name: string; subs: { code: string; name: string }[] };
|
|
||||||
|
|
||||||
export type CompiledCodeAreaMaps = {
|
|
||||||
mainAreaMap: Map<string, CodeArea>;
|
|
||||||
subAreaMap: Map<string, CodeArea['subs'][number]>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CompiledCodeAreas = Record<SiteType, CompiledCodeAreaMaps>;
|
|
||||||
|
|
||||||
interface CompileCodeAreasParams {
|
|
||||||
codeStationAreas: CodeArea[];
|
|
||||||
codeParkingAreas: CodeArea[];
|
|
||||||
codeOccAreas: CodeArea[];
|
|
||||||
codeTrainAreas: CodeArea[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const compileCodeAreaMaps = (areas: CodeArea[]): CompiledCodeAreaMaps => {
|
|
||||||
const mainAreaMap = new Map<string, CodeArea>();
|
|
||||||
const subAreaMap = new Map<string, CodeArea['subs'][number]>();
|
|
||||||
for (const area of areas) {
|
|
||||||
mainAreaMap.set(area.code, area);
|
|
||||||
for (const subArea of area.subs) {
|
|
||||||
subAreaMap.set(subArea.code, subArea);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
mainAreaMap,
|
|
||||||
subAreaMap,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const compileCodeAreas = (parmas: CompileCodeAreasParams): CompiledCodeAreas => {
|
|
||||||
const { codeStationAreas, codeParkingAreas, codeOccAreas, codeTrainAreas } = parmas;
|
|
||||||
return {
|
|
||||||
station: compileCodeAreaMaps(codeStationAreas),
|
|
||||||
parking: compileCodeAreaMaps(codeParkingAreas),
|
|
||||||
occ: compileCodeAreaMaps(codeOccAreas),
|
|
||||||
train: compileCodeAreaMaps(codeTrainAreas),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface CountStats {
|
|
||||||
online: number;
|
|
||||||
offline: number;
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 摄像机树相关类型
|
|
||||||
// ==========================================
|
|
||||||
export interface CameraNodeOption extends TreeOption {
|
|
||||||
camera: VimpChannel;
|
|
||||||
type: string;
|
|
||||||
site: VimpSite;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CameraSubAreaNodeOption extends TreeOption {
|
|
||||||
children?: CameraNodeOption[];
|
|
||||||
stats: CountStats;
|
|
||||||
site: VimpSite;
|
|
||||||
areaLevel: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CameraMainAreaNodeOption extends TreeOption {
|
|
||||||
children?: CameraSubAreaNodeOption[];
|
|
||||||
stats: CountStats;
|
|
||||||
site: VimpSite;
|
|
||||||
areaLevel: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CameraSiteNodeOption extends TreeOption {
|
|
||||||
children?: CameraMainAreaNodeOption[];
|
|
||||||
stats: CountStats;
|
|
||||||
online: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCameraSiteNode(option: TreeOption): option is CameraSiteNodeOption {
|
|
||||||
return 'online' in option && !('camera' in option);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCameraAreaNode(option: TreeOption): option is CameraMainAreaNodeOption | CameraSubAreaNodeOption {
|
|
||||||
return 'site' in option && !('camera' in option) && !('online' in option);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCameraNode(option: TreeOption): option is CameraNodeOption {
|
|
||||||
return 'camera' in option && 'site' in option;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CameraLineTabPane extends TabPaneProps {
|
|
||||||
lineCode: string;
|
|
||||||
lineName: string;
|
|
||||||
cameraTree: CameraSiteNodeOption[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// 警报器树相关类型
|
|
||||||
// ==========================================
|
|
||||||
export interface AlarmNodeOption extends TreeOption {
|
|
||||||
alarm: VimpChannel;
|
|
||||||
type: string;
|
|
||||||
site: VimpSite;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AlarmSubAreaNodeOption extends TreeOption {
|
|
||||||
children?: AlarmNodeOption[];
|
|
||||||
stats: CountStats;
|
|
||||||
site: VimpSite;
|
|
||||||
areaLevel: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AlarmMainAreaNodeOption extends TreeOption {
|
|
||||||
children?: AlarmSubAreaNodeOption[];
|
|
||||||
stats: CountStats;
|
|
||||||
site: VimpSite;
|
|
||||||
areaLevel: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AlarmSiteNodeOption extends TreeOption {
|
|
||||||
children?: AlarmMainAreaNodeOption[];
|
|
||||||
stats: CountStats;
|
|
||||||
online: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isAlarmSiteNode(option: TreeOption): option is AlarmSiteNodeOption {
|
|
||||||
return 'online' in option && !('alarm' in option);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isAlarmAreaNode(option: TreeOption): option is AlarmMainAreaNodeOption | AlarmSubAreaNodeOption {
|
|
||||||
return 'site' in option && !('alarm' in option) && !('online' in option);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isAlarmNode(option: TreeOption): option is AlarmNodeOption {
|
|
||||||
return 'alarm' in option && 'site' in option;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AlarmLineTabPane extends TabPaneProps {
|
|
||||||
lineCode: string;
|
|
||||||
lineName: string;
|
|
||||||
alarmTree: AlarmSiteNodeOption[];
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './axios';
|
|
||||||
export * from './device-tree';
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, type ComponentInstance } from 'vue';
|
|
||||||
import { useEventListener } from '@vueuse/core';
|
|
||||||
import ResourcePanel from './components/resource-panel.vue';
|
|
||||||
import ConfigPanel from './components/config-panel.vue';
|
|
||||||
import ScreenPanel from './components/screen-panel.vue';
|
|
||||||
import ChannelsFalloutModal from './components/channels-fallout-modal.vue';
|
|
||||||
import { useChannelsSync } from './composables';
|
|
||||||
|
|
||||||
useChannelsSync();
|
|
||||||
|
|
||||||
const falloutModalRef = ref<ComponentInstance<typeof ChannelsFalloutModal>>();
|
|
||||||
|
|
||||||
useEventListener('keydown', (e: KeyboardEvent) => {
|
|
||||||
if (e.ctrlKey && e.altKey && e.key === 'v') {
|
|
||||||
e.preventDefault();
|
|
||||||
falloutModalRef.value?.open();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div style="height: 100%; display: flex; overflow: hidden">
|
|
||||||
<ResourcePanel />
|
|
||||||
<ScreenPanel />
|
|
||||||
<ConfigPanel />
|
|
||||||
</div>
|
|
||||||
<ChannelsFalloutModal ref="falloutModalRef" />
|
|
||||||
</template>
|
|
||||||
@@ -60,10 +60,6 @@ const router = createRouter({
|
|||||||
path: 'changelog',
|
path: 'changelog',
|
||||||
component: () => import('@/pages/system/changelog/changelog-page.vue'),
|
component: () => import('@/pages/system/changelog/changelog-page.vue'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'vimp',
|
|
||||||
component: () => import('@/pages/vimp/vimp-page.vue'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
component: () => import('@/pages/system/error/not-found-page.vue'),
|
component: () => import('@/pages/system/error/not-found-page.vue'),
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const useSettingStore = defineStore(
|
|||||||
showDeviceRawData.value = false;
|
showDeviceRawData.value = false;
|
||||||
pollingStations.value = true;
|
pollingStations.value = true;
|
||||||
activeRequests.value = true;
|
activeRequests.value = true;
|
||||||
subscribeMessages.value = true;
|
subscribeMessages.value = false;
|
||||||
mockUser.value = false;
|
mockUser.value = false;
|
||||||
useLocalDB.value = false;
|
useLocalDB.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-25
@@ -4,8 +4,6 @@ import { fileURLToPath, URL } from 'node:url';
|
|||||||
import { defineConfig, ProxyOptions } from 'vite';
|
import { defineConfig, ProxyOptions } from 'vite';
|
||||||
import vueDevTools from 'vite-plugin-vue-devtools';
|
import vueDevTools from 'vite-plugin-vue-devtools';
|
||||||
|
|
||||||
const SERVER_PORT = 9763;
|
|
||||||
|
|
||||||
type ProxyItem = {
|
type ProxyItem = {
|
||||||
key: string;
|
key: string;
|
||||||
target: string;
|
target: string;
|
||||||
@@ -177,12 +175,6 @@ const line10ApiProxyList: ProxyItem[] = [
|
|||||||
{ key: '/1032/api', target: 'http://10.18.244.10:18760', rewrite: ['/1032/api', '/api'] },
|
{ key: '/1032/api', target: 'http://10.18.244.10:18760', rewrite: ['/1032/api', '/api'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
const line21YangpuTestProxyList: ProxyItem[] = [
|
|
||||||
{ key: '/2175/api', target: 'http://10.24.0.10:18760', rewrite: ['/2175/api', '/api'] },
|
|
||||||
{ key: '/2109/api', target: 'http://10.24.17.10:18760', rewrite: ['/2109/api', '/api'] },
|
|
||||||
{ key: '/2180/api', target: 'http://10.24.116.10:18760', rewrite: ['/2180/api', '/api'] },
|
|
||||||
];
|
|
||||||
|
|
||||||
const apiProxyList: ProxyItem[] = [
|
const apiProxyList: ProxyItem[] = [
|
||||||
// { key: '/minio', target: 'http://10.14.0.10:9000', rewrite: ['/minio', ''] },
|
// { key: '/minio', target: 'http://10.14.0.10:9000', rewrite: ['/minio', ''] },
|
||||||
// { key: '/api', target: 'http://10.14.0.10:18760' },
|
// { key: '/api', target: 'http://10.14.0.10:18760' },
|
||||||
@@ -200,24 +192,9 @@ const apiProxyList: ProxyItem[] = [
|
|||||||
...line04ApiProxyList,
|
...line04ApiProxyList,
|
||||||
|
|
||||||
{ key: '/minio', target: 'http://10.18.128.10:9000', rewrite: ['/minio', ''] },
|
{ key: '/minio', target: 'http://10.18.128.10:9000', rewrite: ['/minio', ''] },
|
||||||
{ key: '/api', target: 'http://10.18.128.10:18760' }, // 吴中路控制中心
|
{ key: '/api', target: 'http://10.18.128.10:18760' },
|
||||||
// { key: '/api', target: 'http://10.18.187.10:18760' }, // 紫藤路
|
|
||||||
{ key: '/ws', target: 'ws://10.18.128.10:18103', ws: true },
|
{ key: '/ws', target: 'ws://10.18.128.10:18103', ws: true },
|
||||||
...line10ApiProxyList,
|
...line10ApiProxyList,
|
||||||
|
|
||||||
// 杨浦厂验环境
|
|
||||||
// { key: '/minio', target: 'http://10.24.0.10:9000', rewrite: ['/minio', ''] },
|
|
||||||
// { key: '/api', target: 'http://10.24.0.10:18760' },
|
|
||||||
// { key: '/ws', target: 'ws://10.24.0.10:18103', ws: true },
|
|
||||||
...line21YangpuTestProxyList,
|
|
||||||
|
|
||||||
// 设备树测试用代理配置
|
|
||||||
// { key: '/vimp/api', target: 'http://10.14.0.10:18080', rewrite: ['/vimp/api', '/api'] },
|
|
||||||
// { key: '/vimp/api', target: 'http://10.18.128.6:18080', rewrite: ['/vimp/api', '/api'] },
|
|
||||||
{ key: '/vimp/api', target: 'http://localhost:4000', rewrite: ['/vimp/api', '/api'] },
|
|
||||||
// { key: '/vimp/api', target: 'http://10.24.17.6:18080', rewrite: ['/vimp/api', '/api'] },
|
|
||||||
// { key: '/vimp/api', target: 'http://10.18.128.6:18080', rewrite: ['/vimp/api', '/api'] },
|
|
||||||
{ key: '/cdn', target: `http://localhost:${SERVER_PORT}`, rewrite: ['/cdn', ''] },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
@@ -243,7 +220,7 @@ export default defineConfig((/* { command, mode } */) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: SERVER_PORT,
|
port: 9763,
|
||||||
proxy: viteProxy,
|
proxy: viteProxy,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user