Compare commits
10 Commits
82789c78a9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db831e82ff | ||
|
|
6bf205f461 | ||
|
|
cf3d19d89d | ||
|
|
b020226538 | ||
|
|
b79b1df57e | ||
|
|
9b21beed0f | ||
|
|
aa4684273b | ||
|
|
36e839142a | ||
|
|
03006a8f06 | ||
|
|
0af52c62ce |
143
README.md
143
README.md
@@ -39,3 +39,146 @@ pnpm build
|
||||
```
|
||||
|
||||
在执行 `pnpm build` 之前,你可以在 `package.json` 中修改 `version` 字段,将其设置为你期望的版本号,构建完成后,项目的根目录中除了 `dist` 目录外,还会生成三个压缩包,文件名的格式统一为 `ndm-web-platform_v<version>_<datetime>`,文件格式则分别为 `zip`、`tar`、`tar.gz`。
|
||||
|
||||
## 业务结构
|
||||
|
||||
所有业务相关的页面都在 `src/pages` 目录下,路由配置在 `src/router/index.ts` 文件,除登录页之外,其余页面都作为 `src/layouts/app-layout.vue` 的子路由。
|
||||
|
||||
```bash
|
||||
src/
|
||||
router/
|
||||
index.ts # 路由配置文件
|
||||
layouts/
|
||||
app-layout.vue # 布局
|
||||
pages/
|
||||
login/
|
||||
login-page.vue # 登录页面
|
||||
station/
|
||||
station-page.vue # 车站状态页面(首页)
|
||||
device/
|
||||
device-page.vue # 设备诊断页面
|
||||
alarm/
|
||||
alarm-ignore-page.vue # 告警忽略管理页面
|
||||
alarm-log-page.vue # 设备告警记录页面
|
||||
log/
|
||||
call-log-page.vue # 上级调用日志页面
|
||||
vimp-log-page.vue # 视频平台日志页面
|
||||
permission/
|
||||
permission-page.vue # 权限管理页面
|
||||
error/
|
||||
not-found-page.vue # 404 页面
|
||||
```
|
||||
|
||||
## 数据轮询
|
||||
|
||||
由于后端服务的架构限制,需要前端向所有车站服务依次发送请求来获取数据,需要获取的数据包含车站状态、设备数据以及告警数据,因此需要设计一套数据轮询方案,定期从所有车站服务获取数据。
|
||||
|
||||
在项目中,`src/composables/query/` 目录下是所有数据轮询相关的代码,其中与业务相关的代码主要包括:
|
||||
|
||||
- `use-line-stations-query.ts`: 查询所有车站
|
||||
- `use-line-devices-query.ts`: 查询所有设备
|
||||
- `use-line-alarms-query.ts`: 查询所有告警
|
||||
- `use-user-permission-query.ts`: 查询用户权限
|
||||
|
||||
在描述整个数据轮询流程之前,我们要明确项目中必须存在的几个关键概念:
|
||||
|
||||
- 车站相关:车站query + 车站store
|
||||
- 设备相关:设备query + 设备store
|
||||
- 告警相关:告警query + 告警store
|
||||
- 权限相关:权限query + 权限store
|
||||
|
||||
整个数据轮询流程采用“单点驱动 + 变更监听 + 级联触发”的模式,如下图所示。
|
||||
|
||||

|
||||
|
||||
1. 轮询入口:车站query
|
||||
- 触发条件:以120秒的周期自动轮询车站列表
|
||||
- 数据流向:车站store
|
||||
2. 核心调度:权限query
|
||||
- 触发条件:车站query执行后触发
|
||||
- 数据流向:权限store,并计算当前用户在各车站的权限
|
||||
- 数据监听:监听车站和权限变化,触发设备query和告警query
|
||||
3. 设备query & 告警query
|
||||
- 触发条件:被动触发,由权限query主动调用
|
||||
- 数据流向:设备store & 告警store
|
||||
|
||||
## 调试模式
|
||||
|
||||
在设置面板中有一系列与调试模式有关的设置项,主要用于开发和故障排查。
|
||||
|
||||
### 开启方式
|
||||
|
||||
调试模式默认隐藏,通过以下方式开启:
|
||||
|
||||
1. 使用快捷键 `Ctrl + Alt + D` 唤起验证弹窗
|
||||
2. 输入授权码进行验证(授权码对应环境变量 `.env` 中的 `VITE_DEBUG_CODE`)
|
||||
3. 验证通过后,在“系统设置”面板中会出现 **调试** 分组
|
||||
|
||||
### 设置项说明
|
||||
|
||||
#### 数据设置
|
||||
|
||||
- **显示设备原始数据**
|
||||
- 控制是否在设备详情页显示“原始数据”标签页
|
||||
- 开启后可查看设备接口返回的原始 JSON 数据,便于排查字段缺失或格式错误
|
||||
|
||||
#### 网络设置
|
||||
|
||||
- **轮询车站**
|
||||
- 控制是否定时拉取车站状态,进而触发权限、设备及告警数据的更新
|
||||
- 关闭后将暂停所有业务数据的自动轮询机制
|
||||
- **主动请求**
|
||||
- 控制组件挂载时是否自动发起数据请求
|
||||
- 涵盖设备在线状态检测、用户登录验证等逻辑,关闭后组件在初始化时将不再自动拉取数据
|
||||
- **订阅消息**
|
||||
- 控制是否通过 WebSocket (STOMP) 接收实时告警或状态推送
|
||||
- 关闭后将不再处理后端推送的实时消息
|
||||
- **模拟用户**
|
||||
- 开启后使用内置的超管用户绕过登录
|
||||
- 开启时会自动进入调试模式,便于开发环境快速测试
|
||||
|
||||
#### 数据库设置
|
||||
|
||||
- **直接操作本地数据库**
|
||||
- 控制某些业务逻辑(如交换机端口、安防箱回路)是否直接读写本地 IndexedDB
|
||||
- 用于在无后端环境或特定测试场景下验证本地数据逻辑
|
||||
|
||||
## 离线开发
|
||||
|
||||
项目支持在无后端服务的情况下正常启动,具体操作取决于你的本地环境是否已有历史数据。
|
||||
|
||||
### 场景一:已有本地缓存
|
||||
|
||||
如果你的浏览器曾接入过现场环境,IndexedDB 中已保存了车站、设备等数据,只需在设置中关闭网络请求即可进入离线模式:
|
||||
|
||||
1. 开启调试模式(`Ctrl + Alt + D`)。
|
||||
2. 在“网络设置”中,关闭 **轮询车站**、**主动请求** 和 **订阅消息**。
|
||||
3. 此时平台将停止向后端发起请求,直接展示本地缓存的历史数据。
|
||||
|
||||
### 场景二:全新环境启动(新人推荐)
|
||||
|
||||
如果你是首次拉取项目且无法连接后端,需要按以下步骤操作:
|
||||
|
||||
1. **模拟登录**
|
||||
在登录页按 `F12` 打开控制台,输入以下命令强制进入平台:
|
||||
|
||||
```javascript
|
||||
window.$mockUser.value = true;
|
||||
```
|
||||
|
||||
执行后平台将自动完成以下操作:
|
||||
- 注入测试 Token 和管理员身份信息
|
||||
- 关闭所有网络请求(轮询、主动请求、消息订阅)
|
||||
- 开启调试模式
|
||||
- 自动跳转至平台首页
|
||||
|
||||
2. **导入模拟数据**
|
||||
进入平台后,页面默认为空。需导入预设数据以填充内容:
|
||||
- 打开“系统设置”(已自动开启调试模式)。
|
||||
- 在 **调试** -> **数据库设置** 中,勾选 **直接操作本地数据库**。
|
||||
- 点击该选项下方的 **导入数据** 按钮。
|
||||
- 依次导入项目根目录 `docs/data/` 下的三个文件:
|
||||
- `ndm-station-store.json`(车站数据)
|
||||
- `ndm-device-store.json`(设备数据)
|
||||
- `ndm-alarm-store.json`(告警数据)
|
||||
> **注意**:每次导入一个文件后,平台会自动刷新页面以应用数据。请等待刷新完成后,重新打开设置面板导入下一个文件。
|
||||
|
||||
BIN
docs/assets/query-chain.png
Normal file
BIN
docs/assets/query-chain.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
527708
docs/data/ndm-alarm-store.json
Normal file
527708
docs/data/ndm-alarm-store.json
Normal file
File diff suppressed because it is too large
Load Diff
195971
docs/data/ndm-device-store.json
Normal file
195971
docs/data/ndm-device-store.json
Normal file
File diff suppressed because one or more lines are too long
203
docs/data/ndm-station-store.json
Normal file
203
docs/data/ndm-station-store.json
Normal file
@@ -0,0 +1,203 @@
|
||||
{
|
||||
"stations": [
|
||||
{
|
||||
"code": "1075",
|
||||
"name": "吴中路控制中心",
|
||||
"online": true,
|
||||
"ip": "10.18.128.10",
|
||||
"occ": true
|
||||
},
|
||||
{
|
||||
"code": "1001",
|
||||
"name": "虹桥火车站",
|
||||
"online": true,
|
||||
"ip": "10.18.129.10"
|
||||
},
|
||||
{
|
||||
"code": "1002",
|
||||
"name": "虹桥2号航站楼",
|
||||
"online": true,
|
||||
"ip": "10.18.131.10"
|
||||
},
|
||||
{
|
||||
"code": "1003",
|
||||
"name": "虹桥一号航站楼",
|
||||
"online": true,
|
||||
"ip": "10.18.133.10"
|
||||
},
|
||||
{
|
||||
"code": "1004",
|
||||
"name": "上海动物园",
|
||||
"online": true,
|
||||
"ip": "10.18.135.10"
|
||||
},
|
||||
{
|
||||
"code": "1005",
|
||||
"name": "龙溪路",
|
||||
"online": true,
|
||||
"ip": "10.18.137.10"
|
||||
},
|
||||
{
|
||||
"code": "1006",
|
||||
"name": "水城路",
|
||||
"online": true,
|
||||
"ip": "10.18.139.10"
|
||||
},
|
||||
{
|
||||
"code": "1007",
|
||||
"name": "伊犁路",
|
||||
"online": true,
|
||||
"ip": "10.18.141.10"
|
||||
},
|
||||
{
|
||||
"code": "1008",
|
||||
"name": "宋园路",
|
||||
"online": true,
|
||||
"ip": "10.18.143.10"
|
||||
},
|
||||
{
|
||||
"code": "1009",
|
||||
"name": "虹桥路",
|
||||
"online": true,
|
||||
"ip": "10.18.145.10"
|
||||
},
|
||||
{
|
||||
"code": "1010",
|
||||
"name": "交通大学",
|
||||
"online": true,
|
||||
"ip": "10.18.147.10"
|
||||
},
|
||||
{
|
||||
"code": "1011",
|
||||
"name": "图书馆",
|
||||
"online": true,
|
||||
"ip": "10.18.149.10"
|
||||
},
|
||||
{
|
||||
"code": "1012",
|
||||
"name": "陕西南路",
|
||||
"online": true,
|
||||
"ip": "10.18.151.10"
|
||||
},
|
||||
{
|
||||
"code": "1013",
|
||||
"name": "新天地",
|
||||
"online": true,
|
||||
"ip": "10.18.153.10"
|
||||
},
|
||||
{
|
||||
"code": "1014",
|
||||
"name": "老西门",
|
||||
"online": true,
|
||||
"ip": "10.18.155.10"
|
||||
},
|
||||
{
|
||||
"code": "1015",
|
||||
"name": "豫园",
|
||||
"online": true,
|
||||
"ip": "10.18.157.10"
|
||||
},
|
||||
{
|
||||
"code": "1016",
|
||||
"name": "南京东路",
|
||||
"online": true,
|
||||
"ip": "10.18.159.10"
|
||||
},
|
||||
{
|
||||
"code": "1017",
|
||||
"name": "天潼路",
|
||||
"online": true,
|
||||
"ip": "10.18.161.10"
|
||||
},
|
||||
{
|
||||
"code": "1018",
|
||||
"name": "四川北路",
|
||||
"online": true,
|
||||
"ip": "10.18.163.10"
|
||||
},
|
||||
{
|
||||
"code": "1019",
|
||||
"name": "海伦路",
|
||||
"online": true,
|
||||
"ip": "10.18.165.10"
|
||||
},
|
||||
{
|
||||
"code": "1020",
|
||||
"name": "邮电新村",
|
||||
"online": true,
|
||||
"ip": "10.18.167.10"
|
||||
},
|
||||
{
|
||||
"code": "1021",
|
||||
"name": "四平路",
|
||||
"online": true,
|
||||
"ip": "10.18.169.10"
|
||||
},
|
||||
{
|
||||
"code": "1022",
|
||||
"name": "同济大学",
|
||||
"online": true,
|
||||
"ip": "10.18.171.10"
|
||||
},
|
||||
{
|
||||
"code": "1023",
|
||||
"name": "国权路",
|
||||
"online": true,
|
||||
"ip": "10.18.173.10"
|
||||
},
|
||||
{
|
||||
"code": "1024",
|
||||
"name": "五角场",
|
||||
"online": true,
|
||||
"ip": "10.18.175.10"
|
||||
},
|
||||
{
|
||||
"code": "1025",
|
||||
"name": "江湾体育场",
|
||||
"online": true,
|
||||
"ip": "10.18.177.10"
|
||||
},
|
||||
{
|
||||
"code": "1026",
|
||||
"name": "三门路",
|
||||
"online": true,
|
||||
"ip": "10.18.179.10"
|
||||
},
|
||||
{
|
||||
"code": "1027",
|
||||
"name": "殷高东路",
|
||||
"online": true,
|
||||
"ip": "10.18.181.10"
|
||||
},
|
||||
{
|
||||
"code": "1028",
|
||||
"name": "新江湾城",
|
||||
"online": true,
|
||||
"ip": "10.18.183.10"
|
||||
},
|
||||
{
|
||||
"code": "1029",
|
||||
"name": "航中路",
|
||||
"online": true,
|
||||
"ip": "10.18.185.10"
|
||||
},
|
||||
{
|
||||
"code": "1030",
|
||||
"name": "紫藤路",
|
||||
"online": true,
|
||||
"ip": "10.18.187.10"
|
||||
},
|
||||
{
|
||||
"code": "1031",
|
||||
"name": "龙柏新村",
|
||||
"online": true,
|
||||
"ip": "10.18.189.10"
|
||||
},
|
||||
{
|
||||
"code": "1032",
|
||||
"name": "吴中路基地",
|
||||
"online": true,
|
||||
"ip": "10.18.244.10"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,4 +3,5 @@ export interface Station {
|
||||
name: string;
|
||||
online: boolean;
|
||||
ip: string;
|
||||
occ?: boolean; // 是否为控制中心
|
||||
}
|
||||
|
||||
21
src/apis/model/base/base-employee.ts
Normal file
21
src/apis/model/base/base-employee.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { BaseModel, ReduceForPageQuery, ReduceForSaveVO, ReduceForUpdateVO } from '@/apis';
|
||||
import type { Nullable, Optional } from '@/types';
|
||||
|
||||
export interface BaseEmployee extends BaseModel {
|
||||
userId: string;
|
||||
realName: string;
|
||||
defUser: Nullable<
|
||||
{
|
||||
username: string;
|
||||
nickName: string;
|
||||
} & BaseModel
|
||||
>;
|
||||
}
|
||||
|
||||
export type BaseEmployeeResultVO = Nullable<BaseEmployee>;
|
||||
|
||||
export type BaseEmployeeSaveVO = Partial<Omit<BaseEmployee, ReduceForSaveVO>>;
|
||||
|
||||
export type BaseEmployeeUpdateVO = Optional<Omit<BaseEmployee, ReduceForUpdateVO>>;
|
||||
|
||||
export type BaseEmployeePageQuery = Partial<Omit<BaseEmployee, ReduceForPageQuery>>;
|
||||
1
src/apis/model/base/index.ts
Normal file
1
src/apis/model/base/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './base-employee';
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './ndm-permission';
|
||||
export * from './ndm-security-box';
|
||||
export * from './ndm-switch';
|
||||
|
||||
34
src/apis/model/biz/entity/other/ndm-permission.ts
Normal file
34
src/apis/model/biz/entity/other/ndm-permission.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { BaseModel, ReduceForPageQuery, ReduceForSaveVO, ReduceForUpdateVO, Station } from '@/apis';
|
||||
import type { PermissionType } from '@/enums';
|
||||
import type { Nullable, Optional } from '@/types';
|
||||
|
||||
export interface NdmPermission extends BaseModel {
|
||||
/**
|
||||
* 员工ID
|
||||
*/
|
||||
employeeId: string;
|
||||
/**
|
||||
* 服务器IP地址
|
||||
*/
|
||||
ipAddress: string;
|
||||
/**
|
||||
* 站号
|
||||
*/
|
||||
stationCode: Station['code'];
|
||||
/**
|
||||
* 站名
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 权限类型
|
||||
*/
|
||||
type: PermissionType;
|
||||
}
|
||||
|
||||
export type NdmPermissionResultVO = Nullable<NdmPermission>;
|
||||
|
||||
export type NdmPermissionSaveVO = Partial<Omit<NdmPermission, ReduceForSaveVO>>;
|
||||
|
||||
export type NdmPermissionUpdateVO = Optional<Omit<NdmPermission, ReduceForUpdateVO>>;
|
||||
|
||||
export type NdmPermissionPageQuery = Partial<Omit<NdmPermission, ReduceForPageQuery>>;
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './base';
|
||||
export * from './biz';
|
||||
export * from './common';
|
||||
export * from './schema';
|
||||
|
||||
21
src/apis/request/base/base-employee.ts
Normal file
21
src/apis/request/base/base-employee.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { BaseEmployeePageQuery, BaseEmployeeResultVO, PageParams, PageResult } from '@/apis';
|
||||
import { userClient } from '@/apis/client';
|
||||
import { unwrapResponse } from '@/utils';
|
||||
|
||||
export const pageBaseEmployeeApi = async (pageQuery: PageParams<BaseEmployeePageQuery>, options?: { signal?: AbortSignal }) => {
|
||||
const { signal } = options ?? {};
|
||||
const client = userClient;
|
||||
const endpoint = '/api/base/baseEmployee/page';
|
||||
const resp = await client.post<PageResult<BaseEmployeeResultVO>>(endpoint, pageQuery, { signal });
|
||||
const data = unwrapResponse(resp);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const detailBaseEmployeeApi = async (id: string, options?: { signal?: AbortSignal }) => {
|
||||
const { signal } = options ?? {};
|
||||
const client = userClient;
|
||||
const endpoint = `/api/base/baseEmployee/detail`;
|
||||
const resp = await client.get<BaseEmployeeResultVO>(endpoint, { params: { id }, signal });
|
||||
const data = unwrapResponse(resp);
|
||||
return data;
|
||||
};
|
||||
1
src/apis/request/base/index.ts
Normal file
1
src/apis/request/base/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './base-employee';
|
||||
@@ -48,3 +48,13 @@ export const reloadAllRecordCheckApi = async (dayOffset: number, options?: { sta
|
||||
if (!data) throw new Error(`${data}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const batchExportRecordCheckApi = async (params: { checkDuration: number; gapSeconds: number; stationCode: Station['code'][] }, options?: { signal?: AbortSignal }) => {
|
||||
const { signal } = options ?? {};
|
||||
const { checkDuration, gapSeconds, stationCode } = params;
|
||||
const client = userClient;
|
||||
const endpoint = `/api/ndm/ndmRecordCheck/batchExportByTemplate`;
|
||||
const resp = await client.post<Blob>(endpoint, { checkDuration, gapSeconds, stationCode }, { responseType: 'blob', retRaw: true, signal });
|
||||
const data = unwrapResponse(resp);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './ndm-permission';
|
||||
export * from './ndm-security-box';
|
||||
export * from './ndm-service-available';
|
||||
export * from './ndm-switch';
|
||||
|
||||
83
src/apis/request/biz/other/ndm-permission.ts
Normal file
83
src/apis/request/biz/other/ndm-permission.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
ndmClient,
|
||||
userClient,
|
||||
type NdmPermissionPageQuery,
|
||||
type NdmPermissionResultVO,
|
||||
type NdmPermissionSaveVO,
|
||||
type NdmPermissionUpdateVO,
|
||||
type PageParams,
|
||||
type PageResult,
|
||||
type Station,
|
||||
} from '@/apis';
|
||||
import type { PermissionTypeEnum } from '@/enums';
|
||||
import { unwrapResponse } from '@/utils';
|
||||
|
||||
export const permissionTypesApi = async (options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
const { stationCode, signal } = options ?? {};
|
||||
const client = stationCode ? ndmClient : userClient;
|
||||
const prefix = stationCode ? `/${stationCode}` : '';
|
||||
const endpoint = `${prefix}/api/ndm/ndmPermission/types`;
|
||||
const resp = await client.get<PermissionTypeEnum>(endpoint, { signal });
|
||||
const data = unwrapResponse(resp);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const pagePermissionApi = async (pageQuery: PageParams<NdmPermissionPageQuery>, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
const { stationCode, signal } = options ?? {};
|
||||
const client = stationCode ? ndmClient : userClient;
|
||||
const prefix = stationCode ? `/${stationCode}` : '';
|
||||
const endpoint = `${prefix}/api/ndm/ndmPermission/page`;
|
||||
const resp = await client.post<PageResult<NdmPermissionResultVO>>(endpoint, pageQuery, { signal });
|
||||
const data = unwrapResponse(resp);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const detailPermissionApi = async (id: 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/ndmPermission/detail`;
|
||||
const resp = await client.get<NdmPermissionResultVO>(endpoint, { params: { id }, signal });
|
||||
const data = unwrapResponse(resp);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const savePermissionApi = async (saveVO: NdmPermissionSaveVO, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
const { stationCode, signal } = options ?? {};
|
||||
const client = stationCode ? ndmClient : userClient;
|
||||
const prefix = stationCode ? `/${stationCode}` : '';
|
||||
const endpoint = `${prefix}/api/ndm/ndmPermission`;
|
||||
const resp = await client.post<NdmPermissionResultVO>(endpoint, saveVO, { signal });
|
||||
const result = unwrapResponse(resp);
|
||||
return result;
|
||||
};
|
||||
|
||||
export const updatePermissionApi = async (updateVO: NdmPermissionUpdateVO, options?: { stationCode?: Station['code']; signal?: AbortSignal }) => {
|
||||
const { stationCode, signal } = options ?? {};
|
||||
const client = stationCode ? ndmClient : userClient;
|
||||
const prefix = stationCode ? `/${stationCode}` : '';
|
||||
const endpoint = `${prefix}/api/ndm/ndmPermission`;
|
||||
const resp = await client.put<NdmPermissionResultVO>(endpoint, updateVO, { signal });
|
||||
const result = unwrapResponse(resp);
|
||||
return result;
|
||||
};
|
||||
|
||||
export const deletePermissionApi = async (ids: 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/ndmPermission`;
|
||||
const resp = await client.delete<boolean>(endpoint, ids, { signal });
|
||||
const result = unwrapResponse(resp);
|
||||
return result;
|
||||
};
|
||||
|
||||
export const modifyPermissionApi = async (params: { employeeId: string; saveList: NdmPermissionSaveVO[]; removeList: 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/ndmPermission/modify`;
|
||||
const resp = await client.post<boolean>(endpoint, params, { signal });
|
||||
const result = unwrapResponse(resp);
|
||||
return result;
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './base';
|
||||
export * from './biz';
|
||||
export * from './system';
|
||||
|
||||
@@ -1,29 +1,58 @@
|
||||
<script setup lang="ts">
|
||||
import { getChannelListApi, getRecordCheckApi, reloadAllRecordCheckApi, reloadRecordCheckApi, type NdmNvrResultVO, type NdmRecordCheck, type RecordItem, type Station } from '@/apis';
|
||||
import { getChannelListApi, getRecordCheckApi, reloadAllRecordCheckApi, reloadRecordCheckApi, type NdmNvrResultVO, type RecordItem, type Station } from '@/apis';
|
||||
import { exportRecordDiagCsv, transformRecordChecks } from '@/helpers';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { parseErrorFeedback } from '@/utils';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query';
|
||||
import { isCancel } from 'axios';
|
||||
import dayjs from 'dayjs';
|
||||
import { DownloadIcon, RotateCwIcon } from 'lucide-vue-next';
|
||||
import { NButton, NCard, NFlex, NIcon, NPagination, NPopconfirm, NPopover, NRadioButton, NRadioGroup, NTooltip, useThemeVars } from 'naive-ui';
|
||||
import { computed, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, onBeforeUnmount, ref, toRefs, watch } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
ndmDevice: NdmNvrResultVO;
|
||||
station: Station;
|
||||
}>();
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
const { activeRequests } = storeToRefs(settingStore);
|
||||
|
||||
const themeVars = useThemeVars();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { ndmDevice, station } = toRefs(props);
|
||||
|
||||
const recordChecks = ref<NdmRecordCheck[]>([]);
|
||||
|
||||
const lossInput = ref<number>(0);
|
||||
|
||||
const abortController = ref<AbortController>(new AbortController());
|
||||
|
||||
const NVR_RECORD_CHECK_KEY = 'nvr_record_check_query';
|
||||
|
||||
const {
|
||||
data: recordChecks,
|
||||
isFetching: loading,
|
||||
refetch: refetchRecordChecks,
|
||||
} = useQuery({
|
||||
queryKey: computed(() => [NVR_RECORD_CHECK_KEY, ndmDevice.value.id, ndmDevice.value.lastDiagTime]),
|
||||
enabled: computed(() => activeRequests.value),
|
||||
refetchInterval: 30 * 1000,
|
||||
gcTime: 0,
|
||||
queryFn: async ({ signal }) => {
|
||||
const checks = await getRecordCheckApi(ndmDevice.value, 90, [], { stationCode: station.value.code, signal });
|
||||
return checks;
|
||||
},
|
||||
});
|
||||
watch(activeRequests, (active) => {
|
||||
if (!active) {
|
||||
queryClient.cancelQueries({ queryKey: [NVR_RECORD_CHECK_KEY] });
|
||||
}
|
||||
});
|
||||
|
||||
const recordDiags = computed(() => {
|
||||
return transformRecordChecks(recordChecks.value).filter((recordDiag) => {
|
||||
return transformRecordChecks(recordChecks.value ?? []).filter((recordDiag) => {
|
||||
if (lossInput.value === 0) {
|
||||
return true;
|
||||
} else if (lossInput.value === 1) {
|
||||
@@ -35,26 +64,6 @@ const recordDiags = computed(() => {
|
||||
});
|
||||
});
|
||||
|
||||
const abortController = ref<AbortController>(new AbortController());
|
||||
|
||||
const { mutate: getRecordCheckByParentId, isPending: loading } = useMutation({
|
||||
mutationFn: async () => {
|
||||
abortController.value.abort();
|
||||
abortController.value = new AbortController();
|
||||
const checks = await getRecordCheckApi(ndmDevice.value, 90, [], { stationCode: station.value.code, signal: abortController.value.signal });
|
||||
return checks;
|
||||
},
|
||||
onSuccess: (checks) => {
|
||||
recordChecks.value = checks;
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isCancel(error)) return;
|
||||
console.error(error);
|
||||
const errorFeedback = parseErrorFeedback(error);
|
||||
window.$message.error(errorFeedback);
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: reloadAllRecordCheck, isPending: reloading } = useMutation({
|
||||
mutationFn: async () => {
|
||||
abortController.value.abort();
|
||||
@@ -114,7 +123,7 @@ const { mutate: reloadRecordCheckByGbId } = useMutation({
|
||||
}
|
||||
},
|
||||
onSuccess: () => {
|
||||
getRecordCheckByParentId();
|
||||
refetchRecordChecks();
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isCancel(error)) return;
|
||||
@@ -124,20 +133,6 @@ const { mutate: reloadRecordCheckByGbId } = useMutation({
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getRecordCheckByParentId();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => ndmDevice.value.id,
|
||||
(devieDbId) => {
|
||||
if (devieDbId) {
|
||||
recordChecks.value = [];
|
||||
getRecordCheckByParentId();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
abortController.value.abort();
|
||||
});
|
||||
@@ -162,7 +157,7 @@ onBeforeUnmount(() => {
|
||||
<NFlex>
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<NButton size="small" quaternary circle :loading="loading" @click="() => getRecordCheckByParentId()">
|
||||
<NButton size="small" quaternary circle :loading="loading" @click="() => refetchRecordChecks()">
|
||||
<template #icon>
|
||||
<NIcon :component="RotateCwIcon" />
|
||||
</template>
|
||||
|
||||
@@ -13,7 +13,9 @@ import {
|
||||
type Station,
|
||||
} from '@/apis';
|
||||
import { SecurityBoxCircuitLinkModal } from '@/components';
|
||||
import { usePermission } from '@/composables';
|
||||
import { SELECT_DEVICE_FN_INJECTION_KEY } from '@/constants';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { useDeviceStore, useSettingStore } from '@/stores';
|
||||
import { parseErrorFeedback } from '@/utils';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
@@ -40,6 +42,8 @@ const { lineDevices } = storeToRefs(deviceStore);
|
||||
const settingStore = useSettingStore();
|
||||
const { useLocalDB } = storeToRefs(settingStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { ndmDevice, station, circuits } = toRefs(props);
|
||||
|
||||
const showCard = computed(() => !!circuits.value && circuits.value.length > 0);
|
||||
@@ -223,6 +227,7 @@ const onSelectDropdownOption = (key: string, option: DropdownOption) => {
|
||||
const onContextmenu = (payload: PointerEvent, circuitIndex: number) => {
|
||||
payload.stopPropagation();
|
||||
payload.preventDefault();
|
||||
if (!hasPermission(station.value.code, PERMISSION_TYPE_LITERALS.OPERATION)) return;
|
||||
const { clientX, clientY } = payload;
|
||||
contextmenu.value = { x: clientX, y: clientY, circuitIndex };
|
||||
showContextmenu.value = true;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { detailDeviceApi, updateDeviceApi, type LinkDescription, type NdmDeviceResultVO, type NdmSwitchLinkDescription, type NdmSwitchPortInfo, type NdmSwitchResultVO, type Station } from '@/apis';
|
||||
import { SwitchPortLinkModal } from '@/components';
|
||||
import { usePermission } from '@/composables';
|
||||
import { SELECT_DEVICE_FN_INJECTION_KEY } from '@/constants';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { getPortStatusValue, transformPortSpeed } from '@/helpers';
|
||||
import { useDeviceStore, useSettingStore } from '@/stores';
|
||||
import { parseErrorFeedback } from '@/utils';
|
||||
@@ -27,6 +29,8 @@ const { lineDevices } = storeToRefs(deviceStore);
|
||||
const settingStore = useSettingStore();
|
||||
const { useLocalDB } = storeToRefs(settingStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { ndmDevice, station, ports } = toRefs(props);
|
||||
|
||||
const showCard = computed(() => !!ports.value);
|
||||
@@ -172,6 +176,7 @@ const onSelectDropdownOption = (key: string, option: DropdownOption) => {
|
||||
const onContextmenu = (payload: PointerEvent, port: NdmSwitchPortInfo) => {
|
||||
payload.stopPropagation();
|
||||
payload.preventDefault();
|
||||
if (!hasPermission(station.value.code, PERMISSION_TYPE_LITERALS.OPERATION)) return;
|
||||
const { clientX, clientY } = payload;
|
||||
contextmenu.value = { x: clientX, y: clientY, port };
|
||||
showContextmenu.value = true;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmAlarmHostResultVO, Station } from '@/apis';
|
||||
import { AlarmHostCurrentDiag, AlarmHostHistoryDiag, AlarmHostUpdate, DeviceRawCard } from '@/components';
|
||||
import { usePermission } from '@/composables';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
||||
const settingStore = useSettingStore();
|
||||
const { showDeviceRawData } = storeToRefs(settingStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { ndmDevice, station } = toRefs(props);
|
||||
|
||||
const showPageHeader = computed(() => {
|
||||
@@ -31,8 +35,8 @@ const activeTabName = ref('当前诊断');
|
||||
const onTabChange = (name: string) => {
|
||||
activeTabName.value = name;
|
||||
};
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || !enabled) {
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, showRaw], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || (!showRaw && activeTabName.value === '原始数据')) {
|
||||
activeTabName.value = '当前诊断';
|
||||
}
|
||||
});
|
||||
@@ -45,7 +49,7 @@ watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||
<NTab name="当前诊断">当前诊断</NTab>
|
||||
<NTab name="历史诊断">历史诊断</NTab>
|
||||
<NTab name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="showDeviceRawData" name="原始数据">原始数据</NTab>
|
||||
</NTabs>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmCameraResultVO, Station } from '@/apis';
|
||||
import { CameraCurrentDiag, CameraHistoryDiag, CameraUpdate, DeviceRawCard } from '@/components';
|
||||
import { usePermission } from '@/composables';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
||||
const settingStore = useSettingStore();
|
||||
const { showDeviceRawData } = storeToRefs(settingStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { ndmDevice, station } = toRefs(props);
|
||||
|
||||
const showPageHeader = computed(() => {
|
||||
@@ -31,8 +35,8 @@ const activeTabName = ref('当前诊断');
|
||||
const onTabChange = (name: string) => {
|
||||
activeTabName.value = name;
|
||||
};
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || !enabled) {
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, showRaw], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || (!showRaw && activeTabName.value === '原始数据')) {
|
||||
activeTabName.value = '当前诊断';
|
||||
}
|
||||
});
|
||||
@@ -45,7 +49,7 @@ watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||
<NTab name="当前诊断">当前诊断</NTab>
|
||||
<NTab name="历史诊断">历史诊断</NTab>
|
||||
<NTab name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="showDeviceRawData" name="原始数据">原始数据</NTab>
|
||||
</NTabs>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmDecoderResultVO, Station } from '@/apis';
|
||||
import { DecoderCurrentDiag, DecoderHistoryDiag, DecoderUpdate, DeviceRawCard } from '@/components';
|
||||
import { usePermission } from '@/composables';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
||||
const settingStore = useSettingStore();
|
||||
const { showDeviceRawData } = storeToRefs(settingStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { ndmDevice, station } = toRefs(props);
|
||||
|
||||
const showPageHeader = computed(() => {
|
||||
@@ -31,8 +35,8 @@ const activeTabName = ref('当前诊断');
|
||||
const onTabChange = (name: string) => {
|
||||
activeTabName.value = name;
|
||||
};
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || !enabled) {
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, showRaw], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || (!showRaw && activeTabName.value === '原始数据')) {
|
||||
activeTabName.value = '当前诊断';
|
||||
}
|
||||
});
|
||||
@@ -45,7 +49,7 @@ watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||
<NTab name="当前诊断">当前诊断</NTab>
|
||||
<NTab name="历史诊断">历史诊断</NTab>
|
||||
<NTab name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="showDeviceRawData" name="原始数据">原始数据</NTab>
|
||||
</NTabs>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmKeyboardResultVO, Station } from '@/apis';
|
||||
import { DeviceRawCard, KeyboardCurrentDiag, KeyboardHistoryDiag, KeyboardUpdate } from '@/components';
|
||||
import { usePermission } from '@/composables';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
||||
const settingStore = useSettingStore();
|
||||
const { showDeviceRawData } = storeToRefs(settingStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { ndmDevice, station } = toRefs(props);
|
||||
|
||||
const showPageHeader = computed(() => {
|
||||
@@ -31,8 +35,8 @@ const activeTabName = ref('当前诊断');
|
||||
const onTabChange = (name: string) => {
|
||||
activeTabName.value = name;
|
||||
};
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || !enabled) {
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, showRaw], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || (!showRaw && activeTabName.value === '原始数据')) {
|
||||
activeTabName.value = '当前诊断';
|
||||
}
|
||||
});
|
||||
@@ -45,7 +49,7 @@ watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||
<NTab name="当前诊断">当前诊断</NTab>
|
||||
<NTab name="历史诊断">历史诊断</NTab>
|
||||
<NTab name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="showDeviceRawData" name="原始数据">原始数据</NTab>
|
||||
</NTabs>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmNvrResultVO, Station } from '@/apis';
|
||||
import { DeviceRawCard, NvrCurrentDiag, NvrHistoryDiag, NvrUpdate } from '@/components';
|
||||
import { usePermission } from '@/composables';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
||||
const settingStore = useSettingStore();
|
||||
const { showDeviceRawData } = storeToRefs(settingStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { ndmDevice, station } = toRefs(props);
|
||||
|
||||
const showPageHeader = computed(() => {
|
||||
@@ -31,8 +35,8 @@ const activeTabName = ref('当前诊断');
|
||||
const onTabChange = (name: string) => {
|
||||
activeTabName.value = name;
|
||||
};
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || !enabled) {
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, showRaw], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || (!showRaw && activeTabName.value === '原始数据')) {
|
||||
activeTabName.value = '当前诊断';
|
||||
}
|
||||
});
|
||||
@@ -45,7 +49,7 @@ watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||
<NTab name="当前诊断">当前诊断</NTab>
|
||||
<NTab name="历史诊断">历史诊断</NTab>
|
||||
<NTab name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="showDeviceRawData" name="原始数据">原始数据</NTab>
|
||||
</NTabs>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmSecurityBoxResultVO, Station } from '@/apis';
|
||||
import { DeviceRawCard, SecurityBoxCurrentDiag, SecurityBoxHistoryDiag, SecurityBoxUpdate } from '@/components';
|
||||
import { usePermission } from '@/composables';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
||||
const settingStore = useSettingStore();
|
||||
const { showDeviceRawData } = storeToRefs(settingStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { ndmDevice, station } = toRefs(props);
|
||||
|
||||
const showPageHeader = computed(() => {
|
||||
@@ -31,8 +35,8 @@ const activeTabName = ref('当前诊断');
|
||||
const onTabChange = (name: string) => {
|
||||
activeTabName.value = name;
|
||||
};
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || !enabled) {
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, showRaw], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || (!showRaw && activeTabName.value === '原始数据')) {
|
||||
activeTabName.value = '当前诊断';
|
||||
}
|
||||
});
|
||||
@@ -45,7 +49,7 @@ watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||
<NTab name="当前诊断">当前诊断</NTab>
|
||||
<NTab name="历史诊断">历史诊断</NTab>
|
||||
<NTab name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="showDeviceRawData" name="原始数据">原始数据</NTab>
|
||||
</NTabs>
|
||||
</template>
|
||||
|
||||
@@ -56,7 +56,7 @@ watch(activeRequests, (active) => {
|
||||
<span>服务状态</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<template v-if="activeRequests">
|
||||
<template v-if="!activeRequests">
|
||||
<span>-</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmServerResultVO, Station } from '@/apis';
|
||||
import { DeviceRawCard, ServerCurrentDiag, ServerHistoryDiag, ServerUpdate } from '@/components';
|
||||
import { usePermission } from '@/composables';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
||||
const settingStore = useSettingStore();
|
||||
const { showDeviceRawData } = storeToRefs(settingStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { ndmDevice, station } = toRefs(props);
|
||||
|
||||
const showPageHeader = computed(() => {
|
||||
@@ -31,8 +35,8 @@ const activeTabName = ref('当前诊断');
|
||||
const onTabChange = (name: string) => {
|
||||
activeTabName.value = name;
|
||||
};
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || !enabled) {
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, showRaw], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || (!showRaw && activeTabName.value === '原始数据')) {
|
||||
activeTabName.value = '当前诊断';
|
||||
}
|
||||
});
|
||||
@@ -45,7 +49,7 @@ watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||
<NTab name="当前诊断">当前诊断</NTab>
|
||||
<NTab name="历史诊断">历史诊断</NTab>
|
||||
<NTab name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="showDeviceRawData" name="原始数据">原始数据</NTab>
|
||||
</NTabs>
|
||||
</template>
|
||||
|
||||
@@ -70,7 +70,7 @@ const streamPushStat = computed(() => {
|
||||
<span>推流统计</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<template v-if="activeRequests">
|
||||
<template v-if="!activeRequests">
|
||||
<span>-</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import type { NdmSwitchResultVO, Station } from '@/apis';
|
||||
import { DeviceRawCard, SwitchCurrentDiag, SwitchHistoryDiag, SwitchUpdate } from '@/components';
|
||||
import { usePermission } from '@/composables';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { NCard, NPageHeader, NScrollbar, NTab, NTabs } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@@ -18,6 +20,8 @@ const router = useRouter();
|
||||
const settingStore = useSettingStore();
|
||||
const { showDeviceRawData } = storeToRefs(settingStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { ndmDevice, station } = toRefs(props);
|
||||
|
||||
const showPageHeader = computed(() => {
|
||||
@@ -31,8 +35,8 @@ const activeTabName = ref('当前诊断');
|
||||
const onTabChange = (name: string) => {
|
||||
activeTabName.value = name;
|
||||
};
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || !enabled) {
|
||||
watch([ndmDevice, showDeviceRawData], ([newDevice, showRaw], [oldDevice]) => {
|
||||
if (newDevice.id !== oldDevice.id || (!showRaw && activeTabName.value === '原始数据')) {
|
||||
activeTabName.value = '当前诊断';
|
||||
}
|
||||
});
|
||||
@@ -45,7 +49,7 @@ watch([ndmDevice, showDeviceRawData], ([newDevice, enabled], [oldDevice]) => {
|
||||
<NTabs :value="activeTabName" @update:value="onTabChange">
|
||||
<NTab name="当前诊断">当前诊断</NTab>
|
||||
<NTab name="历史诊断">历史诊断</NTab>
|
||||
<NTab name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)" name="修改设备">修改设备</NTab>
|
||||
<NTab v-if="showDeviceRawData" name="原始数据">原始数据</NTab>
|
||||
</NTabs>
|
||||
</template>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { initStationDevices, type NdmDeviceResultVO, type NdmNvrResultVO, type Station } from '@/apis';
|
||||
import { useDeviceTree, type UseDeviceTreeReturn } from '@/composables';
|
||||
import { DEVICE_TYPE_NAMES, DEVICE_TYPE_LITERALS, tryGetDeviceType, type DeviceType } from '@/enums';
|
||||
import { useDeviceTree, usePermission, type UseDeviceTreeReturn } from '@/composables';
|
||||
import { DEVICE_TYPE_NAMES, DEVICE_TYPE_LITERALS, tryGetDeviceType, type DeviceType, PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { isNvrCluster } from '@/helpers';
|
||||
import { useDeviceStore, useStationStore } from '@/stores';
|
||||
import { watchImmediate } from '@vueuse/core';
|
||||
import { useDeviceStore, usePermissionStore } from '@/stores';
|
||||
import { watchDebounced, watchImmediate } from '@vueuse/core';
|
||||
import destr from 'destr';
|
||||
import { isFunction } from 'es-toolkit';
|
||||
import {
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
type TreeProps,
|
||||
} from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, h, nextTick, onBeforeUnmount, ref, toRefs, useTemplateRef, watch, type CSSProperties } from 'vue';
|
||||
import { computed, h, nextTick, onBeforeUnmount, onMounted, ref, toRefs, useTemplateRef, watch, type CSSProperties } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
/**
|
||||
@@ -60,20 +60,22 @@ const { station, events, syncRoute, devicePrefixLabel } = toRefs(props);
|
||||
|
||||
const themeVars = useThemeVars();
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const {
|
||||
// 设备选择
|
||||
selectedStationCode,
|
||||
selectedDeviceType,
|
||||
selectedDevice,
|
||||
syncFromRoute,
|
||||
syncToRoute,
|
||||
selectDevice,
|
||||
// 设备管理
|
||||
exportDevice,
|
||||
exportDeviceTemplate,
|
||||
importDevice,
|
||||
deleteDevice,
|
||||
} = useDeviceTree({
|
||||
syncRoute: computed(() => !!syncRoute.value),
|
||||
});
|
||||
} = useDeviceTree();
|
||||
|
||||
// 将 `selectDevice` 函数暴露给父组件
|
||||
emit('exposeSelectDeviceFn', selectDevice);
|
||||
@@ -87,8 +89,9 @@ const onSelectDevice = (device: NdmDeviceResultVO, stationCode: Station['code'])
|
||||
emit('afterSelectDevice', device, stationCode);
|
||||
};
|
||||
|
||||
const stationStore = useStationStore();
|
||||
const { stations } = storeToRefs(stationStore);
|
||||
const permissionStore = usePermissionStore();
|
||||
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||
|
||||
const deviceStore = useDeviceStore();
|
||||
const { lineDevices } = storeToRefs(deviceStore);
|
||||
|
||||
@@ -220,13 +223,17 @@ const nodeProps: TreeProps['nodeProps'] = ({ option }) => {
|
||||
payload.stopPropagation();
|
||||
payload.preventDefault();
|
||||
|
||||
// 仅当事件列表包含 `manage` 时才显示右键菜单
|
||||
// 如果事件列表不包含 `manage`,则直接结束逻辑
|
||||
if (!events.value?.includes('manage')) return;
|
||||
|
||||
const { clientX, clientY } = payload;
|
||||
const stationCode = option['stationCode'] as Station['code'];
|
||||
|
||||
// 仅当用户在该车站拥有操作权限时才显示右键菜单
|
||||
if (!hasPermission(stationCode, PERMISSION_TYPE_LITERALS.OPERATION)) return;
|
||||
|
||||
const deviceType = option['deviceType'] as DeviceType | undefined;
|
||||
const device = option['device'] as NdmDeviceResultVO | undefined;
|
||||
const { clientX, clientY } = payload;
|
||||
contextmenu.value = { x: clientX, y: clientY, stationCode, deviceType, device };
|
||||
showContextmenu.value = true;
|
||||
},
|
||||
@@ -477,11 +484,38 @@ const onLocateDeviceTree = async () => {
|
||||
|
||||
animated.value = true;
|
||||
};
|
||||
// 渲染全线设备树时,当选择的设备发生变化,则定位设备树
|
||||
|
||||
// 当选择的设备发生变化时,定位设备树,并同步选中状态到路由参数
|
||||
// 暂时不考虑多次执行的问题,因为当选择的设备在设备树视口内时,不会发生滚动
|
||||
watch(selectedDevice, async () => {
|
||||
watch(selectedDevice, async (newDevice, oldDevice) => {
|
||||
if (!!station.value) return;
|
||||
await onLocateDeviceTree();
|
||||
if (newDevice?.id === oldDevice?.id) return;
|
||||
// console.log('selectedDevice changed');
|
||||
onLocateDeviceTree();
|
||||
syncToRoute();
|
||||
});
|
||||
|
||||
// 当全线设备发生变化时,从路由参数同步选中状态
|
||||
// 但lineDevices是shallowRef,因此需要深度侦听才能获取内部变化,
|
||||
// 而单纯的深度侦听又可能会引发性能问题,因此尝试使用防抖侦听
|
||||
watchDebounced(
|
||||
lineDevices,
|
||||
(newLineDevices) => {
|
||||
if (syncRoute.value) {
|
||||
// console.log('lineDevices changed');
|
||||
syncFromRoute(newLineDevices);
|
||||
}
|
||||
},
|
||||
{
|
||||
debounce: 500,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (syncRoute.value) {
|
||||
syncFromRoute(lineDevices.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { retentionDaysApi, snapStatusApi, type LineAlarms, type LineDevices, type Station, type VersionInfo } from '@/apis';
|
||||
import { ThemeSwitch } from '@/components';
|
||||
import { usePermission } from '@/composables';
|
||||
import { NDM_ALARM_STORE_ID, NDM_DEVICE_STORE_ID, NDM_STATION_STORE_ID } from '@/constants';
|
||||
import { useSettingStore } from '@/stores';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { useSettingStore, useStationStore } from '@/stores';
|
||||
import { downloadByData, getAppEnvConfig, parseErrorFeedback, sleep } from '@/utils';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
@@ -13,13 +15,20 @@ import localforage from 'localforage';
|
||||
import { DownloadIcon, Trash2Icon, UploadIcon } from 'lucide-vue-next';
|
||||
import { NButton, NButtonGroup, NDivider, NDrawer, NDrawerContent, NDropdown, NFlex, NFormItem, NIcon, NInput, NInputNumber, NModal, NSwitch, NText, type DropdownOption } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, watch } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
const show = defineModel<boolean>('show', { default: false });
|
||||
|
||||
const stationStore = useStationStore();
|
||||
const { stations } = storeToRefs(stationStore);
|
||||
|
||||
const occStation = computed(() => stations.value.find((station) => !!station.occ));
|
||||
|
||||
const settingsStore = useSettingStore();
|
||||
const { menuCollpased, stationGridCols, debugMode, showDeviceRawData, pollingStations, activeRequests, subscribeMessages, mockUser, useLocalDB } = storeToRefs(settingsStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const versionInfo = ref<VersionInfo>({ version: '', buildTime: '' });
|
||||
|
||||
const { mutate: getVersionInfo } = useMutation({
|
||||
@@ -294,6 +303,7 @@ const onDrawerAfterLeave = () => {
|
||||
<NInputNumber v-model:value="stationGridCols" :min="1" :max="10" />
|
||||
</NFormItem>
|
||||
|
||||
<template v-if="!!occStation && hasPermission(occStation.code, PERMISSION_TYPE_LITERALS.OPERATION)">
|
||||
<NDivider>告警</NDivider>
|
||||
<NFormItem label="告警画面截图保留天数" label-placement="left">
|
||||
<NFlex justify="space-between" align="center" style="width: 100%">
|
||||
@@ -313,6 +323,7 @@ const onDrawerAfterLeave = () => {
|
||||
</NButtonGroup>
|
||||
</NFlex>
|
||||
</NFormItem>
|
||||
</template>
|
||||
|
||||
<template v-if="debugMode">
|
||||
<NDivider title-placement="center">调试</NDivider>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './device';
|
||||
export * from './global';
|
||||
export * from './permission';
|
||||
export * from './station';
|
||||
|
||||
6
src/components/permission/index.ts
Normal file
6
src/components/permission/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { ComponentInstance } from 'vue';
|
||||
import PermissionConfigModal from './permission-config-modal.vue';
|
||||
|
||||
export type PermissionConfigModalProps = ComponentInstance<typeof PermissionConfigModal>['$props'];
|
||||
|
||||
export { PermissionConfigModal };
|
||||
302
src/components/permission/permission-config-modal.vue
Normal file
302
src/components/permission/permission-config-modal.vue
Normal file
@@ -0,0 +1,302 @@
|
||||
<script setup lang="ts">
|
||||
import { detailBaseEmployeeApi, modifyPermissionApi, pagePermissionApi, type BaseEmployeeResultVO, type NdmPermissionResultVO, type NdmPermissionSaveVO, type Station } from '@/apis';
|
||||
import { PERMISSION_TYPE_LITERALS, PERMISSION_TYPE_NAMES, type PermissionType } from '@/enums';
|
||||
import { useStationStore } from '@/stores';
|
||||
import { parseErrorFeedback } from '@/utils';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { objectEntries } from '@vueuse/core';
|
||||
import { isCancel } from 'axios';
|
||||
import { cloneDeep } from 'es-toolkit';
|
||||
import { NButton, NCheckbox, NDataTable, NFlex, NModal, NText, type DataTableColumn, type DataTableColumns } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, h, ref, toRefs } from 'vue';
|
||||
|
||||
type NdmPermissionSaveOrResultVO = NdmPermissionSaveVO | NdmPermissionResultVO;
|
||||
|
||||
const props = defineProps<{
|
||||
employeeId?: string;
|
||||
}>();
|
||||
|
||||
const stationStore = useStationStore();
|
||||
const { stations } = storeToRefs(stationStore);
|
||||
|
||||
const show = defineModel<boolean>('show', { default: false });
|
||||
|
||||
const { employeeId } = toRefs(props);
|
||||
|
||||
const abortController = ref<AbortController>(new AbortController());
|
||||
|
||||
const employee = ref<BaseEmployeeResultVO>();
|
||||
const { mutateAsync: getEmployeeAsync } = useMutation({
|
||||
mutationFn: async () => {
|
||||
abortController.value.abort();
|
||||
abortController.value = new AbortController();
|
||||
|
||||
if (!employeeId.value) return;
|
||||
|
||||
const signal = abortController.value.signal;
|
||||
|
||||
const data = await detailBaseEmployeeApi(employeeId.value, { signal });
|
||||
return data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (!data) return;
|
||||
employee.value = data;
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isCancel(error)) return;
|
||||
console.error(error);
|
||||
const errorFeedback = parseErrorFeedback(error);
|
||||
window.$message.error(errorFeedback);
|
||||
},
|
||||
});
|
||||
|
||||
// 从后端获取的原始权限列表
|
||||
const originalList = ref<NdmPermissionResultVO[]>([]);
|
||||
// 当前用户配置的权限列表
|
||||
const currentList = ref<NdmPermissionSaveOrResultVO[]>([]);
|
||||
|
||||
const { mutate: getPermissions, isPending: permissionsLoading } = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!employeeId.value) throw new Error('员工ID不能为空');
|
||||
|
||||
abortController.value.abort();
|
||||
abortController.value = new AbortController();
|
||||
|
||||
const signal = abortController.value.signal;
|
||||
|
||||
const data = await pagePermissionApi(
|
||||
{
|
||||
model: {
|
||||
employeeId: employeeId.value,
|
||||
},
|
||||
current: 1,
|
||||
size: Object.keys(PERMISSION_TYPE_LITERALS).length * stations.value.length,
|
||||
},
|
||||
{ signal },
|
||||
);
|
||||
|
||||
return data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
if (!data) return;
|
||||
const { records } = data;
|
||||
originalList.value = cloneDeep(records);
|
||||
currentList.value = cloneDeep(records);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isCancel(error)) return;
|
||||
console.error(error);
|
||||
const errorFeedback = parseErrorFeedback(error);
|
||||
window.$message.error(errorFeedback);
|
||||
},
|
||||
});
|
||||
|
||||
const onUpdatePermissionChecked = (checked: boolean, stationCode: Station['code'], permissionType: PermissionType) => {
|
||||
if (!employeeId.value) return;
|
||||
if (checked) {
|
||||
const existed = currentList.value.some((permission) => permission.stationCode === stationCode && permission.type === permissionType);
|
||||
if (!existed) {
|
||||
const saveVO: NdmPermissionSaveVO = {
|
||||
employeeId: employeeId.value,
|
||||
stationCode,
|
||||
type: permissionType,
|
||||
};
|
||||
currentList.value.push(saveVO);
|
||||
}
|
||||
} else {
|
||||
const index = currentList.value.findIndex((permission) => permission.stationCode === stationCode && permission.type === permissionType);
|
||||
if (index !== -1) {
|
||||
currentList.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const tableColumns = computed<DataTableColumns<Station>>(() => {
|
||||
return [
|
||||
{
|
||||
title: () => {
|
||||
const permissionCount = currentList.value.length;
|
||||
const permissionTypeCount = objectEntries(PERMISSION_TYPE_LITERALS).length;
|
||||
const checked = permissionCount === stations.value.length * permissionTypeCount;
|
||||
const indeterminate = permissionCount > 0 && permissionCount < stations.value.length * permissionTypeCount;
|
||||
return h(NCheckbox, {
|
||||
checked,
|
||||
indeterminate,
|
||||
onUpdateChecked: (checked) => {
|
||||
objectEntries(PERMISSION_TYPE_LITERALS).forEach(([permissionType]) => {
|
||||
stations.value.forEach((station) => {
|
||||
onUpdatePermissionChecked(checked, station.code, permissionType);
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
key: 'row-check',
|
||||
align: 'center',
|
||||
width: 60,
|
||||
fixed: 'left',
|
||||
render: (rowData) => {
|
||||
const { code: stationCode } = rowData;
|
||||
const permissionTypeCount = objectEntries(PERMISSION_TYPE_LITERALS).length;
|
||||
const stationCheckedPermissions = currentList.value.filter((permission) => permission.stationCode === stationCode);
|
||||
const checked = stationCheckedPermissions.length === permissionTypeCount;
|
||||
const indeterminate = stationCheckedPermissions.length > 0 && stationCheckedPermissions.length < permissionTypeCount;
|
||||
return h(NCheckbox, {
|
||||
checked,
|
||||
indeterminate,
|
||||
onUpdateChecked: (checked) => {
|
||||
objectEntries(PERMISSION_TYPE_LITERALS).forEach(([permissionType]) => {
|
||||
onUpdatePermissionChecked(checked, stationCode, permissionType);
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{ title: '车站编号', key: 'code', align: 'center', width: 120 },
|
||||
{ title: '车站名称', key: 'name', align: 'center', width: 360 },
|
||||
// 「权限」列
|
||||
...objectEntries(PERMISSION_TYPE_NAMES).map<DataTableColumn<Station>>(([permissionType, title]) => ({
|
||||
title: () => {
|
||||
const permissionCount = currentList.value.filter((permission) => permission.type === permissionType).length;
|
||||
const checked = permissionCount === stations.value.length;
|
||||
const indeterminate = permissionCount > 0 && permissionCount < stations.value.length;
|
||||
return h(
|
||||
NFlex,
|
||||
{
|
||||
justify: 'center',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
default: () => [
|
||||
h(NCheckbox, {
|
||||
checked,
|
||||
indeterminate,
|
||||
onUpdateChecked: (checked) => {
|
||||
stations.value.forEach((station) => {
|
||||
onUpdatePermissionChecked(checked, station.code, permissionType);
|
||||
});
|
||||
},
|
||||
}),
|
||||
h('span', title),
|
||||
],
|
||||
},
|
||||
);
|
||||
},
|
||||
key: permissionType,
|
||||
align: 'center',
|
||||
render: (rowData) => {
|
||||
const { code: stationCode } = rowData;
|
||||
return h(NCheckbox, {
|
||||
checked: currentList.value.some((permission) => permission.stationCode === stationCode && permission.type === permissionType),
|
||||
onUpdateChecked: (checked) => onUpdatePermissionChecked(checked, stationCode, permissionType),
|
||||
});
|
||||
},
|
||||
})),
|
||||
];
|
||||
});
|
||||
|
||||
const { mutate: savePermissions, isPending: permissionsSaving } = useMutation({
|
||||
mutationFn: async () => {
|
||||
if (!employeeId.value) throw new Error('员工ID不能为空');
|
||||
|
||||
abortController.value.abort();
|
||||
abortController.value = new AbortController();
|
||||
|
||||
const signal = abortController.value.signal;
|
||||
|
||||
// 执行diff计算,生成需要保存的权限列表和需要删除的权限ID列表
|
||||
const saveList: NdmPermissionSaveVO[] = [];
|
||||
const removeList: string[] = [];
|
||||
// 遍历当前状态,如果权限不在原始权限列表中,说明是需要新增的权限
|
||||
currentList.value.forEach((permission) => {
|
||||
const { stationCode, type } = permission;
|
||||
if (!stationCode || !type) return;
|
||||
if (!originalList.value.some((permission) => permission.stationCode === stationCode && permission.type === type)) {
|
||||
saveList.push({
|
||||
employeeId: employeeId.value,
|
||||
stationCode,
|
||||
type,
|
||||
});
|
||||
}
|
||||
});
|
||||
// 遍历原始状态,如果权限不在当前状态中,说明是需要删除的权限
|
||||
originalList.value.forEach((permission) => {
|
||||
const { id, stationCode, type } = permission;
|
||||
if (!id) return;
|
||||
if (!currentList.value.some((permission) => permission.stationCode === stationCode && permission.type === type)) {
|
||||
removeList.push(id);
|
||||
}
|
||||
});
|
||||
|
||||
await modifyPermissionApi(
|
||||
{
|
||||
employeeId: employeeId.value,
|
||||
saveList,
|
||||
removeList,
|
||||
},
|
||||
{
|
||||
signal,
|
||||
},
|
||||
);
|
||||
},
|
||||
onSuccess: () => {
|
||||
window.$message.success('权限配置保存成功');
|
||||
getPermissions();
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isCancel(error)) return;
|
||||
console.error(error);
|
||||
const errorFeedback = parseErrorFeedback(error);
|
||||
window.$message.error(errorFeedback);
|
||||
},
|
||||
});
|
||||
|
||||
const onAfterEnter = () => {
|
||||
getEmployeeAsync().then(() => getPermissions());
|
||||
};
|
||||
|
||||
const onAfterLeave = () => {
|
||||
employee.value = undefined;
|
||||
originalList.value = [];
|
||||
currentList.value = [];
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
abortController.value.abort();
|
||||
show.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NModal
|
||||
v-model:show="show"
|
||||
preset="card"
|
||||
style="width: 100vw; height: 100vh"
|
||||
:content-style="{ height: '100%', overflow: 'hidden' }"
|
||||
:close-on-esc="false"
|
||||
:mask-closable="false"
|
||||
:auto-focus="false"
|
||||
@after-enter="onAfterEnter"
|
||||
@after-leave="onAfterLeave"
|
||||
@close="onClose"
|
||||
>
|
||||
<template #header>
|
||||
<span>{{ `配置权限 - ${employee?.realName ?? ''}` }}</span>
|
||||
</template>
|
||||
<template #default>
|
||||
<NDataTable flex-height style="height: 100%" :columns="tableColumns" :data="stations" :loading="permissionsLoading" :single-line="false" />
|
||||
</template>
|
||||
<template #footer>
|
||||
<NText depth="3" style="font-size: smaller">*未勾选任何权限的用户将被认为拥有所有权限</NText>
|
||||
</template>
|
||||
<template #action>
|
||||
<NFlex justify="end">
|
||||
<NButton size="small" @click="onClose">取消</NButton>
|
||||
<NButton type="primary" size="small" :loading="permissionsSaving" @click="() => savePermissions()">保存</NButton>
|
||||
</NFlex>
|
||||
</template>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -1,13 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { getRecordCheckApi, type NdmNvrResultVO, type Station } from '@/apis';
|
||||
import { exportRecordDiagCsv, isNvrCluster, transformRecordChecks } from '@/helpers';
|
||||
import { useDeviceStore } from '@/stores';
|
||||
import { parseErrorFeedback } from '@/utils';
|
||||
import { batchExportRecordCheckApi, pageDefParameterApi, type Station } from '@/apis';
|
||||
import { downloadByData, parseErrorFeedback } from '@/utils';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { isCancel } from 'axios';
|
||||
import { NButton, NGrid, NGridItem, NModal, NScrollbar, NSpin } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, ref, toRefs } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { NButton, NFlex, NGrid, NGridItem, NModal, NScrollbar, NSpin } from 'naive-ui';
|
||||
import { ref, toRefs } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
stations: Station[];
|
||||
@@ -19,50 +17,66 @@ const emit = defineEmits<{
|
||||
|
||||
const show = defineModel<boolean>('show');
|
||||
|
||||
const deviceStore = useDeviceStore();
|
||||
const { lineDevices } = storeToRefs(deviceStore);
|
||||
|
||||
const { stations } = toRefs(props);
|
||||
|
||||
const nvrClusterRecord = computed(() => {
|
||||
const clusterMap: Record<Station['code'], { stationName: Station['name']; clusters: NdmNvrResultVO[] }> = {};
|
||||
stations.value.forEach((station) => {
|
||||
clusterMap[station.code] = {
|
||||
stationName: station.name,
|
||||
clusters: [],
|
||||
};
|
||||
const stationDevices = lineDevices.value[station.code];
|
||||
const nvrs = stationDevices?.['ndmNvr'] ?? [];
|
||||
nvrs.forEach((nvr) => {
|
||||
if (isNvrCluster(nvr)) {
|
||||
clusterMap[station.code]?.clusters?.push(nvr);
|
||||
}
|
||||
});
|
||||
});
|
||||
return clusterMap;
|
||||
});
|
||||
|
||||
const abortController = ref<AbortController>(new AbortController());
|
||||
|
||||
const { mutate: exportRecordDiags, isPending: exporting } = useMutation({
|
||||
mutationFn: async (params: { clusters: NdmNvrResultVO[]; stationCode: Station['code'] }) => {
|
||||
const { clusters, stationCode } = params;
|
||||
if (clusters.length === 0) {
|
||||
const stationName = nvrClusterRecord.value[stationCode]?.stationName ?? '';
|
||||
window.$message.info(`${stationName} 没有录像诊断数据`);
|
||||
return;
|
||||
}
|
||||
const cluster = clusters.at(0);
|
||||
if (!cluster) return;
|
||||
const { mutate: batchExportRecordCheck, isPending: batchExporting } = useMutation({
|
||||
mutationFn: async (params: { stations: Station[] }) => {
|
||||
const timer = setTimeout(() => {
|
||||
if (!batchExporting.value) return;
|
||||
window.$message.info('导出耗时较长,请耐心等待...', { duration: 0 });
|
||||
}, 3000);
|
||||
|
||||
try {
|
||||
abortController.value.abort();
|
||||
abortController.value = new AbortController();
|
||||
const checks = await getRecordCheckApi(cluster, 90, [], { stationCode: stationCode, signal: abortController.value.signal });
|
||||
return checks;
|
||||
const { records = [] } = await pageDefParameterApi(
|
||||
{
|
||||
model: {
|
||||
key: 'NVR_GAP_SECONDS',
|
||||
},
|
||||
onSuccess: (checks, { stationCode }) => {
|
||||
if (!checks || checks.length === 0) return;
|
||||
const recordDiags = transformRecordChecks(checks);
|
||||
exportRecordDiagCsv(recordDiags, nvrClusterRecord.value[stationCode]?.stationName ?? '');
|
||||
extra: {},
|
||||
current: 1,
|
||||
size: 1,
|
||||
sort: 'id',
|
||||
order: 'descending',
|
||||
},
|
||||
{
|
||||
signal: abortController.value.signal,
|
||||
},
|
||||
);
|
||||
const gapSeconds = parseInt(records.at(0)?.value ?? '5');
|
||||
|
||||
abortController.value.abort();
|
||||
abortController.value = new AbortController();
|
||||
const data = await batchExportRecordCheckApi(
|
||||
{
|
||||
checkDuration: 90,
|
||||
gapSeconds,
|
||||
stationCode: params.stations.map((station) => station.code),
|
||||
},
|
||||
{
|
||||
signal: abortController.value.signal,
|
||||
},
|
||||
);
|
||||
|
||||
return data;
|
||||
} finally {
|
||||
window.$message.destroyAll();
|
||||
clearTimeout(timer);
|
||||
}
|
||||
},
|
||||
onSuccess: (data, { stations }) => {
|
||||
const time = dayjs().format('YYYY-MM-DD_HH-mm-ss');
|
||||
let stationName = '';
|
||||
if (stations.length === 1) {
|
||||
const name = stations.at(0)?.name;
|
||||
if (!!name) {
|
||||
stationName = `${name}_`;
|
||||
}
|
||||
}
|
||||
downloadByData(data, `${stationName}录像缺失记录_${time}.xlsx`);
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isCancel(error)) return;
|
||||
@@ -73,6 +87,7 @@ const { mutate: exportRecordDiags, isPending: exporting } = useMutation({
|
||||
});
|
||||
|
||||
const onAfterLeave = () => {
|
||||
abortController.value.abort();
|
||||
emit('afterLeave');
|
||||
};
|
||||
</script>
|
||||
@@ -81,17 +96,22 @@ const onAfterLeave = () => {
|
||||
<NModal v-model:show="show" preset="card" title="导出录像诊断" @after-leave="onAfterLeave" style="width: 800px">
|
||||
<template #default>
|
||||
<NScrollbar style="height: 300px">
|
||||
<NSpin size="small" :show="exporting">
|
||||
<NSpin size="small" :show="batchExporting">
|
||||
<NGrid :cols="6">
|
||||
<template v-for="({ stationName, clusters }, code) in nvrClusterRecord" :key="code">
|
||||
<template v-for="station in stations" :key="station.code">
|
||||
<NGridItem>
|
||||
<NButton text type="info" style="height: 30px" @click="() => exportRecordDiags({ clusters, stationCode: code })">{{ stationName }}</NButton>
|
||||
<NButton text type="info" style="height: 30px" @click="() => batchExportRecordCheck({ stations: [station] })">{{ station.name }}</NButton>
|
||||
</NGridItem>
|
||||
</template>
|
||||
</NGrid>
|
||||
</NSpin>
|
||||
</NScrollbar>
|
||||
</template>
|
||||
<template #action>
|
||||
<NFlex justify="flex-end" align="center">
|
||||
<NButton secondary :loading="batchExporting" @click="() => batchExportRecordCheck({ stations })">导出全部</NButton>
|
||||
</NFlex>
|
||||
</template>
|
||||
</NModal>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { Station, StationAlarms, StationDevices } from '@/apis';
|
||||
import { DEVICE_TYPE_LITERALS } from '@/enums';
|
||||
import { usePermission } from '@/composables';
|
||||
import { DEVICE_TYPE_LITERALS, PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import axios from 'axios';
|
||||
import dayjs from 'dayjs';
|
||||
import { isFunction } from 'es-toolkit';
|
||||
@@ -24,6 +25,8 @@ const emit = defineEmits<{
|
||||
clickConfig: [station: Station];
|
||||
}>();
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { station, devices, alarms, selectable } = toRefs(props);
|
||||
|
||||
const onlineDeviceCount = computed(() => {
|
||||
@@ -71,7 +74,7 @@ const openDeviceConfigModal = () => {
|
||||
emit('clickConfig', station.value);
|
||||
};
|
||||
|
||||
const dropdownOptions: DropdownOption[] = [
|
||||
const dropdownOptions = computed<DropdownOption[]>(() => [
|
||||
{
|
||||
label: '视频平台',
|
||||
key: 'video-platform',
|
||||
@@ -80,9 +83,10 @@ const dropdownOptions: DropdownOption[] = [
|
||||
{
|
||||
label: '设备配置',
|
||||
key: 'device-config',
|
||||
show: hasPermission(station.value.code, PERMISSION_TYPE_LITERALS.OPERATION),
|
||||
onSelect: openDeviceConfigModal,
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
const onSelectDropdownOption = (key: string, option: DropdownOption) => {
|
||||
const onSelect = option['onSelect'];
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { Station, SyncCameraResult } from '@/apis';
|
||||
import { useStationStore } from '@/stores';
|
||||
import { usePermissionStore } from '@/stores';
|
||||
import { watchDebounced } from '@vueuse/core';
|
||||
import { EditIcon, PlusCircleIcon, Trash2Icon } from 'lucide-vue-next';
|
||||
import { NFlex, NIcon, NList, NListItem, NModal, NScrollbar, NStatistic, NText, NThing } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, ref, toRefs } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -15,8 +14,8 @@ const emit = defineEmits<{
|
||||
afterLeave: [];
|
||||
}>();
|
||||
|
||||
const stationStore = useStationStore();
|
||||
const { stations } = storeToRefs(stationStore);
|
||||
const permissionStore = usePermissionStore();
|
||||
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||
|
||||
const { syncCameraResult } = toRefs(props);
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { usePermission } from '../permission';
|
||||
import { deleteCameraIgnoreApi, pageCameraIgnoreApi, saveCameraIgnoreApi, updateDeviceAlarmLogApi, type NdmDeviceAlarmLogResultVO } from '@/apis';
|
||||
import { DEVICE_TYPE_LITERALS, tryGetDeviceType } from '@/enums';
|
||||
import { DEVICE_TYPE_LITERALS, PERMISSION_TYPE_LITERALS, tryGetDeviceType } from '@/enums';
|
||||
import { parseErrorFeedback } from '@/utils';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { NButton, NFlex, NPopconfirm, type DataTableColumn, type DataTableRowData } from 'naive-ui';
|
||||
import { h, type Ref } from 'vue';
|
||||
|
||||
export const useAlarmActionColumn = (tableData: Ref<DataTableRowData[]>) => {
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const { mutate: confirmAlarm } = useMutation({
|
||||
mutationFn: async (params: { id: string | null }) => {
|
||||
const { id } = params;
|
||||
@@ -115,7 +118,9 @@ export const useAlarmActionColumn = (tableData: Ref<DataTableRowData[]>) => {
|
||||
default: () => '确认告警?',
|
||||
},
|
||||
),
|
||||
tryGetDeviceType(rowData.deviceType) === DEVICE_TYPE_LITERALS.ndmCamera && [
|
||||
tryGetDeviceType(rowData.deviceType) === DEVICE_TYPE_LITERALS.ndmCamera &&
|
||||
rowData.stationCode &&
|
||||
hasPermission(rowData.stationCode, PERMISSION_TYPE_LITERALS.OPERATION) && [
|
||||
h(
|
||||
NPopconfirm,
|
||||
{
|
||||
|
||||
@@ -1,39 +1,33 @@
|
||||
import type { LineDevices, NdmDeviceResultVO, Station } from '@/apis';
|
||||
import { tryGetDeviceType, type DeviceType } from '@/enums';
|
||||
import { useDeviceStore } from '@/stores';
|
||||
import { watchDebounced } from '@vueuse/core';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onMounted, ref, toValue, watch, type MaybeRefOrGetter } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
export const useDeviceSelection = (options?: { syncRoute?: MaybeRefOrGetter<boolean> }) => {
|
||||
const { syncRoute } = options ?? {};
|
||||
|
||||
export const useDeviceSelection = () => {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const deviceStore = useDeviceStore();
|
||||
const { lineDevices } = storeToRefs(deviceStore);
|
||||
|
||||
const selectedStationCode = ref<Station['code']>();
|
||||
const selectedDeviceType = ref<DeviceType>();
|
||||
const selectedDevice = ref<NdmDeviceResultVO>();
|
||||
|
||||
const initFromRoute = (lineDevices: LineDevices) => {
|
||||
const { stationCode, deviceType, deviceDbId } = route.query;
|
||||
if (stationCode) {
|
||||
selectedStationCode.value = stationCode as Station['code'];
|
||||
// 从路由参数同步选中的车站、设备类型以及设备
|
||||
const syncFromRoute = (lineDevices: LineDevices) => {
|
||||
// console.log('sync from route');
|
||||
const { stationCode: routeStationCode, deviceType: routeDeviceType, deviceDbId: routeDeviceDbId } = route.query;
|
||||
if (routeStationCode) {
|
||||
selectedStationCode.value = routeStationCode as Station['code'];
|
||||
}
|
||||
if (deviceType) {
|
||||
selectedDeviceType.value = deviceType as DeviceType;
|
||||
if (routeDeviceType) {
|
||||
selectedDeviceType.value = routeDeviceType as DeviceType;
|
||||
}
|
||||
if (deviceDbId && selectedStationCode.value && selectedDeviceType.value) {
|
||||
const selectedDeviceDbId = deviceDbId as string;
|
||||
if (routeDeviceDbId && selectedStationCode.value && selectedDeviceType.value) {
|
||||
const selectedDeviceDbId = routeDeviceDbId as string;
|
||||
const stationDevices = lineDevices[selectedStationCode.value];
|
||||
if (stationDevices) {
|
||||
const devices = stationDevices[selectedDeviceType.value];
|
||||
if (devices) {
|
||||
const device = devices.find((device) => device.id === selectedDeviceDbId);
|
||||
const classifiedDevices = stationDevices[selectedDeviceType.value];
|
||||
if (classifiedDevices) {
|
||||
const device = classifiedDevices.find((device) => device.id === selectedDeviceDbId);
|
||||
if (device) {
|
||||
selectedDevice.value = device;
|
||||
}
|
||||
@@ -51,7 +45,9 @@ export const useDeviceSelection = (options?: { syncRoute?: MaybeRefOrGetter<bool
|
||||
}
|
||||
};
|
||||
|
||||
// 将选中的车站、设备类型以及设备ID同步到路由参数
|
||||
const syncToRoute = () => {
|
||||
// console.log('sync to route');
|
||||
const query = { ...route.query };
|
||||
// 当选中的设备发生变化时,删除fromPage参数
|
||||
if (selectedDevice.value?.id && route.query.deviceDbId !== selectedDevice.value.id) {
|
||||
@@ -69,39 +65,13 @@ export const useDeviceSelection = (options?: { syncRoute?: MaybeRefOrGetter<bool
|
||||
router.replace({ query });
|
||||
};
|
||||
|
||||
watch(selectedDevice, () => {
|
||||
if (toValue(syncRoute)) {
|
||||
syncToRoute();
|
||||
}
|
||||
});
|
||||
|
||||
// lineDevices是shallowRef,因此需要深度侦听才能获取内部变化,
|
||||
// 而单纯的深度侦听又可能会引发性能问题,因此尝试使用防抖侦听
|
||||
watchDebounced(
|
||||
lineDevices,
|
||||
(newLineDevices) => {
|
||||
if (toValue(syncRoute)) {
|
||||
initFromRoute(newLineDevices);
|
||||
}
|
||||
},
|
||||
{
|
||||
debounce: 500,
|
||||
deep: true,
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (toValue(syncRoute)) {
|
||||
initFromRoute(lineDevices.value);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
selectedStationCode,
|
||||
selectedDeviceType,
|
||||
selectedDevice,
|
||||
|
||||
initFromRoute,
|
||||
syncFromRoute,
|
||||
syncToRoute,
|
||||
selectDevice,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import type { MaybeRefOrGetter } from 'vue';
|
||||
import { useDeviceManagement } from './use-device-management';
|
||||
import { useDeviceSelection } from './use-device-selection';
|
||||
|
||||
export const useDeviceTree = (options?: { syncRoute?: MaybeRefOrGetter<boolean> }) => {
|
||||
const { syncRoute } = options ?? {};
|
||||
|
||||
const deviceSelection = useDeviceSelection({ syncRoute });
|
||||
export const useDeviceTree = () => {
|
||||
const deviceSelection = useDeviceSelection();
|
||||
const deviceManagement = useDeviceManagement();
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './alarm';
|
||||
export * from './device';
|
||||
export * from './permission';
|
||||
export * from './query';
|
||||
export * from './station';
|
||||
export * from './stomp';
|
||||
|
||||
1
src/composables/permission/index.ts
Normal file
1
src/composables/permission/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './use-permission';
|
||||
14
src/composables/permission/use-permission.ts
Normal file
14
src/composables/permission/use-permission.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { PermissionType } from '@/enums';
|
||||
import { usePermissionStore } from '@/stores';
|
||||
|
||||
export const usePermission = () => {
|
||||
const permissionStore = usePermissionStore();
|
||||
|
||||
const hasPermission = (stationCode: string, permissionType: PermissionType) => {
|
||||
return !!permissionStore.permissions[stationCode]?.includes(permissionType);
|
||||
};
|
||||
|
||||
return {
|
||||
hasPermission,
|
||||
};
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './use-line-alarms-query';
|
||||
export * from './use-line-devices-query';
|
||||
export * from './use-line-stations-query';
|
||||
export * from './use-user-permission-query';
|
||||
export * from './use-verify-user-query';
|
||||
export * from './use-version-check-query';
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { initStationAlarms, pageDeviceAlarmLogApi, type Station } from '@/apis';
|
||||
import { LINE_ALARMS_QUERY_KEY, STATION_ALARMS_MUTATION_KEY } from '@/constants';
|
||||
import { tryGetDeviceType } from '@/enums';
|
||||
import { useAlarmStore, useStationStore } from '@/stores';
|
||||
import { useAlarmStore, usePermissionStore } from '@/stores';
|
||||
import { parseErrorFeedback } from '@/utils';
|
||||
import { CancelledError, useMutation, useQuery } from '@tanstack/vue-query';
|
||||
import { isCancel } from 'axios';
|
||||
import dayjs from 'dayjs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
|
||||
export const useStationAlarmsMutation = () => {
|
||||
@@ -69,8 +68,9 @@ export const useStationAlarmsMutation = () => {
|
||||
* @see [use-line-stations-query.ts](./use-line-stations-query.ts)
|
||||
*/
|
||||
export const useLineAlarmsQuery = () => {
|
||||
const stationStore = useStationStore();
|
||||
const { stations } = storeToRefs(stationStore);
|
||||
const permissionStore = usePermissionStore();
|
||||
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||
|
||||
const { mutateAsync: getStationAlarms } = useStationAlarmsMutation();
|
||||
|
||||
return useQuery({
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { getAllDevicesApi, initStationDevices, type Station } from '@/apis';
|
||||
import { LINE_DEVICES_QUERY_KEY, STATION_DEVICES_MUTATION_KEY } from '@/constants';
|
||||
import { useDeviceStore, useStationStore } from '@/stores';
|
||||
import { useDeviceStore, usePermissionStore } from '@/stores';
|
||||
import { parseErrorFeedback } from '@/utils';
|
||||
import { CancelledError, useMutation, useQuery } from '@tanstack/vue-query';
|
||||
import { isCancel } from 'axios';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
|
||||
export const useStationDevicesMutation = () => {
|
||||
@@ -36,8 +35,9 @@ export const useStationDevicesMutation = () => {
|
||||
* @see [use-line-stations-query.ts](./use-line-stations-query.ts)
|
||||
*/
|
||||
export const useLineDevicesQuery = () => {
|
||||
const stationStore = useStationStore();
|
||||
const { stations } = storeToRefs(stationStore);
|
||||
const permissionStore = usePermissionStore();
|
||||
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||
|
||||
const { mutateAsync: getStationDevices } = useStationDevicesMutation();
|
||||
|
||||
return useQuery({
|
||||
|
||||
@@ -7,8 +7,6 @@ import axios, { isCancel } from 'axios';
|
||||
import dayjs from 'dayjs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import { useLineDevicesQuery } from './use-line-devices-query';
|
||||
import { useLineAlarmsQuery } from './use-line-alarms-query';
|
||||
|
||||
export const useLineStationsMutation = () => {
|
||||
const stationStore = useStationStore();
|
||||
@@ -17,12 +15,13 @@ export const useLineStationsMutation = () => {
|
||||
mutationKey: [LINE_STATIONS_MUTATION_KEY],
|
||||
mutationFn: async (params: { signal?: AbortSignal }) => {
|
||||
const { signal } = params;
|
||||
const { data: ndmStationList } = await axios.get<{ code: string; name: string }[]>(`/minio/ndm/ndm-stations.json?_t=${dayjs().unix()}`, { signal });
|
||||
const { data: ndmStationList } = await axios.get<Omit<Station, 'online' | 'ip'>[]>(`/minio/ndm/ndm-stations.json?_t=${dayjs().unix()}`, { signal });
|
||||
const stations = ndmStationList.map<Station>((station) => ({
|
||||
code: station.code ?? '',
|
||||
name: station.name ?? '',
|
||||
online: false,
|
||||
ip: '',
|
||||
occ: station.occ,
|
||||
}));
|
||||
const verifyList = await batchVerifyApi({ signal });
|
||||
return stations.map((station) => ({
|
||||
@@ -48,8 +47,6 @@ export const useLineStationsQuery = () => {
|
||||
const { pollingStations } = storeToRefs(settingStore);
|
||||
const { requestInterval } = getAppEnvConfig();
|
||||
const { mutateAsync: getLineStations } = useLineStationsMutation();
|
||||
const { refetch: refetchLineDevicesQuery } = useLineDevicesQuery();
|
||||
const { refetch: refetchLineAlarmsQuery } = useLineAlarmsQuery();
|
||||
|
||||
return useQuery({
|
||||
queryKey: computed(() => [LINE_STATIONS_QUERY_KEY]),
|
||||
@@ -62,12 +59,6 @@ export const useLineStationsQuery = () => {
|
||||
const endTime = performance.now();
|
||||
console.log(`${LINE_STATIONS_QUERY_KEY}: ${endTime - startTime} ms`);
|
||||
|
||||
if (!pollingStations.value) return null;
|
||||
await refetchLineDevicesQuery();
|
||||
|
||||
if (!pollingStations.value) return null;
|
||||
await refetchLineAlarmsQuery();
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
67
src/composables/query/use-user-permission-query.ts
Normal file
67
src/composables/query/use-user-permission-query.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { useLineDevicesQuery } from './use-line-devices-query';
|
||||
import { useLineAlarmsQuery } from './use-line-alarms-query';
|
||||
import { pagePermissionApi } from '@/apis';
|
||||
import { USER_PERMISSION_QUERY_KEY } from '@/constants';
|
||||
import { PERMISSION_TYPE_LITERALS } from '@/enums';
|
||||
import { usePermissionStore, useSettingStore, useStationStore, useUserStore } from '@/stores';
|
||||
import { useQuery } from '@tanstack/vue-query';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, watch } from 'vue';
|
||||
import { useLineStationsQuery } from './use-line-stations-query';
|
||||
|
||||
export const useUserPermissionQuery = () => {
|
||||
const settingStore = useSettingStore();
|
||||
const { pollingStations, activeRequests } = storeToRefs(settingStore);
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { userInfo } = storeToRefs(userStore);
|
||||
|
||||
const stationStore = useStationStore();
|
||||
const { stations } = storeToRefs(stationStore);
|
||||
|
||||
const permissionStore = usePermissionStore();
|
||||
const { permissions } = storeToRefs(permissionStore);
|
||||
|
||||
const { dataUpdatedAt: stationsUpdatedTime } = useLineStationsQuery();
|
||||
const { refetch: refetchLineDevicesQuery } = useLineDevicesQuery();
|
||||
const { refetch: refetchLineAlarmsQuery } = useLineAlarmsQuery();
|
||||
|
||||
watch([permissions, stationsUpdatedTime], async ([newPermissions, newUpdatedTime], [oldPermissions, oldUpdatedTime]) => {
|
||||
const newPermissionsJson = JSON.stringify(newPermissions);
|
||||
const oldPermissionsJson = JSON.stringify(oldPermissions);
|
||||
if (newPermissionsJson === oldPermissionsJson && newUpdatedTime === oldUpdatedTime) return;
|
||||
// 设备查询和告警查询依赖pollingEnabdled
|
||||
// 当关闭轮询时,只会取消当前正在执行的查询,
|
||||
// 所以如果在关闭轮询时refetch还未执行,那么这一次取消就是无效的,refetch依然会执行,
|
||||
// 所以在每个refetch被调用前都需要检查pollingEnabled,否则就可能会取消失败
|
||||
if (!pollingStations.value) return;
|
||||
await refetchLineDevicesQuery();
|
||||
if (!pollingStations.value) return;
|
||||
await refetchLineAlarmsQuery();
|
||||
});
|
||||
|
||||
return useQuery({
|
||||
queryKey: computed(() => [USER_PERMISSION_QUERY_KEY]),
|
||||
// 启用【车站轮询】或【主动请求】时,都认为查询被启用
|
||||
enabled: computed(() => (pollingStations.value || activeRequests.value) && userInfo.value?.['employeeId'] && stations.value.length > 0),
|
||||
// 当启用【车站轮询】时,刷新间隔为10秒,缓存时间为5秒
|
||||
refetchInterval: computed(() => (pollingStations.value ? 10 * 1000 : undefined)),
|
||||
staleTime: computed(() => (pollingStations.value ? 5 * 1000 : undefined)),
|
||||
queryFn: async ({ signal }) => {
|
||||
const { records } = await pagePermissionApi(
|
||||
{
|
||||
model: {
|
||||
employeeId: userInfo.value['employeeId'],
|
||||
},
|
||||
current: 1,
|
||||
size: Object.keys(PERMISSION_TYPE_LITERALS).length * stations.value.length,
|
||||
},
|
||||
{
|
||||
signal,
|
||||
},
|
||||
);
|
||||
permissionStore.setPermRecords(records);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,8 @@
|
||||
import { usePermission } from '../permission';
|
||||
import { type Station } from '@/apis';
|
||||
import { PERMISSION_TYPE_LITERALS, type PermissionType } from '@/enums';
|
||||
import { objectEntries } from '@vueuse/core';
|
||||
import type { CheckboxProps } from 'naive-ui';
|
||||
import { computed, ref, watch, type Ref } from 'vue';
|
||||
|
||||
type BatchActionKey = 'export-icmp' | 'export-record' | 'sync-camera' | 'sync-nvr';
|
||||
@@ -6,29 +10,36 @@ type BatchActionKey = 'export-icmp' | 'export-record' | 'sync-camera' | 'sync-nv
|
||||
type BatchAction = {
|
||||
label: string;
|
||||
key: BatchActionKey;
|
||||
permission: PermissionType;
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
export const useBatchActions = (stations: Ref<Station[]>, abortController?: Ref<AbortController | undefined>) => {
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const batchActions = ref<BatchAction[]>([
|
||||
{
|
||||
label: '导出设备状态',
|
||||
key: 'export-icmp',
|
||||
permission: PERMISSION_TYPE_LITERALS.VIEW,
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
label: '导出录像诊断',
|
||||
key: 'export-record',
|
||||
permission: PERMISSION_TYPE_LITERALS.VIEW,
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
label: '同步摄像机',
|
||||
key: 'sync-camera',
|
||||
permission: PERMISSION_TYPE_LITERALS.OPERATION,
|
||||
active: false,
|
||||
},
|
||||
{
|
||||
label: '同步录像机通道',
|
||||
key: 'sync-nvr',
|
||||
permission: PERMISSION_TYPE_LITERALS.OPERATION,
|
||||
active: false,
|
||||
},
|
||||
]);
|
||||
@@ -39,11 +50,33 @@ export const useBatchActions = (stations: Ref<Station[]>, abortController?: Ref<
|
||||
|
||||
const selectableStations = computed(() => {
|
||||
if (!selectedAction.value) return [];
|
||||
return stations.value;
|
||||
const result: Station[] = [];
|
||||
if (selectedAction.value.permission === PERMISSION_TYPE_LITERALS.VIEW) {
|
||||
result.push(...stations.value.filter((station) => hasPermission(station.code, PERMISSION_TYPE_LITERALS.VIEW)));
|
||||
}
|
||||
if (selectedAction.value.permission === PERMISSION_TYPE_LITERALS.OPERATION) {
|
||||
result.push(...stations.value.filter((station) => hasPermission(station.code, PERMISSION_TYPE_LITERALS.OPERATION)));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
const stationSelection = ref<Record<Station['code'], boolean>>({});
|
||||
|
||||
const selectionProps = computed<CheckboxProps>(() => {
|
||||
const selectableStationsLength = selectableStations.value.length;
|
||||
const selectedStationsLength = objectEntries(stationSelection.value).filter(([, selected]) => selected).length;
|
||||
|
||||
const disabled = selectableStationsLength === 0;
|
||||
const checked = selectableStationsLength > 0 && selectedStationsLength === selectableStationsLength;
|
||||
const indeterminate = selectableStationsLength > 0 && selectedStationsLength > 0 && selectedStationsLength < selectableStationsLength;
|
||||
|
||||
return {
|
||||
disabled,
|
||||
checked,
|
||||
indeterminate,
|
||||
};
|
||||
});
|
||||
|
||||
const toggleSelectAction = (action: BatchAction) => {
|
||||
batchActions.value.forEach((batchAction) => {
|
||||
if (batchAction.key === action.key) {
|
||||
@@ -95,6 +128,8 @@ export const useBatchActions = (stations: Ref<Station[]>, abortController?: Ref<
|
||||
selectableStations,
|
||||
stationSelection,
|
||||
|
||||
selectionProps,
|
||||
|
||||
toggleSelectAction,
|
||||
toggleSelectAllStations,
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { NdmDeviceAlarmLogResultVO, Station, SyncCameraResult } from '@/apis';
|
||||
import { ALARM_TOPIC, SYNC_CAMERA_STATUS_TOPIC } from '@/constants';
|
||||
import { useSettingStore, useStationStore, useUnreadStore } from '@/stores';
|
||||
import { ALARM_TOPIC, PERMISSION_TOPIC, SYNC_CAMERA_STATUS_TOPIC } from '@/constants';
|
||||
import { useSettingStore, useStationStore, useUnreadStore, useUserStore } from '@/stores';
|
||||
import { Client } from '@stomp/stompjs';
|
||||
import { watchDebounced } from '@vueuse/core';
|
||||
import destr from 'destr';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { useStationAlarmsMutation } from '../query';
|
||||
import { useStationAlarmsMutation, useUserPermissionQuery } from '../query';
|
||||
|
||||
const getBrokerUrl = () => {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
@@ -26,6 +26,10 @@ export const useStompClient = () => {
|
||||
const settingStore = useSettingStore();
|
||||
const { subscribeMessages } = storeToRefs(settingStore);
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { userInfo } = storeToRefs(userStore);
|
||||
|
||||
const { refetch: refetchUserPermissionQuery } = useUserPermissionQuery();
|
||||
const { mutate: refreshStationAlarms } = useStationAlarmsMutation();
|
||||
|
||||
const stompClient = ref<Client | null>(null);
|
||||
@@ -47,6 +51,11 @@ export const useStompClient = () => {
|
||||
unreadStore.pushUnreadAlarm(alarm);
|
||||
}
|
||||
});
|
||||
stompClient.value?.subscribe(PERMISSION_TOPIC, (message) => {
|
||||
const employeeId = destr<string>(message.body);
|
||||
if (userInfo.value?.['employeeId'] !== employeeId) return;
|
||||
refetchUserPermissionQuery();
|
||||
});
|
||||
stompClient.value?.subscribe(SYNC_CAMERA_STATUS_TOPIC, (message) => {
|
||||
const { stationCode, startTime, endTime, insertList, updateList, deleteList } = destr<SyncCameraResult>(message.body);
|
||||
syncCameraResult.value[stationCode] = { stationCode, startTime, endTime, insertList, updateList, deleteList };
|
||||
@@ -55,6 +64,7 @@ export const useStompClient = () => {
|
||||
onDisconnect: () => {
|
||||
console.log('Stomp连接断开');
|
||||
stompClient.value?.unsubscribe(ALARM_TOPIC);
|
||||
stompClient.value?.unsubscribe(PERMISSION_TOPIC);
|
||||
stompClient.value?.unsubscribe(SYNC_CAMERA_STATUS_TOPIC);
|
||||
},
|
||||
onStompError: (frame) => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const LINE_ALARMS_QUERY_KEY = 'line-alarms';
|
||||
export const LINE_DEVICES_QUERY_KEY = 'line-devices';
|
||||
export const LINE_STATIONS_QUERY_KEY = 'line-stations';
|
||||
export const USER_PERMISSION_QUERY_KEY = 'user-permission';
|
||||
export const VERIFY_USER_QUERY_KEY = 'verify-user';
|
||||
export const VERSION_CHECK_QUERY_KEY = 'version-check';
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export const ALARM_TOPIC = '/topic/deviceAlarm';
|
||||
|
||||
export const PERMISSION_TOPIC = '/topic/permission';
|
||||
export const SYNC_CAMERA_STATUS_TOPIC = '/topic/syncCameraStatus';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const NDM_ALARM_STORE_ID = 'ndm-alarm-store';
|
||||
export const NDM_DEVICE_STORE_ID = 'ndm-device-store';
|
||||
export const NDM_PERMISSION_STORE_ID = 'ndm-permission-store';
|
||||
export const NDM_POLLIING_STORE_ID = 'ndm-polling-store';
|
||||
export const NDM_SETTING_STORE_ID = 'ndm-setting-store';
|
||||
export const NDM_STATION_STORE_ID = 'ndm-station-store';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './alarm-type';
|
||||
export * from './device-type';
|
||||
export * from './fault-level';
|
||||
export * from './permission-type';
|
||||
|
||||
13
src/enums/permission-type.ts
Normal file
13
src/enums/permission-type.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export const PERMISSION_TYPE_LITERALS = {
|
||||
VIEW: 'VIEW',
|
||||
OPERATION: 'OPERATION',
|
||||
} as const;
|
||||
|
||||
export type PermissionType = keyof typeof PERMISSION_TYPE_LITERALS;
|
||||
|
||||
export const PERMISSION_TYPE_NAMES = {
|
||||
[PERMISSION_TYPE_LITERALS.VIEW]: '查看',
|
||||
[PERMISSION_TYPE_LITERALS.OPERATION]: '操作',
|
||||
} as const;
|
||||
|
||||
export type PermissionTypeEnum = typeof PERMISSION_TYPE_NAMES;
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { SettingsDrawer, SyncCameraResultModal } from '@/components';
|
||||
import { useLineStationsQuery, useStompClient, useVerifyUserQuery } from '@/composables';
|
||||
import { useLineStationsQuery, useStompClient, useUserPermissionQuery, useVerifyUserQuery } from '@/composables';
|
||||
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 { useIsFetching, useIsMutating } from '@tanstack/vue-query';
|
||||
import { ChevronDownIcon, ChevronsLeftIcon, ChevronsRightIcon, ComputerIcon, LogOutIcon, LogsIcon, MapPinIcon, SettingsIcon, SirenIcon } from 'lucide-vue-next';
|
||||
import { ChevronDownIcon, ChevronsLeftIcon, ChevronsRightIcon, ComputerIcon, KeyRoundIcon, LogOutIcon, LogsIcon, MapPinIcon, SettingsIcon, SirenIcon } from 'lucide-vue-next';
|
||||
import {
|
||||
NBadge,
|
||||
NButton,
|
||||
@@ -29,7 +29,7 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { userInfo } = storeToRefs(userStore);
|
||||
const { userInfo, isLamp } = storeToRefs(userStore);
|
||||
|
||||
const unreadStore = useUnreadStore();
|
||||
const { unreadAlarmCount } = storeToRefs(unreadStore);
|
||||
@@ -41,6 +41,7 @@ const { syncCameraResult, afterCheckSyncCameraResult } = useStompClient();
|
||||
|
||||
useVerifyUserQuery();
|
||||
useLineStationsQuery();
|
||||
useUserPermissionQuery();
|
||||
|
||||
// 全局loading状态依赖于轮询query的queryKey以及相关的mutationKey
|
||||
const queryingCount = useIsFetching({
|
||||
@@ -63,7 +64,7 @@ const onToggleMenuCollapsed = () => {
|
||||
menuCollpased.value = !menuCollpased.value;
|
||||
};
|
||||
|
||||
const menuOptions: MenuOption[] = [
|
||||
const menuOptions = computed<MenuOption[]>(() => [
|
||||
{
|
||||
label: () => h(RouterLink, { to: '/station' }, { default: () => '车站状态' }),
|
||||
key: '/station',
|
||||
@@ -104,7 +105,13 @@ const menuOptions: MenuOption[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
{
|
||||
label: () => h(RouterLink, { to: '/permission' }, { default: () => '权限管理' }),
|
||||
key: '/permission',
|
||||
show: isLamp.value,
|
||||
icon: renderIcon(KeyRoundIcon),
|
||||
},
|
||||
]);
|
||||
|
||||
const dropdownOptions: DropdownOption[] = [
|
||||
{
|
||||
|
||||
@@ -16,8 +16,9 @@ const NDM_TYPES: Record<string, DeviceType> = {
|
||||
|
||||
<script setup lang="ts">
|
||||
import { deleteCameraIgnoreApi, pageCameraIgnoreApi, type NdmCameraIgnore, type NdmCameraIgnoreResultVO, type PageQueryExtra, type Station } from '@/apis';
|
||||
import { DEVICE_TYPE_LITERALS, DEVICE_TYPE_NAMES, type DeviceType } from '@/enums';
|
||||
import { useDeviceStore, useStationStore } from '@/stores';
|
||||
import { usePermission } from '@/composables';
|
||||
import { DEVICE_TYPE_LITERALS, DEVICE_TYPE_NAMES, PERMISSION_TYPE_LITERALS, type DeviceType } from '@/enums';
|
||||
import { useDeviceStore, usePermissionStore } from '@/stores';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { isCancel } from 'axios';
|
||||
import {
|
||||
@@ -46,8 +47,13 @@ interface SearchFields extends PageQueryExtra<NdmCameraIgnore> {
|
||||
// deviceId_like?: string;
|
||||
}
|
||||
|
||||
const stationStore = useStationStore();
|
||||
const { stations } = storeToRefs(stationStore);
|
||||
const permissionStore = usePermissionStore();
|
||||
const { permissions } = storeToRefs(permissionStore);
|
||||
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||
|
||||
const deviceStore = useDeviceStore();
|
||||
const { lineDevices } = storeToRefs(deviceStore);
|
||||
|
||||
@@ -64,6 +70,14 @@ const stationSelectOptions = computed<SelectOption[]>(() => {
|
||||
// }));
|
||||
// });
|
||||
|
||||
// 权限变化时,需要刷新表格数据
|
||||
watch(permissions, (newPermissions, oldPermissions) => {
|
||||
const oldPermissionsJson = JSON.stringify(oldPermissions);
|
||||
const newPermissionsJson = JSON.stringify(newPermissions);
|
||||
if (oldPermissionsJson === newPermissionsJson) return;
|
||||
onClickReset();
|
||||
});
|
||||
|
||||
const searchFields = ref<SearchFields>({});
|
||||
const resetSearchFields = () => {
|
||||
searchFields.value = {};
|
||||
@@ -84,7 +98,7 @@ watch(searchFields, () => {
|
||||
searchFieldsChanged.value = true;
|
||||
});
|
||||
|
||||
const tableColumns: DataTableColumns<NdmCameraIgnoreResultVO> = [
|
||||
const tableColumns = computed<DataTableColumns<NdmCameraIgnoreResultVO>>(() => [
|
||||
{ title: '忽略时间', key: 'createdTime', align: 'center' },
|
||||
// { title: '更新时间', key: 'updatedTime' },
|
||||
{
|
||||
@@ -142,6 +156,11 @@ const tableColumns: DataTableColumns<NdmCameraIgnoreResultVO> = [
|
||||
align: 'center',
|
||||
width: 120,
|
||||
render: (rowData) => {
|
||||
const { deviceId } = rowData;
|
||||
if (!deviceId) return null;
|
||||
const stationCode = deviceId.slice(0, 4);
|
||||
if (!stationCode) return null;
|
||||
if (!hasPermission(stationCode, PERMISSION_TYPE_LITERALS.OPERATION)) return null;
|
||||
return h(
|
||||
NPopconfirm,
|
||||
{
|
||||
@@ -167,7 +186,7 @@ const tableColumns: DataTableColumns<NdmCameraIgnoreResultVO> = [
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
const { mutate: cancelIgnore } = useMutation({
|
||||
mutationFn: async (params: { id?: string; signal?: AbortSignal }) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { exportDeviceAlarmLogApi, pageDeviceAlarmLogApi, type NdmDeviceAlarmLog,
|
||||
import { useAlarmActionColumn, useCameraSnapColumn } from '@/composables';
|
||||
import { ALARM_TYPES, DEVICE_TYPE_CODES, DEVICE_TYPE_LITERALS, DEVICE_TYPE_NAMES, FAULT_LEVELS, tryGetDeviceType, type DeviceType } from '@/enums';
|
||||
import { renderAlarmDateCell, renderAlarmTypeCell, renderDeviceTypeCell, renderFaultLevelCell } from '@/helpers';
|
||||
import { useDeviceStore, useStationStore, useUnreadStore } from '@/stores';
|
||||
import { useDeviceStore, usePermissionStore, useUnreadStore } from '@/stores';
|
||||
import { downloadByData, parseErrorFeedback } from '@/utils';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { watchDebounced } from '@vueuse/core';
|
||||
@@ -44,8 +44,10 @@ interface SearchFields extends PageQueryExtra<NdmDeviceAlarmLog> {
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const stationStore = useStationStore();
|
||||
const { stations } = storeToRefs(stationStore);
|
||||
const permissionStore = usePermissionStore();
|
||||
const { permissions } = storeToRefs(permissionStore);
|
||||
|
||||
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||
|
||||
const deviceStore = useDeviceStore();
|
||||
const { lineDevices } = storeToRefs(deviceStore);
|
||||
@@ -78,6 +80,14 @@ const faultLevelSelectOptions = computed<SelectOption[]>(() => {
|
||||
}));
|
||||
});
|
||||
|
||||
// 权限变化时,需要刷新表格数据
|
||||
watch(permissions, (newPermissions, oldPermissions) => {
|
||||
const oldPermissionsJson = JSON.stringify(oldPermissions);
|
||||
const newPermissionsJson = JSON.stringify(newPermissions);
|
||||
if (oldPermissionsJson === newPermissionsJson) return;
|
||||
onClickReset();
|
||||
});
|
||||
|
||||
// 未读告警数量被清零时,代表从别的页面跳转过来,需要刷新告警表格数据
|
||||
const unreadCountCleared = computed(() => unreadAlarmCount.value === 0);
|
||||
watch(unreadCountCleared, (newValue, oldValue) => {
|
||||
@@ -125,14 +135,17 @@ const resetSearchFields = () => {
|
||||
const getExtraFields = (): PageQueryExtra<NdmDeviceAlarmLog> => {
|
||||
const stationCodeIn = searchFields.value.stationCode_in;
|
||||
const deviceTypeIn = searchFields.value.deviceType_in.flatMap((deviceType) => DEVICE_TYPE_CODES[deviceType as DeviceType]);
|
||||
const deviceNameLike = searchFields.value.deviceName_like;
|
||||
const alarmTypeIn = searchFields.value.alarmType_in;
|
||||
const faultLevelIn = searchFields.value.faultLevel_in;
|
||||
const alarmDateGe = searchFields.value.alarmDate[0];
|
||||
const alarmDateLe = searchFields.value.alarmDate[1];
|
||||
return {
|
||||
stationCode_in: stationCodeIn ? (stationCodeIn.length > 0 ? [...stationCodeIn] : undefined) : undefined,
|
||||
deviceType_in: deviceTypeIn ? (deviceTypeIn.length > 0 ? [...deviceTypeIn] : undefined) : undefined,
|
||||
deviceName_like: !!searchFields.value.deviceName_like ? searchFields.value.deviceName_like : undefined,
|
||||
alarmType_in: searchFields.value.alarmType_in.length > 0 ? [...searchFields.value.alarmType_in] : undefined,
|
||||
faultLevel_in: searchFields.value.faultLevel_in.length > 0 ? [...searchFields.value.faultLevel_in] : undefined,
|
||||
stationCode_in: stationCodeIn.length > 0 ? [...stationCodeIn] : stations.value.map((station) => station.code),
|
||||
deviceType_in: deviceTypeIn.length > 0 ? [...deviceTypeIn] : undefined,
|
||||
deviceName_like: deviceNameLike.length > 0 ? deviceNameLike : undefined,
|
||||
alarmType_in: alarmTypeIn.length > 0 ? [...alarmTypeIn] : undefined,
|
||||
faultLevel_in: faultLevelIn.length > 0 ? [...faultLevelIn] : undefined,
|
||||
alarmDate_ge: alarmDateGe,
|
||||
alarmDate_le: alarmDateLe,
|
||||
};
|
||||
|
||||
@@ -3,13 +3,12 @@ import type { NdmDeviceResultVO, Station } from '@/apis';
|
||||
import { DeviceRenderer, DeviceTree, type DeviceTreeProps } from '@/components';
|
||||
import type { UseDeviceSelectionReturn } from '@/composables';
|
||||
import { SELECT_DEVICE_FN_INJECTION_KEY } from '@/constants';
|
||||
import { useStationStore } from '@/stores';
|
||||
import { usePermissionStore } from '@/stores';
|
||||
import { NLayout, NLayoutContent, NLayoutSider } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { provide, ref } from 'vue';
|
||||
import { computed, provide, ref } from 'vue';
|
||||
|
||||
const stationStore = useStationStore();
|
||||
const { stations } = storeToRefs(stationStore);
|
||||
const permissionStore = usePermissionStore();
|
||||
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||
|
||||
const selectedStation = ref<Station>();
|
||||
const selectedDevice = ref<NdmDeviceResultVO>();
|
||||
|
||||
@@ -32,7 +32,7 @@ const callLogTypeOptions: SelectOption[] = [
|
||||
|
||||
<script setup lang="ts">
|
||||
import { exportCallLogApi, pageCallLogApi, type NdmCallLog, type NdmCallLogResultVO, type PageQueryExtra, type Station } from '@/apis';
|
||||
import { useStationStore } from '@/stores';
|
||||
import { usePermissionStore } from '@/stores';
|
||||
import { downloadByData, parseErrorFeedback } from '@/utils';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { isCancel } from 'axios';
|
||||
@@ -63,8 +63,11 @@ interface SearchFields extends PageQueryExtra<NdmCallLog> {
|
||||
createdTime: [string, string];
|
||||
}
|
||||
|
||||
const stationStore = useStationStore();
|
||||
const { stations, onlineStations } = storeToRefs(stationStore);
|
||||
const permissionStore = usePermissionStore();
|
||||
const { permissions } = storeToRefs(permissionStore);
|
||||
|
||||
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||
const onlineStations = computed(() => stations.value.filter((station) => station.online));
|
||||
|
||||
const stationSelectOptions = computed(() => {
|
||||
return stations.value.map<SelectOption>((station) => ({
|
||||
@@ -74,6 +77,14 @@ const stationSelectOptions = computed(() => {
|
||||
}));
|
||||
});
|
||||
|
||||
// 权限变化时,需要刷新表格数据
|
||||
watch(permissions, (newPermissions, oldPermissions) => {
|
||||
const oldPermissionsJson = JSON.stringify(oldPermissions);
|
||||
const newPermissionsJson = JSON.stringify(newPermissions);
|
||||
if (oldPermissionsJson === newPermissionsJson) return;
|
||||
onClickReset();
|
||||
});
|
||||
|
||||
const searchFields = ref<SearchFields>({
|
||||
logType_in: [],
|
||||
createdTime: [dayjs().startOf('date').subtract(1, 'week').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('date').format('YYYY-MM-DD HH:mm:ss')],
|
||||
|
||||
@@ -29,7 +29,7 @@ const vimpLogTypeOptions: SelectOption[] = [
|
||||
|
||||
<script setup lang="ts">
|
||||
import { exportVimpLogApi, pageVimpLogApi, type NdmVimpLog, type NdmVimpLogResultVO, type PageQueryExtra, type Station } from '@/apis';
|
||||
import { useStationStore } from '@/stores';
|
||||
import { usePermissionStore } from '@/stores';
|
||||
import { downloadByData, parseErrorFeedback } from '@/utils';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { isCancel } from 'axios';
|
||||
@@ -59,8 +59,11 @@ interface SearchFields extends PageQueryExtra<NdmVimpLog> {
|
||||
createdTime: [string, string];
|
||||
}
|
||||
|
||||
const stationStore = useStationStore();
|
||||
const { stations, onlineStations } = storeToRefs(stationStore);
|
||||
const permissionStore = usePermissionStore();
|
||||
const { permissions } = storeToRefs(permissionStore);
|
||||
|
||||
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||
const onlineStations = computed(() => stations.value.filter((station) => station.online));
|
||||
|
||||
const stationSelectOptions = computed(() => {
|
||||
return stations.value.map<SelectOption>((station) => ({
|
||||
@@ -70,6 +73,14 @@ const stationSelectOptions = computed(() => {
|
||||
}));
|
||||
});
|
||||
|
||||
// 权限变化时,需要刷新表格数据
|
||||
watch(permissions, (newPermissions, oldPermissions) => {
|
||||
const oldPermissionsJson = JSON.stringify(oldPermissions);
|
||||
const newPermissionsJson = JSON.stringify(newPermissions);
|
||||
if (oldPermissionsJson === newPermissionsJson) return;
|
||||
onClickReset();
|
||||
});
|
||||
|
||||
const searchFields = ref<SearchFields>({
|
||||
logType_in: [],
|
||||
createdTime: [dayjs().startOf('date').subtract(1, 'week').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('date').format('YYYY-MM-DD HH:mm:ss')] as [string, string],
|
||||
|
||||
184
src/pages/permission/permission-page.vue
Normal file
184
src/pages/permission/permission-page.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<script setup lang="ts">
|
||||
import { pageBaseEmployeeApi, type BaseEmployeePageQuery, type BaseEmployeeResultVO } from '@/apis';
|
||||
import { PermissionConfigModal } from '@/components';
|
||||
import { parseErrorFeedback } from '@/utils';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { isCancel } from 'axios';
|
||||
import { KeyIcon } from 'lucide-vue-next';
|
||||
import { NButton, NDataTable, NFlex, NForm, NFormItemGi, NGrid, NGridItem, NInput, type DataTableColumns, type DataTableRowData, type PaginationProps } from 'naive-ui';
|
||||
import { h, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
||||
|
||||
interface SearchFields extends BaseEmployeePageQuery {}
|
||||
|
||||
const searchFields = ref<SearchFields>({});
|
||||
const resetSearchFields = () => {
|
||||
searchFields.value = {
|
||||
realName: '',
|
||||
};
|
||||
};
|
||||
const getModelFields = (): BaseEmployeePageQuery => {
|
||||
return {
|
||||
realName: searchFields.value.realName,
|
||||
};
|
||||
};
|
||||
|
||||
const searchFieldsChanged = ref(false);
|
||||
watch(searchFields, () => {
|
||||
searchFieldsChanged.value = true;
|
||||
});
|
||||
|
||||
const showPermissionConfigModal = ref(false);
|
||||
const selectedEmployeeId = ref('');
|
||||
|
||||
const tableColumns: DataTableColumns<BaseEmployeeResultVO> = [
|
||||
{ title: '姓名', key: 'realName', align: 'center' },
|
||||
{ title: '创建时间', key: 'createdTime', align: 'center' },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
render: (rowData) => {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
secondary: true,
|
||||
type: 'info',
|
||||
size: 'small',
|
||||
onClick: () => {
|
||||
const { id } = rowData;
|
||||
if (!id) return;
|
||||
selectedEmployeeId.value = id;
|
||||
showPermissionConfigModal.value = true;
|
||||
},
|
||||
},
|
||||
{
|
||||
icon: () => h(KeyIcon),
|
||||
default: () => '配置权限',
|
||||
},
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const tableData = ref<DataTableRowData[]>([]);
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
const pagination = reactive<PaginationProps>({
|
||||
showSizePicker: true,
|
||||
page: 1,
|
||||
pageSize: DEFAULT_PAGE_SIZE,
|
||||
pageSizes: [5, 10, 20, 50, 80, 100],
|
||||
itemCount: 0,
|
||||
prefix: ({ itemCount }) => {
|
||||
return h('div', {}, { default: () => `共${itemCount}条` });
|
||||
},
|
||||
onUpdatePage: (page: number) => {
|
||||
pagination.page = page;
|
||||
getTableData();
|
||||
},
|
||||
onUpdatePageSize: (pageSize: number) => {
|
||||
pagination.pageSize = pageSize;
|
||||
pagination.page = 1;
|
||||
getTableData();
|
||||
},
|
||||
});
|
||||
|
||||
const abortController = ref(new AbortController());
|
||||
|
||||
const { mutate: getTableData, isPending: tableLoading } = useMutation({
|
||||
mutationFn: async () => {
|
||||
abortController.value.abort();
|
||||
abortController.value = new AbortController();
|
||||
|
||||
const signal = abortController.value.signal;
|
||||
|
||||
const res = await pageBaseEmployeeApi(
|
||||
{
|
||||
model: getModelFields(),
|
||||
extra: {},
|
||||
current: pagination.page ?? 1,
|
||||
size: pagination.pageSize ?? DEFAULT_PAGE_SIZE,
|
||||
order: 'descending',
|
||||
sort: 'id',
|
||||
},
|
||||
{
|
||||
signal,
|
||||
},
|
||||
);
|
||||
return res;
|
||||
},
|
||||
onSuccess: (res) => {
|
||||
const { records, size, total } = res;
|
||||
pagination.pageSize = parseInt(size);
|
||||
pagination.itemCount = parseInt(total);
|
||||
tableData.value = records;
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isCancel(error)) return;
|
||||
console.error(error);
|
||||
const errorFeedback = parseErrorFeedback(error);
|
||||
window.$message.error(errorFeedback);
|
||||
},
|
||||
});
|
||||
|
||||
const onClickReset = () => {
|
||||
resetSearchFields();
|
||||
pagination.page = 1;
|
||||
pagination.pageSize = DEFAULT_PAGE_SIZE;
|
||||
pagination.itemCount = 0;
|
||||
getTableData();
|
||||
};
|
||||
const onClickQuery = () => {
|
||||
if (searchFieldsChanged.value) {
|
||||
pagination.page = 1;
|
||||
pagination.pageSize = DEFAULT_PAGE_SIZE;
|
||||
searchFieldsChanged.value = false;
|
||||
}
|
||||
getTableData();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getTableData();
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
abortController.value.abort();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NFlex vertical :size="0" style="height: 100%">
|
||||
<!-- 查询面板 -->
|
||||
<NForm style="flex: 0 0 auto; padding: 8px">
|
||||
<NGrid cols="3" :x-gap="24">
|
||||
<NFormItemGi span="1" label="姓名" label-placement="left">
|
||||
<NInput v-model:value="searchFields.realName" />
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
<!-- 操作按钮 -->
|
||||
<NGrid :cols="1">
|
||||
<NGridItem>
|
||||
<NFlex>
|
||||
<NButton @click="onClickReset">重置</NButton>
|
||||
<NButton type="primary" :loading="tableLoading" @click="onClickQuery">查询</NButton>
|
||||
</NFlex>
|
||||
</NGridItem>
|
||||
</NGrid>
|
||||
</NForm>
|
||||
|
||||
<!-- 数据表格工具栏 -->
|
||||
<NFlex align="center" style="padding: 8px; flex: 0 0 auto">
|
||||
<div style="font-size: medium">用户权限列表</div>
|
||||
<NFlex style="margin-left: auto">
|
||||
<!-- <NButton type="primary" :loading="exporting" @click="() => exportTableData()">导出</NButton> -->
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<NDataTable remote :columns="tableColumns" :data="tableData" :pagination="pagination" :loading="tableLoading" :single-line="false" flex-height style="height: 100%; padding: 8px; flex: 1 1 auto" />
|
||||
</NFlex>
|
||||
|
||||
<PermissionConfigModal v-model:show="showPermissionConfigModal" :employee-id="selectedEmployeeId" />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -2,9 +2,9 @@
|
||||
import { initStationAlarms, initStationDevices, syncCameraApi, syncNvrChannelsApi, type Station } from '@/apis';
|
||||
import { AlarmDetailModal, DeviceDetailModal, DeviceParamConfigModal, IcmpExportModal, RecordCheckExportModal, StationCard, type StationCardProps } from '@/components';
|
||||
import { useBatchActions, useLineDevicesQuery } from '@/composables';
|
||||
import { useAlarmStore, useDeviceStore, useSettingStore, useStationStore } from '@/stores';
|
||||
import { useAlarmStore, useDeviceStore, usePermissionStore, useSettingStore } from '@/stores';
|
||||
import { useMutation } from '@tanstack/vue-query';
|
||||
import { objectEntries, useElementSize } from '@vueuse/core';
|
||||
import { useElementSize } from '@vueuse/core';
|
||||
import { isCancel } from 'axios';
|
||||
import { NButton, NButtonGroup, NCheckbox, NFlex, NGrid, NGridItem, NScrollbar } from 'naive-ui';
|
||||
import { storeToRefs } from 'pinia';
|
||||
@@ -13,8 +13,8 @@ import { computed, ref, useTemplateRef } from 'vue';
|
||||
const settingStore = useSettingStore();
|
||||
const { stationGridCols } = storeToRefs(settingStore);
|
||||
|
||||
const stationStore = useStationStore();
|
||||
const { stations } = storeToRefs(stationStore);
|
||||
const permissionStore = usePermissionStore();
|
||||
const stations = computed(() => permissionStore.stations.VIEW ?? []);
|
||||
|
||||
const deviceStore = useDeviceStore();
|
||||
const { lineDevices } = storeToRefs(deviceStore);
|
||||
@@ -42,7 +42,10 @@ const showRecordCheckExportModal = ref(false);
|
||||
|
||||
const abortController = ref(new AbortController());
|
||||
|
||||
const { batchActions, selectedAction, selectableStations, stationSelection, toggleSelectAction, toggleSelectAllStations, confirmAction, cancelAction } = useBatchActions(stations, abortController);
|
||||
const { batchActions, selectedAction, selectableStations, stationSelection, selectionProps, toggleSelectAction, toggleSelectAllStations, confirmAction, cancelAction } = useBatchActions(
|
||||
stations,
|
||||
abortController,
|
||||
);
|
||||
|
||||
const { refetch: refetchLineDevicesQuery } = useLineDevicesQuery();
|
||||
|
||||
@@ -78,7 +81,7 @@ const { mutate: syncCamera, isPending: cameraSyncing } = useMutation({
|
||||
window.$notification.info({
|
||||
title: '摄像机同步结果',
|
||||
content: notices.join(','),
|
||||
duration: 3000,
|
||||
duration: 10000,
|
||||
});
|
||||
if (successRequests.length > 0) {
|
||||
// 摄像机同步后,需要重新查询一次设备
|
||||
@@ -120,7 +123,7 @@ const { mutate: syncNvrChannels, isPending: nvrChannelsSyncing } = useMutation({
|
||||
window.$notification.info({
|
||||
title: '录像机通道同步结果',
|
||||
content: notices.join(','),
|
||||
duration: 3000,
|
||||
duration: 10000,
|
||||
});
|
||||
cancelAction();
|
||||
},
|
||||
@@ -177,12 +180,7 @@ const onClickDetail: StationCardProps['onClickDetail'] = (type, station) => {
|
||||
</template>
|
||||
</NButtonGroup>
|
||||
<template v-if="selectedAction">
|
||||
<NCheckbox
|
||||
label="全选"
|
||||
:disabled="selectableStations.length === 0"
|
||||
:checked="selectableStations.length > 0 && selectableStations.length === objectEntries(stationSelection).filter(([, selected]) => selected).length"
|
||||
@update:checked="toggleSelectAllStations"
|
||||
/>
|
||||
<NCheckbox label="全选" :disabled="selectionProps.disabled" :checked="selectionProps.checked" :indeterminate="selectionProps.indeterminate" @update:checked="toggleSelectAllStations" />
|
||||
<NButton tertiary size="small" type="primary" :focusable="false" :loading="confirming" @click="onClickConfirmAction">确定</NButton>
|
||||
<NButton tertiary size="small" type="tertiary" :focusable="false" @click="cancelAction">取消</NButton>
|
||||
</template>
|
||||
|
||||
@@ -47,6 +47,15 @@ const router = createRouter({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'permission',
|
||||
component: () => import('@/pages/permission/permission-page.vue'),
|
||||
beforeEnter: () => {
|
||||
const userStore = useUserStore();
|
||||
if (userStore.isLamp) return true;
|
||||
return { path: '/404' };
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
component: () => import('@/pages/error/not-found-page.vue'),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export * from './alarm';
|
||||
export * from './device';
|
||||
export * from './permission';
|
||||
export * from './setting';
|
||||
export * from './station';
|
||||
export * from './unread';
|
||||
|
||||
77
src/stores/permission.ts
Normal file
77
src/stores/permission.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { NdmPermissionResultVO, Station } from '@/apis';
|
||||
import { NDM_PERMISSION_STORE_ID } from '@/constants';
|
||||
import { PERMISSION_TYPE_NAMES, type PermissionType } from '@/enums';
|
||||
import { useStationStore } from '@/stores';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref } from 'vue';
|
||||
import { objectEntries } from '@vueuse/core';
|
||||
|
||||
type Permissions = Record<Station['code'], PermissionType[]>;
|
||||
|
||||
export const usePermissionStore = defineStore(
|
||||
NDM_PERMISSION_STORE_ID,
|
||||
() => {
|
||||
const stationStore = useStationStore();
|
||||
|
||||
const permissionRecords = ref<NdmPermissionResultVO[] | null>(null);
|
||||
|
||||
const permissions = computed<Permissions>(() => {
|
||||
const result: Permissions = {};
|
||||
|
||||
const records = permissionRecords.value;
|
||||
|
||||
// 如果权限记录不存在,则不做权限配置
|
||||
if (!records) return result;
|
||||
|
||||
// 如果该用户没有任何权限记录,则开放所有权限,否则根据记录配置权限
|
||||
if (records.length === 0) {
|
||||
stationStore.stations.forEach((station) => {
|
||||
result[station.code] = [...objectEntries(PERMISSION_TYPE_NAMES).map(([permType]) => permType)];
|
||||
});
|
||||
} else {
|
||||
stationStore.stations.forEach((station) => {
|
||||
result[station.code] = [];
|
||||
const stationPermRecords = records.filter((record) => record.stationCode === station.code);
|
||||
if (stationPermRecords.length === 0) return;
|
||||
stationPermRecords.forEach(({ type: permType }) => {
|
||||
if (!permType) return;
|
||||
result[station.code]?.push(permType);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
// 按权限对车站进行分类
|
||||
const stations = computed(() => {
|
||||
const result: Partial<Record<PermissionType, Station[]>> = {};
|
||||
// 按原始的车站顺序进行遍历,保持显示顺序不变
|
||||
stationStore.stations.forEach((station) => {
|
||||
const permissionTypes = permissions.value[station.code];
|
||||
if (!permissionTypes) return;
|
||||
permissionTypes.forEach((permissionType) => {
|
||||
if (!result[permissionType]) result[permissionType] = [];
|
||||
result[permissionType].push(station);
|
||||
});
|
||||
});
|
||||
return result;
|
||||
});
|
||||
|
||||
const setPermRecords = (records: NdmPermissionResultVO[]) => {
|
||||
permissionRecords.value = records;
|
||||
};
|
||||
|
||||
return {
|
||||
permissionRecords,
|
||||
|
||||
permissions,
|
||||
stations,
|
||||
|
||||
setPermRecords,
|
||||
};
|
||||
},
|
||||
{
|
||||
persist: true,
|
||||
},
|
||||
);
|
||||
@@ -47,6 +47,7 @@ export const useSettingStore = defineStore(
|
||||
activeRequests.value = true;
|
||||
subscribeMessages.value = false;
|
||||
mockUser.value = false;
|
||||
useLocalDB.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user