138 Commits

Author SHA1 Message Date
TsMask
d3a452cfd8 Merge remote-tracking branch 'origin/main' into lichang 2024-09-27 11:14:26 +08:00
TsMask
d81b8cdf38 chore: 更新版本号 240927 2024-09-27 11:13:25 +08:00
TsMask
39a417368a docs: 更新说明 2024-09-27 11:11:54 +08:00
TsMask
adfce5d2f7 style: SMSC-CDR结果带result,cause 2024-09-27 10:05:45 +08:00
TsMask
977286d6b3 fix: 驼峰和划线互转函数去除非对象转换 2024-09-26 17:31:57 +08:00
TsMask
c33000045a fix: 消息进行wg关闭销毁 2024-09-26 17:23:24 +08:00
TsMask
b995ac378a fix: 网元版本同版本号进行确认继续操作 2024-09-26 17:20:01 +08:00
TsMask
6bea64f345 style: 多语言views.traceManage.task取值变更views.ne.common 2024-09-24 10:53:33 +08:00
TsMask
94886e255e feat: 网元跟踪数据支持下载pcap文件 2024-09-24 10:52:28 +08:00
TsMask
45f66afe52 fix: 网元配置更新下发配置失败时不更新状态 2024-09-23 17:44:16 +08:00
TsMask
b9105c1e77 style: 注释信息解析html请求 2024-09-23 17:25:26 +08:00
TsMask
909d306942 perf: wg优化代码封装hooks 2024-09-23 17:24:55 +08:00
TsMask
f7273457e9 feat: 跟踪任务查看pcap内容信息 2024-09-23 17:24:02 +08:00
TsMask
2e5ad2f65d fix: 看板MME-CDR的ECM State 2024-09-21 15:52:54 +08:00
TsMask
776e9c5837 chore: 更新版本号 240920 2024-09-20 18:23:12 +08:00
TsMask
0d4979d3d9 style: 注释和代码格式化 2024-09-20 18:22:22 +08:00
TsMask
686c7dd273 fix: 驼峰和划线互转函数 2024-09-20 18:21:03 +08:00
TsMask
d41b308c6d fix: 性能管理报表页面未开发 2024-09-20 18:20:34 +08:00
TsMask
84dac247d2 perf: 重构跟踪任务 2024-09-20 18:20:01 +08:00
TsMask
f8439bb40a feat: HLR跟踪任务页面免登录/trace-task-hlr 2024-09-20 12:05:16 +08:00
TsMask
d268d920e7 chore: 更新版本号 240919 2024-09-19 11:50:51 +08:00
TsMask
f730ef1e3a style: 调整勾选按钮顺序 2024-09-19 11:50:06 +08:00
TsMask
af1ce32063 feat: 调整SMF在线用户列表数据补充显示imsi备注标记 2024-09-19 11:49:30 +08:00
TsMask
678ff2d09d feat: UDM签约补充CAG参数和备注标记参数 2024-09-19 11:48:16 +08:00
TsMask
48f674b6ef Merge remote-tracking branch 'origin/main' into lichang 2024-09-13 09:59:13 +08:00
TsMask
02f0820a69 fix: telnet终端命令多‘号导致命令无效 2024-09-13 09:58:13 +08:00
TsMask
ca8605fd6e fix: 跟踪任务HLR操作 2024-09-12 17:12:41 +08:00
TsMask
6d5e96421b style: 多语言zh去除行头 2024-09-12 17:12:13 +08:00
TsMask
bcc29007bf fix: 4G的MME显示ECM 2024-09-12 17:11:10 +08:00
TsMask
bdf904078d style: 多语言zh去除行头 2024-09-12 17:10:43 +08:00
TsMask
e37cfa5066 Merge remote-tracking branch 'origin/main' into lichang 2024-09-10 09:42:40 +08:00
lai
f1bff23bbc Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-09-09 19:25:26 +08:00
lai
53106ddb5c 调换位置 2024-09-09 19:25:22 +08:00
TsMask
3a04882fe5 Merge remote-tracking branch 'origin/main' into lichang 2024-09-09 19:14:04 +08:00
TsMask
19202a5e81 Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-09-09 19:11:37 +08:00
TsMask
7b311ff673 fix: SMSC添加CDR响应错误原因码 2024-09-09 19:11:34 +08:00
lai
9dba98e0ee 重新排版表单 2024-09-09 19:06:53 +08:00
lai
71338670f0 重新排版表单 2024-09-09 18:12:23 +08:00
lai
7dcdfabce2 增加单位显示的限制 2024-09-09 16:23:18 +08:00
lai
ddfe1723c9 自定义指标 2024-09-09 15:01:04 +08:00
TsMask
57b5f76db7 fix: 重构tool的ps页面 2024-09-06 19:57:10 +08:00
lai
9ac3524877 自定义指标 2024-09-06 19:22:25 +08:00
lai
ca82a0a74b 更改中英文 2024-09-06 19:21:57 +08:00
zhongzm
23007c3bf2 feat:ps界面和net界面 2024-09-06 17:27:38 +08:00
TsMask
5d69d7612a chore: 更新版本号 2.240906 2024-09-06 16:14:16 +08:00
TsMask
ddd8930af4 feat: 跟踪任务功能详情文件页面 2024-09-06 16:12:33 +08:00
lai
757f2ec20a 导出文件管理 2024-09-06 10:15:20 +08:00
lai
30caa79424 Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-09-05 20:22:07 +08:00
TsMask
e3f83a0b98 feat: 跟踪任务功能页面 2024-09-05 17:30:31 +08:00
TsMask
147a3ed77b style: 跟踪任务多语言翻译 2024-09-05 17:30:11 +08:00
TsMask
5d35d950b3 feat: 网元参数配置特殊SMF-upfid选择 2024-09-05 17:29:12 +08:00
lai
6874508d3f neType空时则获取全部基站信息 2024-09-05 16:42:43 +08:00
lai
33f468209a 告警根据中英文导出 2024-09-05 16:38:06 +08:00
lai
e8ef2816df 增加IMSI,ki限制位数以及合并新增批量新增按钮 2024-09-05 16:36:05 +08:00
TsMask
e38d7bbffa fix: 网元信息新增监听neType+neId拼接rmUID 2024-09-03 16:59:06 +08:00
TsMask
2f1265c47a fix: 编译类型缺失 2024-09-03 16:57:43 +08:00
TsMask
66b6b60505 fix: 右上角气泡提示活动告警 2024-09-03 11:28:41 +08:00
TsMask
249d14320d fix: 删除右上角系统用户手册 2024-09-03 11:22:56 +08:00
TsMask
313b90ad31 fix: MME事件类型cm显示改为ECM 2024-09-03 11:19:39 +08:00
TsMask
2ebc90e974 Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-09-03 11:06:50 +08:00
TsMask
640257dd55 feat: 信令抓包数据监控 2024-09-03 11:06:40 +08:00
TsMask
c1a3ce8068 feat: 信令抓包tshark解析pcap 2024-09-03 11:05:58 +08:00
TsMask
0080e9c26e feat: 公共组件-虚拟滚动列表 2024-09-03 11:00:00 +08:00
TsMask
2ccafe622d feat: 插件新增-Web Workers 2024-09-03 10:59:05 +08:00
TsMask
d7a515ed9a feat: 工具函数-格式化文件大小 2024-09-03 10:54:15 +08:00
cd82b71b77 fix: remove OMC limit from parameter config NE list 2024-09-02 16:52:49 +08:00
TsMask
9d6a7dcd9c chore: 更新版本号 2.240831 2024-08-31 10:17:36 +08:00
TsMask
46c2affcc8 fix: 网元信息资源百分比 2024-08-30 19:50:12 +08:00
TsMask
3d00a80588 fix: 手工同步超时时间180s 2024-08-30 18:02:47 +08:00
TsMask
a3c1fe154f chore: 更新版本号 2.240823 2024-08-23 19:06:32 +08:00
TsMask
07dce5a27e style: 暗黑模式下文字反色 2024-08-23 19:05:35 +08:00
TsMask
255cf026a6 style: 编译类型错误 2024-08-22 10:28:35 +08:00
TsMask
840ea56c42 chore: 更新版本号 2.240822 2024-08-22 10:20:45 +08:00
TsMask
09917cc9c9 feat: 补充CBC网元选择 2024-08-22 10:19:52 +08:00
TsMask
4c9fe192f2 feat: 历史抓包文件页面 2024-08-22 10:19:23 +08:00
TsMask
32ec55d44e feat: 网元文件下载支持删除临时缓存文件 2024-08-22 10:18:42 +08:00
TsMask
527cf89d1a fix: 避免get请求带body错误 2024-08-21 17:38:10 +08:00
TsMask
ac7b57c0ae fix: 内嵌地址标识菜单展开高亮 2024-08-21 17:37:06 +08:00
TsMask
8be1a8968e fix: 标签名称修改导致全局标签 2024-08-21 17:36:04 +08:00
TsMask
999ccf64ad perf: 抓包功能优化 2024-08-20 15:49:03 +08:00
TsMask
03352f3aa8 style: 网元快速安装操作Nest放后面 2024-08-17 12:22:38 +08:00
lai
61a58fc661 默认neType为空时显示45G信息 2024-08-16 17:11:39 +08:00
TsMask
4268fa3198 fix: 网元IMS参数配置plmn禁止删除index0 2024-08-15 18:05:56 +08:00
TsMask
f6b62c6c7e fix: 构建目标改为esnext,兼容pdf-js编译 2024-08-15 10:22:39 +08:00
TsMask
b4cbc1c190 chore: 更新版本号 2.240815 2024-08-15 10:11:22 +08:00
TsMask
1871f6f656 chore: 新增crypto-js依赖库 2024-08-15 10:10:50 +08:00
TsMask
409f9836a6 fix: 对登录,网元信息新增更新数据加密 2024-08-15 10:10:09 +08:00
TsMask
b3f40ee683 fix: 网元信息列表不带状态导致无法正常显示 2024-08-15 10:09:11 +08:00
TsMask
aa07b51663 feat: 请求http工具支持接口加解密 2024-08-15 10:08:12 +08:00
TsMask
19b77ed005 style: 监控资源数据超时设为60s 2024-08-15 09:49:44 +08:00
TsMask
06503fd079 fix: 拓扑图组名变更 2024-08-09 19:46:20 +08:00
TsMask
2321dacd2a chore: 更新版本号 2.240809 2024-08-09 18:48:44 +08:00
TsMask
a8b4e91b95 feat: 文本日志文件实时查看功能 2024-08-09 18:47:45 +08:00
TsMask
a5075bef43 feat: SMSC功能接口补充 2024-08-08 20:58:47 +08:00
TsMask
f4ffbc1c86 style: CDR数据页面格式优化 2024-08-08 20:58:06 +08:00
TsMask
6cafa284c7 feat: SMSC-CDR数据列表查询 2024-08-08 20:56:40 +08:00
TsMask
049c0e7a0f fix: 终端面板telnet内容行列数自适应调整 2024-08-08 10:40:19 +08:00
TsMask
377ffc6e10 fix: CDR/Event上报数据对应发网元 2024-08-06 16:56:37 +08:00
TsMask
858431e86e perf: 替换旧网元参数配置页面 2024-08-05 17:51:11 +08:00
TsMask
70fca5ca41 fix: 网元信息OAM配置支持修改omc ip,排除omc编辑OAM信息 2024-08-05 17:44:41 +08:00
lai
e972d14a9a 调整表格字段列 2024-08-05 15:28:16 +08:00
TsMask
c1187383b6 fix: 系统引导跳转不重复引导 2024-08-03 10:09:16 +08:00
lai
8d8605e0cd 调整网元配置模块 2024-08-02 18:55:53 +08:00
TsMask
8af48936e5 chore: 更新版本号 2.240801 2024-08-02 10:11:42 +08:00
TsMask
e21a8dc898 perf: 优化快速安装配置公共参数页面 2024-08-01 17:34:16 +08:00
TsMask
3c1ee63359 fix: 开站网元安装信息保存触发操作不一致 2024-07-30 18:23:51 +08:00
TsMask
bc3940016a perf: 网元公共参数合并到网元快速安装,移除相关多语言翻译 2024-07-30 18:22:32 +08:00
TsMask
6ab4fbea6f fix: UDM签约数据cnType改5GC Flag 2024-07-30 11:27:12 +08:00
TsMask
c73fcbd91c chore: 更新版本号 2.240729 2024-07-29 18:27:15 +08:00
TsMask
023e317b0d feat: 网元配置备份操作权限控制删除/编辑按钮 2024-07-29 14:36:19 +08:00
lai
543a2b7434 新增 统一配置网元 2024-07-29 10:38:14 +08:00
TsMask
4ae9051411 style: 系统设置LOGO和文档判断是否开启语言切换显示控件 2024-07-27 15:01:56 +08:00
TsMask
532546c3f5 chore: 更新版本号 2.240727 2024-07-27 10:28:23 +08:00
TsMask
ab0d26513c perf: 网元备份导入导出弹窗表单重构 2024-07-26 18:27:15 +08:00
TsMask
8e9498ec83 feat: 网元备份文件表格页面 2024-07-26 18:25:15 +08:00
TsMask
ca048f223c feat: UDM鉴权文件导入K4文件支持 2024-07-26 15:28:45 +08:00
TsMask
e9ff6493dd fix: 看板UPF流量吞吐初始查询和ws监听12_neId 2024-07-25 18:48:01 +08:00
TsMask
396fb0b124 fix: KPI接收指定对应neId,实时数据不支持搜索 2024-07-25 18:23:28 +08:00
TsMask
3a854be8fe fix: 查询IMS在线用户数接口数据格式兼容 2024-07-25 10:50:54 +08:00
TsMask
c1f34f56ac fix: 对使用手册,官网进行限制 2024-07-24 18:10:38 +08:00
lai
b80bae0126 Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-07-24 17:18:18 +08:00
lai
6c383b58c8 对使用手册,官网进行限制 2024-07-24 17:18:15 +08:00
TsMask
06db1344c3 fix: 日志事件cm结果多语言显示错误 2024-07-23 14:48:53 +08:00
TsMask
fac47279b5 fix: 日志IMS-CDR类型查询默认空进行全查询,表格宽度增加 2024-07-23 14:26:09 +08:00
TsMask
a36a12f783 fix: 日志事件类型查询默认空进行全查询 2024-07-23 14:25:08 +08:00
TsMask
acd19ebdbd style: 看板基站数量分开展示避免分不清数 2024-07-19 14:41:40 +08:00
TsMask
d53c4c34f0 style: 新增网元信息时ssh默认密码初始 2024-07-18 15:44:35 +08:00
TsMask
2d9011cf6b fix: 看板资源图无网元状体数据取值异常 2024-07-17 18:11:22 +08:00
TsMask
fcf53d0995 fix: 网元删除记录自动移除不要刷新列表 2024-07-17 18:06:59 +08:00
TsMask
a2d93ddafe fix: 系统名称横向滚动衔接动画 2024-07-16 11:21:16 +08:00
TsMask
79920542c1 chore: 更新版本号240712 2024-07-12 21:23:12 +08:00
TsMask
6efd9cb61a fix: 根据浏览器地址栏hostname加33030端口连接后端服务 2024-07-11 17:56:24 +08:00
TsMask
4dc6699974 style: UDM用户数据限制imsi长度输入 2024-07-11 15:02:53 +08:00
TsMask
3bdae264e1 fix: 网元信息单行更新局部信息变更失效 2024-07-10 15:47:28 +08:00
TsMask
9e0a99d160 style: 数据CDR的表头宽度调整 2024-07-09 19:02:54 +08:00
TsMask
5ac0ca41eb style: 签约用户IMSI编辑时禁止修改,cnType添加提示 2024-07-09 18:36:27 +08:00
TsMask
a7de701d4d fix: 支持kvdb依赖包安装,db_ip默认0.0.0.0 2024-07-09 16:11:58 +08:00
TsMask
f0561242ca fix: fetch请求拼接地址栏参数不区分请求方法 2024-07-09 10:04:46 +08:00
126 changed files with 23314 additions and 5020 deletions

View File

@@ -5,13 +5,13 @@ VITE_HISTORY_HASH = false
VITE_HISTORY_BASE_URL = "/" VITE_HISTORY_BASE_URL = "/"
# 应用名称 # 应用名称
VITE_APP_NAME = "Core Network EMS" VITE_APP_NAME = "Core Network OMC"
# 应用标识 # 应用标识
VITE_APP_CODE = "CN EMS" VITE_APP_CODE = "OMC"
# 应用版本 # 应用版本
VITE_APP_VERSION = "2.240704" VITE_APP_VERSION = "2.240927"
# 接口基础URL地址-不带/后缀 # 接口基础URL地址-不带/后缀
VITE_API_BASE_URL = "/omc-api" VITE_API_BASE_URL = "/omc-api"

View File

@@ -5,13 +5,13 @@ VITE_HISTORY_HASH = true
VITE_HISTORY_BASE_URL = "/" VITE_HISTORY_BASE_URL = "/"
# 应用名称 # 应用名称
VITE_APP_NAME = "Core Network EMS" VITE_APP_NAME = "Core Network OMC"
# 应用标识 # 应用标识
VITE_APP_CODE = "CN EMS" VITE_APP_CODE = "OMC"
# 应用版本 # 应用版本
VITE_APP_VERSION = "2.240704" VITE_APP_VERSION = "2.240927"
# 接口基础URL地址-不带/后缀 # 接口基础URL地址-不带/后缀
VITE_API_BASE_URL = "/omc-api" VITE_API_BASE_URL = "/omc-api"

View File

@@ -8,14 +8,8 @@
## 测试环境 ## 测试环境
```text ```text
Jenkins: http://192.168.2.166:3185/
Nginx: http://192.168.2.166:3188/#/index Nginx: http://192.168.2.166:3188/#/index
后端暴露端口: http://192.168.2.166:33030 后端暴露端口: http://192.168.2.166:33030
新网管192.168.5.13
旧网管192.168.5.14
登录账户manager/manager
``` ```
## 程序命令 ## 程序命令
@@ -59,16 +53,5 @@ export NODE_OPTIONS=--max-old-space-size=50000
```text ```text
https://192.168.5.23/ https://192.168.5.23/
admin admin / admin
admin
```
## k8s
master 192.168.5.27 agtuser/admin123
https://192.168.5.27:31325/#/workloads?namespace=default
```text
eyJhbGciOiJSUzI1NiIsImtpZCI6ImZFVUhIb1puLW04M1dfSUYyRU8zWlZueXBpNUh4T0hTRVlzU19jNlVGQ0kifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLW44ZzRtIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI2M2NmYjAyNS01ZmQ0LTQ0ZTgtOTdiNC0yYWRiYWIxNzc5M2MiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.R3GRygFOjngTj-mEMBAHDeBxm3lpsXZYvC6cdTxByONtLrcMXDebwNVeKtAZ1V9qh2OrjD8n9CIygjULGPdfV6S520vjMh7Oa2q68nOyW49DNWQyYD8xLo-dQ6sX07fI7X_I3H35YUWW80jJAXjJawqIGXBSMG5intlo4tLTUSXmjCfhoQvFsgeRWu0j76pDvhMAvLPcgEXfTCi9tyL3yqJBIKONcKwmMlJeaKSR3pQk3KiibqrBO0MZclRozpke6J0ulfzTemwDDyCqBZmLsRPZ2yDd5hVBIJ9bHEcK0a25NmSFFzmd8XWQPZwg3Y4IbbY-8UhByGq0p9xS-7pGCQ
``` ```

View File

@@ -18,7 +18,7 @@
"@codemirror/lang-yaml": "^6.1.1", "@codemirror/lang-yaml": "^6.1.1",
"@codemirror/merge": "^6.6.3", "@codemirror/merge": "^6.6.3",
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
"@tato30/vue-pdf": "~1.9.7", "@tato30/vue-pdf": "^1.10.0",
"@vueuse/core": "~10.10.1", "@vueuse/core": "~10.10.1",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
@@ -26,10 +26,11 @@
"antdv-pro-layout": "~3.3.5", "antdv-pro-layout": "~3.3.5",
"antdv-pro-modal": "^3.1.0", "antdv-pro-modal": "^3.1.0",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"echarts": "~5.5.0", "echarts": "~5.5.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"intl-tel-input": "~23.0.12", "intl-tel-input": "^23.8.1",
"js-base64": "^3.7.7", "js-base64": "^3.7.7",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"localforage": "^1.10.0", "localforage": "^1.10.0",
@@ -43,6 +44,7 @@
"xlsx": "~0.18.5" "xlsx": "~0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@types/crypto-js": "^4.2.2",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/node": "^18.0.0", "@types/node": "^18.0.0",

View File

@@ -11,7 +11,8 @@
*/ */
(function () { (function () {
// host = ip:port // host = ip:port
const host = '192.168.8.100:33030'; // const host = '192.168.8.100:33030';
const host = `${window.location.hostname}:33030`;
// Service Address // Service Address
sessionStorage.setItem('baseUrl', `http://${host}`); sessionStorage.setItem('baseUrl', `http://${host}`);

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,166 @@
/**
* Wraps the WiregasmLib lib functionality and manages a single DissectSession
*/
class Wiregasm {
constructor() {
this.initialized = false;
this.session = null;
}
/**
* Initialize the wrapper and the Wiregasm module
*
* @param loader Loader function for the Emscripten module
* @param overrides Overrides
*/
async init(loader, overrides = {}, beforeInit = null) {
if (this.initialized) {
return;
}
this.lib = await loader(overrides);
this.uploadDir = this.lib.getUploadDirectory();
this.pluginsDir = this.lib.getPluginsDirectory();
if (beforeInit !== null) {
await beforeInit(this.lib);
}
this.lib.init();
this.initialized = true;
}
list_modules() {
return this.lib.listModules();
}
list_prefs(module) {
return this.lib.listPreferences(module);
}
apply_prefs() {
this.lib.applyPreferences();
}
set_pref(module, key, value) {
const ret = this.lib.setPref(module, key, value);
if (ret.code != PrefSetResult.PREFS_SET_OK) {
const message =
ret.error != '' ? ret.error : preferenceSetCodeToError(ret.code);
throw new Error(
`Failed to set preference (${module}.${key}): ${message}`
);
}
}
get_pref(module, key) {
const response = this.lib.getPref(module, key);
if (response.code != 0) {
throw new Error(`Failed to get preference (${module}.${key})`);
}
return response.data;
}
/**
* Check the validity of a filter expression.
*
* @param filter A display filter expression
*/
test_filter(filter) {
return this.lib.checkFilter(filter);
}
complete_filter(filter) {
const out = this.lib.completeFilter(filter);
return {
err: out.err,
fields: vectorToArray(out.fields),
};
}
reload_lua_plugins() {
this.lib.reloadLuaPlugins();
}
add_plugin(name, data, opts = {}) {
const path = this.pluginsDir + '/' + name;
this.lib.FS.writeFile(path, data, opts);
}
/**
* Load a packet trace file for analysis.
*
* @returns Response containing the status and summary
*/
load(name, data, opts = {}) {
if (this.session != null) {
this.session.delete();
}
const path = this.uploadDir + '/' + name;
this.lib.FS.writeFile(path, data, opts);
this.session = new this.lib.DissectSession(path);
return this.session.load();
}
/**
* Get Packet List information for a range of packets.
*
* @param filter Output those frames that pass this filter expression
* @param skip Skip N frames
* @param limit Limit the output to N frames
*/
frames(filter, skip = 0, limit = 0) {
return this.session.getFrames(filter, skip, limit);
}
/**
* Get full information about a frame including the protocol tree.
*
* @param number Frame number
*/
frame(num) {
return this.session.getFrame(num);
}
follow(follow, filter) {
return this.session.follow(follow, filter);
}
destroy() {
if (this.initialized) {
if (this.session !== null) {
this.session.delete();
this.session = null;
}
this.lib.destroy();
this.initialized = false;
}
}
/**
* Returns the column headers
*/
columns() {
const vec = this.lib.getColumns();
// convert it from a vector to array
return vectorToArray(vec);
}
}
/**
* Converts a Vector to a JS array
*
* @param vec Vector
* @returns JS array of the Vector contents
*/
function vectorToArray(vec) {
return new Array(vec.size()).fill(0).map((_, id) => vec.get(id));
}
function preferenceSetCodeToError(code) {
switch (code) {
case PrefSetResult.PREFS_SET_SYNTAX_ERR:
return 'Syntax error in string';
case PrefSetResult.PREFS_SET_NO_SUCH_PREF:
return 'No such preference';
case PrefSetResult.PREFS_SET_OBSOLETE:
return 'Preference used to exist but no longer does';
default:
return 'Unknown error';
}
}
if (typeof exports === 'object' && typeof module === 'object') {
module.exports = Wiregasm;
module.exports = vectorToArray;
} else if (typeof define === 'function' && define['amd']) {
define([], function () {
return Wiregasm;
});
define([], function () {
return vectorToArray;
});
} else if (typeof exports === 'object') {
exports['loadWiregasm'] = Wiregasm;
exports['vectorToArray'] = vectorToArray;
}

162
public/wiregasm/worker.js Normal file
View File

@@ -0,0 +1,162 @@
// load the Wiregasm library
importScripts(
'/wiregasm/wiregasm_new.js', // self-compilation es5
'/wiregasm/wiregasm_load.js'
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.js'
);
const wg = new Wiregasm();
const inflateRemoteBuffer = async url => {
const res = await fetch(url);
return await res.arrayBuffer();
};
const fetchPackages = async () => {
console.log('Fetching packages');
let [wasmBuffer, dataBuffer] = await Promise.all([
await inflateRemoteBuffer(
'/wiregasm/wiregasm.wasm.gz'
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.wasm.gz'
),
await inflateRemoteBuffer(
'/wiregasm/wiregasm.data.gz'
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.data.gz'
),
]);
return { wasmBuffer, dataBuffer };
};
// Load the Wiregasm Wasm data
fetchPackages()
.then(({ wasmBuffer, dataBuffer }) => {
return wg.init(loadWiregasm, {
wasmBinary: wasmBuffer,
getPreloadedPackage() {
return dataBuffer;
},
handleStatus: (type, status) => {
postMessage({ type: 'status', code: type, status: status });
},
printErr: error => {
postMessage({ type: 'error', error: error });
},
});
})
.then(() => {
postMessage({ type: 'init' });
})
.catch(e => {
postMessage({ type: 'error', error: e });
});
/**Converts a Vector to a JS array */
function replacer(key, value) {
if (value.constructor.name.startsWith('Vector')) {
return vectorToArray(value);
}
return value;
}
// Event listener to receive messages from the main script
this.onmessage = ev => {
const data = ev.data;
switch (data.type) {
case 'close':
wg.destroy();
break;
case 'columns':
const columns = wg.columns();
if (Array.isArray(columns)) {
this.postMessage({ type: 'columns', data: columns });
}
break;
case 'select': // select a frame
const number = data.number;
const frameData = wg.frame(number);
const frameDataToJSON = JSON.parse(JSON.stringify(frameData, replacer));
this.postMessage({
type: 'selected',
data: frameDataToJSON,
});
break;
case 'frames': // get frames list
const skip = data.skip;
const limit = data.limit;
const filter = data.filter;
const framesData = wg.frames(filter, skip, limit);
const framesDataToJSON = JSON.parse(JSON.stringify(framesData, replacer));
this.postMessage({
type: 'frames',
data: framesDataToJSON,
});
break;
case 'process-data':
const loadData = wg.load(data.name, new Uint8Array(data.data));
this.postMessage({ type: 'processed', data: loadData });
break;
case 'process':
const f = data.file;
const reader = new FileReader();
reader.addEventListener('load', event => {
// XXX: this blocks the worker thread
const loadData = wg.load(f.name, new Uint8Array(event.target.result));
postMessage({ type: 'processed', data: loadData });
});
reader.readAsArrayBuffer(f);
break;
case 'check-filter':
const filterStr = data.filter;
const checkFilterRes = wg.lib.checkFilter(filterStr);
this.postMessage({ type: 'filter', data: checkFilterRes });
break;
}
if (data.type === 'reload-quick') {
if (wg.session) {
// TODO: this is a hack, we should be able to reload the session
const name = data.name;
const res = wg.session.load();
postMessage({ type: 'processed', name: name, data: res });
}
} else if (data.type === 'module-tree') {
const res = wg.list_modules();
// send it to the correct port
event.ports[0].postMessage({
result: JSON.parse(JSON.stringify(res, replacer)),
});
} else if (data.type === 'module-prefs') {
const res = wg.list_prefs(data.name);
// send it to the correct port
event.ports[0].postMessage({
result: JSON.parse(JSON.stringify(res, replacer)),
});
} else if (data.type === 'upload-file') {
const f = data.file;
const reader = new FileReader();
reader.addEventListener('load', e => {
// XXX: this blocks the worker thread
const path = '/uploads/' + f.name;
wg.lib.FS.writeFile(path, Buffer.from(e.target.result));
event.ports[0].postMessage({ result: path });
});
reader.readAsArrayBuffer(f);
} else if (data.type === 'update-pref') {
try {
console.log(`set_pref(${data.module}, ${data.key}, ${data.value})`);
wg.set_pref(data.module, data.key, data.value);
event.ports[0].postMessage({ result: 'ok' });
} catch (e) {
console.error(
`set_pref(${data.module}, ${data.key}, ${data.value}) failed: ${e.message}`
);
event.ports[0].postMessage({ error: e.message });
}
} else if (data.type === 'apply-prefs') {
console.log(`apply_prefs()`);
wg.apply_prefs();
event.ports[0].postMessage({ result: 'ok' });
}
};

View File

@@ -241,6 +241,7 @@ export function listSync() {
return request({ return request({
url: `/api/rest/faultManagement/v1/elementType/all/objectType/alarms`, url: `/api/rest/faultManagement/v1/elementType/all/objectType/alarms`,
method: 'get', method: 'get',
timeout: 180_000,
}); });
} }

View File

@@ -4,8 +4,6 @@ import { parseObjLineToHump } from '@/utils/parse-utils';
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr } from '@/utils/date-utils';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
/** /**
* 查询列表 * 查询列表
* @param query 查询参数 * @param query 查询参数
@@ -28,8 +26,6 @@ export async function listAct(query: Record<string, any>) {
querySQL += ` and pv_flag = '${query.pvFlag}' `; querySQL += ` and pv_flag = '${query.pvFlag}' `;
} }
if (query.neId) { if (query.neId) {
querySQL += ` and ne_id like '%${query.neId}%' `; querySQL += ` and ne_id like '%${query.neId}%' `;
} }
@@ -69,7 +65,6 @@ export async function listAct(query: Record<string, any>) {
msg: result.msg, msg: result.msg,
}; };
result.data.data.forEach((item: any) => { result.data.data.forEach((item: any) => {
console.log(item)
const itemData = item['alarm_event']; const itemData = item['alarm_event'];
if (Array.isArray(itemData)) { if (Array.isArray(itemData)) {
if (itemData.length === 1 && itemData[0]['total'] >= 0) { if (itemData.length === 1 && itemData[0]['total'] >= 0) {
@@ -84,12 +79,6 @@ export async function listAct(query: Record<string, any>) {
return result; return result;
} }
/** /**
* 事件告警导出 * 事件告警导出
* @param query 查询参数 * @param query 查询参数
@@ -133,7 +122,3 @@ export async function exportAll(query: Record<string, any>) {
} }
return result; return result;
} }

View File

@@ -0,0 +1,53 @@
import { request } from '@/plugins/http-fetch';
/**
* 获取下拉框数据
* @returns object
*/
export function getBakFile() {
return request({
url: '/lm/table/list',
method: 'get',
});
}
/**
* 获取对应类型的文件列表
* @param query 查询参数
* @returns object
*/
export function getBakFileList(query: Record<string, any>) {
return request({
url: '/lm/file/list',
method: 'get',
params: query,
});
}
/**
* 下载远端文件
* @param query 查询参数
* @returns object
*/
export function downFile(query: Record<string, any>) {
return request({
url: `/lm/file/${query.fileName}`,
method: 'get',
params: query,
responseType: 'blob',
timeout: 180_000,
});
}
/**
* 删除远端获取文件
* @param query 查询参数
* @returns object
*/
export function delFile(query: Record<string, any>) {
return request({
url: `/lm/file/${query.fileName}`,
method: 'delete',
params: query,
});
}

View File

@@ -7,6 +7,7 @@ export function login(data: Record<string, string>) {
method: 'post', method: 'post',
data: data, data: data,
whithToken: false, whithToken: false,
crypto: true,
}); });
} }
@@ -21,6 +22,7 @@ export function register(data: Record<string, any>) {
method: 'post', method: 'post',
data: data, data: data,
whithToken: false, whithToken: false,
crypto: true,
}); });
} }

View File

@@ -6,5 +6,6 @@ export function getLoad(query: Record<string, any>) {
url: '/monitor/load', url: '/monitor/load',
method: 'get', method: 'get',
params: query, params: query,
timeout: 60_000,
}); });
} }

View File

@@ -5,5 +5,6 @@ export function getSystemInfo() {
return request({ return request({
url: '/monitor/system-info', url: '/monitor/system-info',
method: 'get', method: 'get',
timeout: 60_000,
}); });
} }

66
src/api/ne/neConfig.ts Normal file
View File

@@ -0,0 +1,66 @@
import { request } from '@/plugins/http-fetch';
/**
* 网元参数配置可用属性值列表指定网元类型全部无分页
* @param query 查询参数
* @returns object
*/
export function getAllNeConfig(neType: string) {
return request({
url: `/ne/config/list/${neType}`,
method: 'get',
timeout: 60_000,
});
}
/**
* 网元参数配置数据信息
* @param params 数据 {neType,neId,paramName}
* @returns object
*/
export function getNeConfigData(params: Record<string, any>) {
return request({
url: `/ne/config/data`,
params,
method: 'get',
});
}
/**
* 网元参数配置数据更新
* @param data 数据 {neType,neId,paramName:"参数名",paramData:{参数},loc:"层级index仅array"}
* @returns object
*/
export function editNeConfigData(data: Record<string, any>) {
return request({
url: `/ne/config/data`,
method: 'put',
data: data,
});
}
/**
* 网元参数配置数据新增array
* @param data 数据 {neType,neId,paramName:"参数名",paramData:{参数},loc:"层级index"}
* @returns object
*/
export function addNeConfigData(data: Record<string, any>) {
return request({
url: `/ne/config/data`,
method: 'post',
data: data,
});
}
/**
* 网元参数配置数据删除array
* @param params 数据 {neType,neId,paramName:"参数名",loc:"层级index"}
* @returns object
*/
export function delNeConfigData(params: Record<string, any>) {
return request({
url: `/ne/config/data`,
method: 'delete',
params,
});
}

View File

@@ -0,0 +1,83 @@
import { request } from '@/plugins/http-fetch';
/**
* 网元配置文件备份记录列表
* @param query 查询参数
* @returns object
*/
export function listNeConfigBackup(query: Record<string, any>) {
return request({
url: '/ne/config/backup/list',
method: 'get',
params: query,
});
}
/**
* 网元配置文件备份记录修改
* @param data 数据 { id, name, remark }
* @returns object
*/
export function updateNeConfigBackup(data: Record<string, any>) {
return request({
url: '/ne/config/backup',
method: 'put',
data: data,
});
}
/**
* 网元配置文件备份记录下载
* @param id 记录ID
* @returns object
*/
export async function downNeConfigBackup(id: string) {
return await request({
url: '/ne/config/backup/download',
method: 'get',
params: { id },
responseType: 'blob',
timeout: 180_000,
});
}
/**
* 网元配置文件备份记录删除
* @param id 记录ID
* @returns object
*/
export async function delNeConfigBackup(id: string) {
return request({
url: '/ne/config/backup',
method: 'delete',
params: { id },
});
}
/**
* 网元配置文件备份导出
* @param data 数据 { neType, neId }
* @returns object
*/
export function exportNeConfigBackup(data: Record<string, any>) {
return request({
url: '/ne/config/backup/export',
method: 'post',
data: data,
responseType: 'blob',
timeout: 180_000,
});
}
/**
* 网元配置文件备份导入
* @param data 数据 { neType, neId, type, path }
* @returns object
*/
export function importNeConfigBackup(data: Record<string, any>) {
return request({
url: '/ne/config/backup/import',
method: 'post',
data: data,
});
}

View File

@@ -36,6 +36,8 @@ export function addNeInfo(data: Record<string, any>) {
url: `/ne/info`, url: `/ne/info`,
method: 'post', method: 'post',
data: data, data: data,
crypto: true,
timeout: 30_000,
}); });
} }
@@ -49,6 +51,8 @@ export function updateNeInfo(data: Record<string, any>) {
url: `/ne/info`, url: `/ne/info`,
method: 'put', method: 'put',
data: data, data: data,
crypto: true,
timeout: 30_000,
}); });
} }

View File

@@ -40,3 +40,16 @@ export function exportSMFDataCDR(data: Record<string, any>) {
timeout: 60_000, timeout: 60_000,
}); });
} }
/**
* SMF-在线订阅用户列表信息
* @param query 查询参数
* @returns object
*/
export function listSMFSubscribers(query: Record<string, any>) {
return request({
url: '/neData/smf/subscribers',
method: 'get',
params: query,
});
}

42
src/api/neData/smsc.ts Normal file
View File

@@ -0,0 +1,42 @@
import { request } from '@/plugins/http-fetch';
/**
* 查询SMSC-CDR会话事件
* @param query 查询参数
* @returns object
*/
export function listSMSCDataCDR(query: Record<string, any>) {
return request({
url: '/neData/smsc/cdr/list',
method: 'get',
params: query,
});
}
/**
* SMSC-CDR会话删除
* @param id 信息ID
* @returns object
*/
export function delSMSCDataCDR(cdrIds: string | number) {
return request({
url: `/neData/smsc/cdr/${cdrIds}`,
method: 'delete',
timeout: 60_000,
});
}
/**
* SMSC-CDR会话列表导出
* @param data 查询列表条件
* @returns object
*/
export function exportSMSCDataCDR(data: Record<string, any>) {
return request({
url: '/neData/smsc/cdr/export',
method: 'post',
data,
responseType: 'blob',
timeout: 60_000,
});
}

View File

@@ -113,7 +113,6 @@ export function batchDelUDMAuth(neId: string, imsi: string, num: number) {
/** /**
* UDM鉴权用户导入 * UDM鉴权用户导入
* @param neId 网元ID
* @param data 表单数据对象 * @param data 表单数据对象
* @returns object * @returns object
*/ */

View File

@@ -52,9 +52,11 @@ export async function listUENumByIMS(neId: String) {
method: 'get', method: 'get',
}); });
if (result.code === RESULT_CODE_SUCCESS) { if (result.code === RESULT_CODE_SUCCESS) {
return Object.assign(result, { let num = result.data['ueNum'] || 0;
data: result.data['ueNum'], if (num === 0) {
}); num = result.data.data['ueNum'] || 0;
}
return Object.assign(result, { data: num });
} }
// 模拟数据 // 模拟数据

View File

@@ -0,0 +1,19 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
import { parseDateToStr } from '@/utils/date-utils';
/**
* 新 查询自定义指标数据
* @param query 查询参数
* @returns object
*/
export async function listCustomData(query: Record<string, any>) {
// 发起请求
const result = await request({
url: `/pm/kpiC/report`,
method: 'get',
params: query,
});
return result;
}

View File

@@ -8,57 +8,72 @@ import { parseDateToStr } from '@/utils/date-utils';
* @param query 查询参数 * @param query 查询参数
* @returns object * @returns object
*/ */
export async function listCustom(query: Record<string, any>) { // export async function listCustom(query: Record<string, any>) {
let totalSQL = 'select count(*) as total from pm_custom_title where 1=1 '; // let totalSQL = 'select count(*) as total from pm_custom_title where 1=1 ';
let rowsSQL = 'select * from pm_custom_title where 1=1 '; // let rowsSQL = 'select * from pm_custom_title where 1=1 ';
// 查询 // // 查询
let querySQL = ''; // let querySQL = '';
if (query.neType) { // if (query.neType) {
querySQL += ` and ne_type like '%${query.neType}%' `; // querySQL += ` and ne_type like '%${query.neType}%' `;
} // }
// 排序 // // 排序
let sortSql = ' order by update_time '; // let sortSql = ' order by update_time ';
if (query.sortOrder === 'asc') { // if (query.sortOrder === 'asc') {
sortSql += ' asc '; // sortSql += ' asc ';
} else { // } else {
sortSql += ' desc '; // sortSql += ' desc ';
} // }
// 分页 // // 分页
const pageNum = (query.pageNum - 1) * query.pageSize; // const pageNum = (query.pageNum - 1) * query.pageSize;
const limtSql = ` limit ${pageNum},${query.pageSize} `; // const limtSql = ` limit ${pageNum},${query.pageSize} `;
// // 发起请求
// const result = await request({
// url: `/api/rest/databaseManagement/v1/select/omc_db/pm_custom_title`,
// method: 'get',
// params: {
// totalSQL: totalSQL + querySQL,
// rowsSQL: rowsSQL + querySQL + sortSql + limtSql,
// },
// });
// // 解析数据
// if (result.code === RESULT_CODE_SUCCESS) {
// const data: DataList = {
// total: 0,
// rows: [],
// code: result.code,
// msg: result.msg,
// };
// result.data.data.forEach((item: any) => {
// const itemData = item['pm_custom_title'];
// if (Array.isArray(itemData)) {
// if (itemData.length === 1 && itemData[0]['total'] >= 0) {
// data.total = itemData[0]['total'];
// } else {
// data.rows = itemData.map(v => parseObjLineToHump(v));
// }
// }
// });
// return data;
// }
// return result;
// }
/**
* 新 查询自定义指标
* @param query 查询参数
* @returns object
*/
export async function listCustom(query?: Record<string, any>) {
// 发起请求 // 发起请求
const result = await request({ const result = await request({
url: `/api/rest/databaseManagement/v1/select/omc_db/pm_custom_title`, url: `/pm/kpiC/title/totalList`,
method: 'get', method: 'get',
params: { params: query,
totalSQL: totalSQL + querySQL,
rowsSQL: rowsSQL + querySQL + sortSql + limtSql,
},
}); });
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
const data: DataList = {
total: 0,
rows: [],
code: result.code,
msg: result.msg,
};
result.data.data.forEach((item: any) => {
const itemData = item['pm_custom_title'];
if (Array.isArray(itemData)) {
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
data.total = itemData[0]['total'];
} else {
data.rows = itemData.map(v => parseObjLineToHump(v));
}
}
});
return data;
}
return result; return result;
} }
@@ -68,22 +83,10 @@ export async function listCustom(query: Record<string, any>) {
* @returns object * @returns object
*/ */
export async function getCustom(id: string | number) { export async function getCustom(id: string | number) {
// 发起请求 return request({
const result = await request({ url: `/pm/kpiC/title/${id}`,
url: `/api/rest/databaseManagement/v1/select/omc_db/pm_custom_title`,
method: 'get', method: 'get',
params: {
SQL: `select * from pm_custom_title where id = ${id}`,
},
}); });
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
let data = result.data.data[0];
return Object.assign(result, {
data: parseObjLineToHump(data['pm_custom_title'][0]),
});
}
return result;
} }
/** /**
@@ -92,21 +95,10 @@ export async function getCustom(id: string | number) {
* @returns object * @returns object
*/ */
export function addCustom(data: Record<string, any>) { export function addCustom(data: Record<string, any>) {
let obj: any = {
title: data.title,
ne_type: data.neType,
kpi_id: data.kpiId,
object_type: data.objectType,
expression: data.expression,
period: data.period,
description: data.description,
kpi_set: data.kpiSet,
};
return request({ return request({
url: `/api/rest/databaseManagement/v1/omc_db/pm_custom_title`, url: `/pm/kpiC/title`,
method: 'post', method: 'post',
data: { 'data': [obj] }, data: data,
}); });
} }
@@ -116,20 +108,10 @@ export function addCustom(data: Record<string, any>) {
* @returns object * @returns object
*/ */
export function updateCustom(data: Record<string, any>) { export function updateCustom(data: Record<string, any>) {
let obj: any = {
title: data.title,
ne_type: data.neType,
kpi_id: data.kpiId,
object_type: data.objectType,
expression: data.expression,
period: data.period,
description: data.description,
kpi_set: data.kpiSet,
};
return request({ return request({
url: `/api/rest/databaseManagement/v1/omc_db/pm_custom_title?WHERE=id=${data.id}`, url: `/pm/kpiC/title/${data.id}`,
method: 'put', method: 'put',
data: { data: obj }, data: data,
}); });
} }
@@ -139,8 +121,7 @@ export function updateCustom(data: Record<string, any>) {
*/ */
export async function delCustom(data: Record<string, any>) { export async function delCustom(data: Record<string, any>) {
return request({ return request({
url: `/api/rest/databaseManagement/v1/omc_db/pm_custom_title?WHERE=id=${data.id}`, url: `/pm/kpiC/title/${data.id}`,
method: 'delete', method: 'delete',
}); });
} }

View File

@@ -18,10 +18,21 @@ export function dumpStop(data: Record<string, string>) {
}); });
} }
// 网元抓包PACP 下载
export function dumpDownload(data: Record<string, any>) {
return request({
url: '/trace/tcpdump/download',
method: 'get',
params: data,
responseType: 'blob',
timeout: 60_000,
});
}
// UPF标准版内部抓包 // UPF标准版内部抓包
export function traceUPF(data: Record<string, string>) { export function traceUPF(data: Record<string, string>) {
return request({ return request({
url: '/trace/tcpdump/traceUPF', url: '/trace/tcpdump/upf',
method: 'post', method: 'post',
data: data, data: data,
}); });

104
src/api/trace/task.ts Normal file
View File

@@ -0,0 +1,104 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
/**
* 查询跟踪任务列表
* @param query 查询参数
* @returns object
*/
export async function listTraceTask(query: Record<string, any>) {
return request({
url: '/trace/task/list',
method: 'get',
params: query,
});
}
/**
* 查询跟踪任务信息
* @param id 网元ID
* @returns object
*/
export async function getTraceTask(id: string | number) {
return request({
url: `/trace/task/${id}`,
method: 'get',
});
}
/**
* 新增任务
* @param data 网元对象
* @returns object
*/
export function addTraceTask(data: Record<string, any>) {
return request({
url: `/trace/task`,
method: 'post',
data: data,
});
}
/**
* 修改任务
* @param data 网元对象
* @returns object
*/
export function updateTraceTask(data: Record<string, any>) {
return request({
url: `/trace/task`,
method: 'put',
data: data,
});
}
/**
* 跟踪任务删除
* @param ids ID多个逗号分隔
* @returns object
*/
export async function delTraceTask(ids: string) {
return request({
url: `/trace/task/${ids}`,
method: 'delete',
});
}
/**
* 跟踪任务文件
* @param query 对象
* @returns object
*/
export function filePullTask(traceId: string) {
return request({
url: '/trace/task/filePull',
method: 'get',
params: { traceId },
responseType: 'blob',
timeout: 60_000,
});
}
/**
* 获取网元跟踪接口列表
* @returns object
*/
export async function getNeTraceInterfaceAll() {
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/ne_info`,
method: 'get',
params: {
SQL: `SELECT ne_type,interface FROM trace_info GROUP BY ne_type,interface`,
},
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
let data = result.data.data[0];
return Object.assign(result, {
data: parseObjLineToHump(data['trace_info']),
});
}
return result;
}

83
src/api/trace/taskHLR.ts Normal file
View File

@@ -0,0 +1,83 @@
import { request } from '@/plugins/http-fetch';
/**
* 查询跟踪任务列表
* @param query 查询参数
* @returns object
*/
export function listTaskHLR(query: Record<string, any>) {
return request({
url: '/trace/task/hlr/list',
method: 'get',
params: query,
});
}
/**
* 跟踪任务删除
* @param ids 任务ID
* @returns object
*/
export function delTaskHLR(ids: string | number) {
return request({
url: `/trace/task/hlr/${ids}`,
method: 'delete',
timeout: 60_000,
});
}
/**
* 跟踪任务创建
* @param data 对象
* @returns object
*/
export function startTaskHLR(data: Record<string, any>) {
return request({
url: '/trace/task/hlr/start',
method: 'post',
data: data,
timeout: 60_000,
});
}
/**
* 跟踪任务停止
* @param data 对象
* @returns object
*/
export function stopTaskHLR(data: Record<string, any>) {
return request({
url: '/trace/task/hlr/stop',
method: 'post',
data: data,
timeout: 60_000,
});
}
/**
* 跟踪任务文件
* @param data 对象
* @returns object
*/
export function fileTaskHLR(data: Record<string, any>) {
return request({
url: '/trace/task/hlr/file',
method: 'post',
data: data,
});
}
/**
* 跟踪任务文件从网元到本地
* @param query 对象
* @returns object
*/
export function filePullTaskHLR(query: Record<string, any>) {
return request({
url: '/trace/task/hlr/filePull',
method: 'get',
params: query,
responseType: 'blob',
timeout: 60_000,
});
}

View File

@@ -1,154 +0,0 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
/**
* 查询任务列表
* @param query 查询参数
* @returns object
*/
export async function listTraceTask(query: Record<string, any>) {
let totalSQL = 'select count(*) as total from trace_task where 1=1 ';
let rowsSQL = 'select * from trace_task where 1=1 ';
// 查询
let querySQL = '';
if (query.imsi) {
querySQL += ` and imsi like '%${query.imsi}%' `;
}
if (query.beginTime) {
querySQL += ` and start_time >= '${query.beginTime}' `;
}
if (query.endTime) {
querySQL += ` and end_time <= '${query.endTime}' `;
}
// 分页
const pageNum = (query.pageNum - 1) * query.pageSize;
const limtSql = ` limit ${pageNum},${query.pageSize} `;
// 排序
let sortSql = ' order by start_time ';
if (query.sortOrder === 'asc') {
sortSql += ' asc ';
} else {
sortSql += ' desc ';
}
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/select/omc_db/trace_task`,
method: 'get',
params: {
totalSQL: totalSQL + querySQL,
rowsSQL: rowsSQL + querySQL + sortSql + limtSql,
},
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
const data: DataList = {
total: 0,
rows: [],
code: result.code,
msg: result.msg,
};
result.data.data.forEach((item: any) => {
const itemData = item['trace_task'];
if (Array.isArray(itemData)) {
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
data.total = itemData[0]['total'];
} else {
data.rows = itemData.map(v => parseObjLineToHump(v));
}
}
});
return data;
}
return result;
}
/**
* 查询任务详细
* @param id 网元ID
* @returns object
*/
export async function getTraceTask(id: string | number) {
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/select/omc_db/trace_task`,
method: 'get',
params: {
SQL: `select * from trace_task where id = ${id}`,
},
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
let data = result.data.data[0];
return Object.assign(result, {
data: parseObjLineToHump(data['trace_task'][0]),
});
}
return result;
}
/**
* 新增任务
* @param data 网元对象
* @returns object
*/
export function addTraceTask(data: Record<string, any>) {
return request({
url: `/api/rest/traceManagement/v1/subscriptions`,
method: 'post',
data: data,
});
}
/**
* 修改任务
* @param data 网元对象
* @returns object
*/
export function updateTraceTask(data: Record<string, any>) {
return request({
url: `/api/rest/traceManagement/v1/subscriptions`,
method: 'put',
data: data,
});
}
/**
* 删除任务
* @param noticeId 网元ID
* @returns object
*/
export async function delTraceTask(id: string) {
return request({
url: `/api/rest/traceManagement/v1/subscriptions?id=${id}`,
method: 'delete',
});
}
/**
* 获取网元跟踪接口列表
* @returns object
*/
export async function getNeTraceInterfaceAll() {
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/ne_info`,
method: 'get',
params: {
SQL: `SELECT ne_type,interface FROM trace_info GROUP BY ne_type,interface`,
},
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
let data = result.data.data[0];
return Object.assign(result, {
data: parseObjLineToHump(data['trace_info']),
});
}
return result;
}

View File

@@ -0,0 +1,11 @@
/**
* worker文件-静态资源文件路径
*/
const baseUrl = import.meta.env.VITE_HISTORY_BASE_URL;
export const scriptUrl = `${
baseUrl.length === 1 && baseUrl.indexOf('/') === 0
? ''
: baseUrl.indexOf('/') === -1
? '/' + baseUrl
: baseUrl
}/wiregasm/worker.js`;

View File

@@ -0,0 +1,227 @@
<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { FitAddon } from '@xterm/addon-fit';
import { Terminal } from '@xterm/xterm';
import '@xterm/xterm/css/xterm.css';
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
import { OptionsType, WS } from '@/plugins/ws-websocket';
const ws = new WS();
const emit = defineEmits(['connect', 'close', 'message']);
const props = defineProps({
/**终端ID必传 */
id: {
type: String,
required: true,
},
/**网元类型,必传 */
neType: {
type: String,
required: true,
},
/**网元ID必传 */
neId: {
type: String,
required: true,
},
/**窗口单行字符数 */
cols: {
type: Number,
default: 80,
},
/**窗口行数 */
rows: {
type: Number,
default: 40,
},
});
/**终端输入DOM节点实例对象 */
const terminalDom = ref<HTMLElement | undefined>(undefined);
/**终端输入实例对象 */
const terminal = ref<any>(null);
/**终端输入渲染 */
function handleRanderXterm(container: HTMLElement | undefined) {
if (!container) return;
const xterm = new Terminal({
cols: props.cols,
rows: props.rows,
lineHeight: 1.2,
fontSize: 12,
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
theme: {
background: '#000000',
},
cursorBlink: true, // 光标闪烁
cursorStyle: 'block',
scrollback: 1000, // 设置历史缓冲区大小为 1000 行
scrollSensitivity: 15,
tabStopWidth: 4,
disableStdin: true, // 禁止输入
});
// 挂载
xterm.open(container);
// 自适应尺寸
const fitAddon = new FitAddon();
xterm.loadAddon(fitAddon);
// 终端尺寸变化触发
xterm.onResize(({ cols, rows }) => {
// console.log('尺寸', cols, rows);
ws.send({
requestId: `resize_${props.id}`,
type: 'resize',
data: { cols, rows },
});
});
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
fitAddon.fit();
});
// 监听元素大小变化
observer.observe(container);
terminal.value = xterm;
}
/**连接打开后回调 */
function wsOpen(ev: any) {
// console.info('wsOpen', ev);
nextTick(() => {
handleRanderXterm(terminalDom.value);
// 连接事件
emit('connect', {
timeStamp: ev.timeStamp,
cols: terminal.value.cols,
rows: terminal.value.rows,
neType: props.neType,
neId: props.neId,
id: props.id,
});
});
}
/**连接错误后回调 */
function wsError(ev: any) {
console.error('wsError', ev);
if (terminal.value != null) {
let message = 'disconnected';
terminal.value.write(`\x1b[31m${message}\x1b[m\r\n`);
} else if (terminalDom.value) {
terminalDom.value.style.background = '#000';
terminalDom.value.style.color = '#ff4d4f';
terminalDom.value.style.height = '60%';
terminalDom.value.innerText = 'disconnected';
}
}
/**连接关闭后回调 */
function wsClose(code: number) {
// console.warn('wsClose', code);
if (terminal.value != null) {
let message = 'disconnected ' + code;
terminal.value.write(`\x1b[31m${message}\x1b[m\r\n`);
}
// 关闭事件
emit('close', {
code: code,
neType: props.neType,
neId: props.neId,
id: props.id,
});
}
/**接收消息后回调 */
function wsMessage(res: Record<string, any>) {
emit('message', res);
// console.log('wsMessage', res);
const { code, requestId, data } = res;
if (code === RESULT_CODE_ERROR) {
console.warn(res.msg);
return;
}
if (!requestId) return;
if (terminal.value != null) {
// 查找的开始输出标记
const parts: string[] = data.split('\u001b[?2004l\r');
if (parts.length > 0) {
let text = parts[parts.length - 1];
// 找到最后输出标记
const lestIndex = text.lastIndexOf('\u001b[?2004h\u001b]0;');
if (lestIndex !== -1) {
text = text.substring(0, lestIndex);
}
if (text === '' || text === '\r\n' || text.startsWith("^C\r\n") ) {
return;
}
// console.log({ parts, text });
terminal.value.write(text);
return;
}
// 无标记
terminal.value.write(data);
}
}
onMounted(() => {
if (props.neType && props.neId) {
// 建立链接
const options: OptionsType = {
url: '/ws/view',
params: {
neType: props.neType,
neId: props.neId,
cols: props.cols,
rows: props.rows,
},
onmessage: wsMessage,
onerror: wsError,
onopen: wsOpen,
onclose: wsClose,
};
ws.connect(options);
}
});
onBeforeUnmount(() => {
ws.close();
});
// 给组件设置属性 ref="xxxTerminal"
// setup内使用 const xxxTerminal = ref();
defineExpose({
/**清除 */
clear: () => {
if (terminal.value != null) {
terminal.value.clear();
}
},
/**发送命令 */
send: (type: string, data: Record<string, any>) => {
ws.send({
requestId: `ssh_${props.id}`,
type,
data,
});
},
/**模拟按下 Ctrl+C */
ctrlC: () => {
ws.send({
requestId: `ssh_${props.id}`,
type: 'ctrl-c',
});
},
});
</script>
<template>
<div ref="terminalDom" :id="id" class="terminal"></div>
</template>
<style lang="css" scoped>
.terminal {
width: 100%;
height: 100%;
}
</style>

View File

@@ -119,7 +119,7 @@ function fnAutoCompleteKeydown(evt: any) {
ws.send({ ws.send({
requestId: `telnet_${props.hostId}`, requestId: `telnet_${props.hostId}`,
type: 'telnet', type: 'telnet',
data: `${cmdStr}\r\n'`, data: `${cmdStr}\r\n`,
}); });
terminalState.text = ' '; terminalState.text = ' ';
@@ -155,6 +155,15 @@ function handleRanderXterm(container: HTMLElement | undefined) {
// 自适应尺寸 // 自适应尺寸
const fitAddon = new FitAddon(); const fitAddon = new FitAddon();
xterm.loadAddon(fitAddon); xterm.loadAddon(fitAddon);
// 终端尺寸变化触发
xterm.onResize(({ cols, rows }) => {
// console.log('尺寸', cols, rows);
ws.send({
requestId: `telnet_resize_${props.hostId}`,
type: 'telnet_resize',
data: { cols, rows },
});
});
// 创建 ResizeObserver 实例 // 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => { var observer = new ResizeObserver(entries => {
@@ -280,7 +289,7 @@ defineExpose({
<template> <template>
<div class="terminal"> <div class="terminal">
<div ref="terminalDom" style="height: 78%" :id="id"></div> <div ref="terminalDom" style="height: calc(100% - 36px)" :id="id"></div>
<a-auto-complete <a-auto-complete
v-model:value="terminalState.text" v-model:value="terminalState.text"
:dropdown-match-select-width="500" :dropdown-match-select-width="500"

View File

@@ -0,0 +1,204 @@
<script lang="ts" setup>
import { reactive, ref, computed, unref, onUpdated, watchEffect } from 'vue';
const props = defineProps({
/**列表高度 */
height: {
type: Number,
default: 300,
},
/**列表项高度 */
itemHeight: {
type: Number,
default: 30,
},
/**数据 */
data: {
type: Array,
default: () => [],
},
/**预先兜底缓存数量 */
cache: {
type: Number,
default: 2,
},
/**是否动态加载 */
dynamic: {
type: Boolean,
default: false,
},
});
const state = reactive<any>({
start: 0,
end: 10,
scrollOffset: 0,
cacheData: [],
});
const virtualListRef = ref();
const getWrapperStyle = computed(() => {
const { height } = props;
return {
height: `${height}px`,
};
});
const getInnerStyle = computed(() => {
return {
height: `${unref(getTotalHeight)}px`,
width: '100%',
};
});
const getListStyle = computed(() => {
return {
willChange: 'transform',
transform: `translateY(${state.scrollOffset}px)`,
};
});
// 数据数量
const total = computed(() => {
return props.data.length;
});
// 总体高度
const getTotalHeight = computed(() => {
if (!props.dynamic) return unref(total) * props.itemHeight;
return getCurrentTop(unref(total));
});
// 当前屏幕显示的数量
const clientCount = computed(() => {
return Math.ceil(props.height / props.itemHeight);
});
// 当前屏幕显示的数据
const clientData = computed(() => {
return props.data.slice(state.start, state.end);
});
const onScroll = (e: any) => {
const { scrollTop } = e.target;
if (state.scrollOffset === scrollTop) return;
const { cache, dynamic, itemHeight } = props;
const cacheCount = Math.max(1, cache);
let startIndex = dynamic
? getStartIndex(scrollTop)
: Math.floor(scrollTop / itemHeight);
const endIndex = Math.max(
0,
Math.min(unref(total), startIndex + unref(clientCount) + cacheCount)
);
if (startIndex > cacheCount) {
startIndex = startIndex - cacheCount;
}
// 偏移量
const offset = dynamic
? getCurrentTop(startIndex)
: scrollTop - (scrollTop % itemHeight);
Object.assign(state, {
start: startIndex,
end: endIndex,
scrollOffset: offset,
});
};
// 二分法去查找对应的index
const getStartIndex = (scrollTop = 0): number => {
let low = 0;
let high = state.cacheData.length - 1;
while (low <= high) {
const middle = low + Math.floor((high - low) / 2);
const middleTopValue = getCurrentTop(middle);
const middleBottomValue = getCurrentTop(middle + 1);
if (middleTopValue <= scrollTop && scrollTop <= middleBottomValue) {
return middle;
} else if (middleBottomValue < scrollTop) {
low = middle + 1;
} else if (middleBottomValue > scrollTop) {
high = middle - 1;
}
}
return Math.min(
unref(total) - unref(clientCount),
Math.floor(scrollTop / props.itemHeight)
);
};
const getCurrentTop = (index: number) => {
const lastIndex = state.cacheData.length - 1;
if (Object.hasOwn(state.cacheData, index)) {
return state.cacheData[index].top;
} else if (Object.hasOwn(state.cacheData, index - 1)) {
return state.cacheData[index - 1].bottom;
} else if (index > lastIndex) {
return (
state.cacheData[lastIndex].bottom +
Math.max(0, index - state.cacheData[lastIndex].index) * props.itemHeight
);
} else {
return index * props.itemHeight;
}
};
onUpdated(() => {
if (!props.dynamic) return;
const childrenList = virtualListRef.value.children || [];
[...childrenList].forEach((node: any, index: number) => {
const height = node.getBoundingClientRect().height;
const currentIndex = state.start + index;
if (state.cacheData[currentIndex].height === height) return;
state.cacheData[currentIndex].height = height;
state.cacheData[currentIndex].top = getCurrentTop(currentIndex);
state.cacheData[currentIndex].bottom =
state.cacheData[currentIndex].top + state.cacheData[currentIndex].height;
});
});
watchEffect(() => {
clientData.value.forEach((_, index) => {
const currentIndex = state.start + index;
if (Object.hasOwn(state.cacheData, currentIndex)) return;
state.cacheData[currentIndex] = {
top: currentIndex * props.itemHeight,
height: props.itemHeight,
bottom: (currentIndex + 1) * props.itemHeight,
index: currentIndex,
};
});
});
</script>
<template>
<div
class="virtual-list-wrapper"
ref="wrapperRef"
:style="getWrapperStyle"
@scroll="onScroll"
>
<div class="virtual-list-inner" ref="innerRef" :style="getInnerStyle">
<div class="virtual-list" :style="getListStyle" ref="virtualListRef">
<div v-for="(item, index) in clientData" :key="index + state.start">
<slot name="default" :item="item"></slot>
</div>
</div>
</div>
</div>
</template>
<style lang="css" scoped>
.virtual-list-wrapper {
position: relative;
overflow-y: auto;
}
</style>

View File

@@ -3,3 +3,6 @@ export const APP_REQUEST_HEADER_CODE = 'X-App-Code';
/**应用-请求头-系统版本 */ /**应用-请求头-系统版本 */
export const APP_REQUEST_HEADER_VERSION = 'X-App-Version'; export const APP_REQUEST_HEADER_VERSION = 'X-App-Version';
/**应用-请求数据-密钥 */
export const APP_DATA_API_KEY = 'T9ox2DCzpLfJIPzkH9pKhsOTMOEMJcFv';

View File

@@ -16,11 +16,12 @@ export const NE_TYPE_LIST = [
'N3IWF', 'N3IWF',
'MOCNGW', 'MOCNGW',
'SMSC', 'SMSC',
'CBC',
]; ];
/** /**
* 网元拓展包列表,默认顺序 * 网元拓展包列表,默认顺序
* IMS-adb/rtproxy/mf * IMS-adb/kvdb/rtproxy/mf
* UDM-adb * UDM-adb/kvdb
*/ */
export const NE_EXPAND_LIST = ['ADB', 'RTPROXY', 'MF']; export const NE_EXPAND_LIST = ['ADB', 'KVDB', 'RTPROXY', 'MF'];

View File

@@ -1,3 +1,12 @@
/**响应-code加密数据 */
export const RESULT_CODE_ENCRYPT = 2;
/**响应-msg加密数据 */
export const RESULT_MSG_ENCRYPT: Record<string, string> = {
zh_CN: '加密!',
en_US: 'encrypt!',
};
/**响应-code正常成功 */ /**响应-code正常成功 */
export const RESULT_CODE_SUCCESS = 1; export const RESULT_CODE_SUCCESS = 1;

View File

@@ -149,6 +149,7 @@ export default {
page403: 'No Access', page403: 'No Access',
page404: 'Match Page Not Found', page404: 'Match Page Not Found',
helpDoc: 'System User Documentation', helpDoc: 'System User Documentation',
traceTaskHLR: 'Tracking Tasks HLR',
lockScreen: 'Lock Screen', lockScreen: 'Lock Screen',
account: { account: {
index: "Personal Center", index: "Personal Center",
@@ -185,11 +186,11 @@ export default {
helpDoc: 'Doc', helpDoc: 'Doc',
}, },
rightContent: { rightContent: {
alarm: "Active Alarms",
lock: "Lock Screen", lock: "Lock Screen",
lockTip: "Confirmation of the lock screen?", lockTip: "Confirmation of the lock screen?",
lockPasswd: "Unlock Password", lockPasswd: "Unlock Password",
lockPasswdTip: "No password can be set", lockPasswdTip: "No password can be set",
helpDoc: "System User Documentation",
fullscreen: "Full Screen", fullscreen: "Full Screen",
logout: "Logout", logout: "Logout",
profile: "Profile", profile: "Profile",
@@ -569,6 +570,8 @@ export default {
caller: "Caller", caller: "Caller",
called: "Called", called: "Called",
result: "Result", result: "Result",
resultOk: "Success",
resultFail: "Fail",
delTip: "Confirm deletion of the data item numbered [{msg}]?", delTip: "Confirm deletion of the data item numbered [{msg}]?",
exportTip: "Do you confirm to export the current query conditions of the CDR data? (Maximum 10,000 items can be exported.)", exportTip: "Do you confirm to export the current query conditions of the CDR data? (Maximum 10,000 items can be exported.)",
smfChargingID: 'Charging ID', smfChargingID: 'Charging ID',
@@ -658,6 +661,7 @@ export default {
kpiEnable: 'Report', kpiEnable: 'Report',
kpiTimer: 'Reporting Cycle', kpiTimer: 'Reporting Cycle',
kpiTimerPlease: 'Please enter the reporting period (in seconds)', kpiTimerPlease: 'Please enter the reporting period (in seconds)',
omcIP: 'OMC IP',
}, },
backConf: { backConf: {
export: 'Config Export', export: 'Config Export',
@@ -668,9 +672,8 @@ export default {
local:'Local File', local:'Local File',
localUpload:'Local Upload', localUpload:'Local Upload',
exportTip:'Confirm that you want to export the network element configuration file?', exportTip:'Confirm that you want to export the network element configuration file?',
exportMsg:'Exported successfully, please download from [Backup Management].', exportMsg:'Exporting Network Element Configuration Information to a File Succeeded',
filePlease: "Please upload a file", pathPlease: 'No Backup File Found',
fileNamePlease: 'Please select the server file',
}, },
}, },
neHost: { neHost: {
@@ -736,11 +739,11 @@ export default {
upgrade: "Upgrade To New Version", upgrade: "Upgrade To New Version",
upgradeTip: "Confirmed to upgrade to the new version?", upgradeTip: "Confirmed to upgrade to the new version?",
upgradeTipEmpty: "There are currently no new versions available", upgradeTipEmpty: "There are currently no new versions available",
upgradeTipEqual: "Current version is the same as the new version", upgradeTipEqual: "The current version is the same as the new version, confirmed to update?",
rollback: 'Switch to previous version', rollback: 'Switch to previous version',
rollbackTip: "Confirm switching to the previous version?", rollbackTip: "Confirm switching to the previous version?",
rollbackTipEmpty: "There is currently no previous version available", rollbackTipEmpty: "There is currently no previous version available",
rollbackTipEqual: 'The current version is the same as the previous version', rollbackTipEqual: 'The current version is the same as the previous version, are you sure you want to make the switch?',
version: "Current Version", version: "Current Version",
preVersion: "Previous Version", preVersion: "Previous Version",
newVersion: "New Version", newVersion: "New Version",
@@ -770,18 +773,38 @@ export default {
uploadChangeOk: 'Network Element renewed license successfully and is being calibrated in the background!', uploadChangeOk: 'Network Element renewed license successfully and is being calibrated in the background!',
uploadChangeFail: "Some network elements failed to update the license, please check whether the service terminal environment is available!", uploadChangeFail: "Some network elements failed to update the license, please check whether the service terminal environment is available!",
}, },
neConfPara5G: { neConfig: {
headerTip: 'Check and save the public parameter properties of the network element before performing the installation of the network element, and make sure that the latest parameter properties are applied.', treeTitle: "Navigation Configuration",
headerTipToPage: 'Jump Installation', treeSelectTip: "Select configuration item information in the left configuration navigation!",
save: 'Save', neType: 'NE Type',
reload: 'Reload', neTypePleace: "Please select the network element type",
title: 'Save Info', noConfigData: "No data on configuration items",
sync: 'Sync to NE', updateValue: "[ {num} ] parameter value modified successfully.",
syncNe: 'Select NE', updateValueErr: "Attribute value modification failure",
syncNeDone: 'Synchronization to network element terminals complete', updateItem: "Modify Index to {num}.",
saveOk: 'Save Success!', updateItemErr: "Record modification failure",
delItemOk: "Deleting Index as {num} succeeded",
addItemOk: "Add Index as {num} Record Succeeded",
addItemErr: "Record addition failure",
requireUn: "[ {display} ] input value is of unknown type",
requireString: "[ {display} ] parameter value is invalid.",
requireInt: "[ {display} ] parameter value not in reasonable range {filter}",
requireIpv4: "[ {display} ] not a legitimate IPV4 address",
requireIpv6: "[ {display} ] not a legitimate IPV6 address.",
requireEnum: "[ {display} ] is not a reasonable enumeration value.",
requireBool: "[ {display} ] is not a reasonable boolean value.",
editOkTip: "Confirm updating the value of this [ {num} ] attribute?",
updateItemTip: "Confirm updating the data item with Index [{num}]?",
delItemTip: "Confirm deleting the data item with Index [{num}]?",
arrayMore: "Expand",
},
neConfigBackup: {
name: "Name",
downTip: 'Confirmed to download the backup file [{txt}]?',
title: "Modify Backup {txt}",
}, },
neQuickSetup: { neQuickSetup: {
reloadPara5G: 'Reload',
stepPrev: 'Previous', stepPrev: 'Previous',
stepPrevTip: 'Confirm that you want to abandon the current change and return to the previous step?', stepPrevTip: 'Confirm that you want to abandon the current change and return to the previous step?',
stepNext: 'Next', stepNext: 'Next',
@@ -854,9 +877,10 @@ export default {
imsiTip3: 'MSIN = Mobile Subscriber Identification Number, consisting of 10 equal digits.', imsiTip3: 'MSIN = Mobile Subscriber Identification Number, consisting of 10 equal digits.',
amfTip: 'Authentication management field, maximum parameter length is 4', amfTip: 'Authentication management field, maximum parameter length is 4',
algoIndexTip: 'Algorithm index, between 0 and 15', algoIndexTip: 'Algorithm index, between 0 and 15',
kiTip: 'User signing key information, the maximum length of 32', kiTip: 'User signing key information, the length can only be 32',
opcTip: 'The authentication key, OPC, is calculated from Ki and OP, OP is the root key of the operator, ki is the authentication key, and the maximum length is 32.', opcTip: 'The authentication key, OPC, is calculated from Ki and OP, OP is the root key of the operator, ki is the authentication key, and the maximum length is 32.',
delSure:'Are you sure you want to delete the user with IMSI number: {imsi} ?', delSure:'Are you sure you want to delete the user with IMSI number: {imsi} ?',
imsiConfirm:'The length of the IMSI must be 15',
}, },
sub: { sub: {
subInfo:' Subscription Info', subInfo:' Subscription Info',
@@ -874,8 +898,8 @@ export default {
checkDel: 'Check Delete', checkDel: 'Check Delete',
batchAddText: 'Batch Add', batchAddText: 'Batch Add',
batchDelText: 'Batch Delete', batchDelText: 'Batch Delete',
enable:'Enable', enable:'Enabled',
disable:'Disable', disable:'Disabled',
startIMSI: 'Start IMSI', startIMSI: 'Start IMSI',
imsiTip: 'IMSI=MCC+MNC+MSIN', imsiTip: 'IMSI=MCC+MNC+MSIN',
imsiTip1: 'MCC=Mobile Country Code, consisting of three digits.', imsiTip1: 'MCC=Mobile Country Code, consisting of three digits.',
@@ -888,7 +912,8 @@ export default {
micoTip: 'Signed MICO business flag bits', micoTip: 'Signed MICO business flag bits',
rfspTip:'RFSP index, in NG-RAN, the index of a specific RRM configuration, parameter between 0 and 127', rfspTip:'RFSP index, in NG-RAN, the index of a specific RRM configuration, parameter between 0 and 127',
ueTypeTip: 'Operator-defined subscriber UE Usage Type, integer, parameter between 0 and 127', ueTypeTip: 'Operator-defined subscriber UE Usage Type, integer, parameter between 0 and 127',
epsFlagTip: 'Enable or disable 4G EPS service', cnFlag: 'Whether to enable 5G Core Network service',
epsFlagTip: 'Whether to enable 4G EPS service',
contextIdTip: 'To sign up for an APN Context ID, you must select it from the APN Context list.', contextIdTip: 'To sign up for an APN Context ID, you must select it from the APN Context list.',
apnContextTip: 'The list of APNs available to the phone, up to six, is defined in the HSS.', apnContextTip: 'The list of APNs available to the phone, up to six, is defined in the HSS.',
staticIpTip: 'Specify the static IP address to be used by the cell phone user to access the Internet, and "-" means dynamic IP address is used.', staticIpTip: 'Specify the static IP address to be used by the cell phone user to access the Internet, and "-" means dynamic IP address is used.',
@@ -1027,6 +1052,7 @@ export default {
}, },
customTarget:{ customTarget:{
kpiId:' Custom Indicator', kpiId:' Custom Indicator',
kpiIdTip:'This Ne has no custom indicators',
period:' Granularity', period:' Granularity',
title:' Custom Indicator Title', title:' Custom Indicator Title',
objectType:' Object type', objectType:' Object type',
@@ -1038,6 +1064,13 @@ export default {
addCustom:' Add custom indicator', addCustom:' Add custom indicator',
editCustom:' Edit Custom indicator', editCustom:' Edit Custom indicator',
errorCustomInfo: 'Failed to get information', errorCustomInfo: 'Failed to get information',
status: 'Status',
active:'Active',
inactive:'Inactive',
symbol:'Symbol',
element:'Element',
granularity:'Granularity',
unit:'Unit',
} }
}, },
traceManage: { traceManage: {
@@ -1063,18 +1096,23 @@ export default {
pcap: { pcap: {
capArgPlease: 'Please enter tcpdump -i any support parameter', capArgPlease: 'Please enter tcpdump -i any support parameter',
cmd: 'Command', cmd: 'Command',
execCmd: "Generic tcpdump packet capture command", execCmd: "Common Command Options",
execCmdsSctp: "Generic tcpdump filter sctp and port commands", execCmd2: "Filter Protocol Port Command",
execUPFCmdA: 'Suitable for anomalous packet capture of other NE', execCmd3: "File Split By Time units of seconds (-G 10), Generated Max File Number (-W 7)",
execUPFCmdB: 'Suitable for UPF anomaly packet capture analysis', execUPFCmdA: 'Standard Edition - UPF with other NE anomalous packet capture analysis',
execUPFCmdB: 'Standard Edition - UPF anomalies requiring packet capture analysis',
batchOper: 'Batch Operations',
batchStartText: 'Batch Start',
batchStopText: 'Batch Stop',
batchDownText: 'Batch Download',
fileView: 'Historical Packet Capture Files',
fileUPF: 'Standard Edition',
fileUPFTip: 'UPF internal packet capture and analysis packet',
textStart: "Start", textStart: "Start",
textStartBatch: "Batch Start",
textStop: "Stop", textStop: "Stop",
textStopBatch: "Batch Stop",
textLog: "Log", textLog: "Log",
textLogMsg: "Log Info", textLogMsg: "Log Info",
textDown: "Download", textDown: "Download",
textDownBatch: "Batch Download",
downTip: "Are you sure you want to download the {title} capture data file?", downTip: "Are you sure you want to download the {title} capture data file?",
downOk: "{title} file download complete", downOk: "{title} file download complete",
downErr: "{title} file download exception", downErr: "{title} file download exception",
@@ -1088,12 +1126,13 @@ export default {
stopNotRun: "{title} not running", stopNotRun: "{title} not running",
}, },
task: { task: {
neTypePlease: 'Query network element type', traceId: 'Tracing No',
neType: 'NE Type',
neID: 'NE ID',
trackType: 'Tracing Type', trackType: 'Tracing Type',
trackTypePlease: 'Please select a tracing type', trackTypePlease: 'Please select a tracing type',
creater: 'Created by', creater: 'Created by',
textStop: "Stop",
status: 'Status',
time: 'Time',
startTime: 'Start Time', startTime: 'Start Time',
endTime: 'End Time', endTime: 'End Time',
msisdn: 'MSISDN', msisdn: 'MSISDN',
@@ -1103,26 +1142,31 @@ export default {
imsiPlease: 'Please enter IMSI', imsiPlease: 'Please enter IMSI',
imsiTip: 'Mobile communication IMSI number', imsiTip: 'Mobile communication IMSI number',
srcIp: 'Source IP Address', srcIp: 'Source IP Address',
srcIpPlease: 'Please enter the source IP address', srcIpPlease: 'Please enter the IP address',
srcIpTip: 'Current sender IPv4 address', srcIpTip: 'Current sender IPv4 address',
dstIp: 'Destination IP Address', dstIp: 'Destination IP Address',
dstIpPlease: 'Please enter the destination IP address', dstIpPlease: 'Please enter the IP address',
dstIpTip: 'IPv4 address of the receiving end of the other party', dstIpTip: 'IPv4 address of the receiving end of the other party',
interfaces: 'Signaling Interface', interfaces: 'Signaling Interface',
interfacesPlease: 'Please enter the signaling interface', interfacesPlease: 'Please enter the signaling interface',
signalPort: 'Signal Port', signalPort: 'Signal Port',
signalPortPlease: 'Please enter the signaling port', signalPortPlease: 'Please enter the signaling port',
signalPortTip: 'Port corresponding to the interface', signalPortTip: 'Port of the side corresponding to the destination IP address or source IP address',
rangePicker: 'Start/End Time', rangePicker: 'Start/End Time',
rangePickerPlease: 'Please select the start and end time of the task', rangePickerPlease: 'Please select the start and end time of the task',
comment: 'Task Description', remark: 'Remark',
commentPlease: 'Task description can be entered', remarkPlease: 'Task description can be entered',
addTask: 'Add Task', addTask: 'Add Task',
editTask: 'Modify Task', editTask: 'Modify Task',
viewTask: 'View Task', viewTask: 'View Task',
errorTaskInfo: 'Failed to obtain task information', errorTaskInfo: 'Failed to obtain task information',
delTask: 'Successfully deleted task {num}', delTaskTip: 'Are you sure to delete the data item with record ID {id} ?',
delTaskTip: 'Are you sure to delete the data item with record number {num}?', stopTask: 'Successful cessation of tasks {id}',
stopTaskTip: 'Confirm stopping the task with record ID {id} ?',
pcapView: "Tracking Data Analysis",
traceFile: "Tracking File",
errMsg: "Error Message",
imsiORmsisdn: "imsi or msisdn is null, cannot start task",
}, },
}, },
faultManage: { faultManage: {
@@ -1235,7 +1279,20 @@ export default {
downTip: "Confirm the download file name is [{fileName}] File?", downTip: "Confirm the download file name is [{fileName}] File?",
downTipErr: "Failed to get file", downTipErr: "Failed to get file",
dirCd: "Enter Dir", dirCd: "Enter Dir",
viewAs: 'View Action',
reload: "Reload",
follow: 'Monitoring Content',
tailChar: 'End Characters',
tailLines: 'End Lines',
}, },
exportFile:{
fileName:'File Source',
downTip: "Confirm the download file name is [{fileName}] File?",
downTipErr: "Failed to get file",
deleteTip: "Confirm the delete file name is [{fileName}] File?",
deleteTipErr: "Failed to delete file",
selectTip:"Please select File Name",
}
}, },
monitor: { monitor: {
session: { session: {
@@ -1947,6 +2004,7 @@ export default {
stepNeInfoStepNext: 'Confirm that you want to proceed to the next step to configure the parameters of the network element?', stepNeInfoStepNext: 'Confirm that you want to proceed to the next step to configure the parameters of the network element?',
stepPara5GTitle: "Configuration Parameter", stepPara5GTitle: "Configuration Parameter",
stepPara5GDesc: "Setting network element global parameter information", stepPara5GDesc: "Setting network element global parameter information",
savePara5GOk: 'Save Success!',
stepPara5GStepPrev: 'Confirm that you want to abandon the current change and return to the previous step?', stepPara5GStepPrev: 'Confirm that you want to abandon the current change and return to the previous step?',
stepPara5GStepNext: 'Confirm that you want to proceed to the next step for the network element service installation?', stepPara5GStepNext: 'Confirm that you want to proceed to the next step for the network element service installation?',
stepInstallTitle: "Service Install", stepInstallTitle: "Service Install",
@@ -2047,6 +2105,30 @@ export default {
hostSelectMore: "Load More {num}", hostSelectMore: "Load More {num}",
hostSelectHeader: "Host List", hostSelectHeader: "Host List",
}, },
ps:{
realTimeHigh:"High",
realTimeLow:"Low",
realTimeRegular:"Regular",
realTimeStop:"Stop",
realTime:"Real Time Speed",
pid:"PID",
name:"APP Name",
username:"User Name",
runTime:"Run Time",
numThreads:"Thread",
cpuPercent:"CPU Percent",
diskRead:"Disk Read",
diskWrite:"Disk Write",
},
net:{
PID:"PID",
name:"name",
localAddr:"localAddr",
remoteAddr:"remoteAddr",
status:"status",
type:"type",
port:"port",
},
}, },
}, },
}; };

View File

@@ -149,6 +149,7 @@ export default {
page403: '没有访问权限', page403: '没有访问权限',
page404: '找不到匹配页面', page404: '找不到匹配页面',
helpDoc: '系统使用文档', helpDoc: '系统使用文档',
traceTaskHLR: '跟踪任务 HLR',
lockScreen: '锁屏', lockScreen: '锁屏',
account: { account: {
index: "个人中心", index: "个人中心",
@@ -185,11 +186,11 @@ export default {
helpDoc: '使用手册', helpDoc: '使用手册',
}, },
rightContent: { rightContent: {
alarm: "活动告警",
lock: "锁屏", lock: "锁屏",
lockTip: "确认要进行锁屏吗?", lockTip: "确认要进行锁屏吗?",
lockPasswd: "解锁密码", lockPasswd: "解锁密码",
lockPasswdTip: "可不设置密码", lockPasswdTip: "可不设置密码",
helpDoc: "系统使用文档",
fullscreen: "全屏显示", fullscreen: "全屏显示",
logout: "退出登录", logout: "退出登录",
profile: "个人中心", profile: "个人中心",
@@ -569,6 +570,8 @@ export default {
caller: "主叫", caller: "主叫",
called: "被叫", called: "被叫",
result: "结果", result: "结果",
resultOk: "成功",
resultFail: "失败",
delTip: "确认删除编号为【{msg}】的数据项?", delTip: "确认删除编号为【{msg}】的数据项?",
exportTip: "确认导出当前查询条件的话单数据吗?(导出最大支持一万条)", exportTip: "确认导出当前查询条件的话单数据吗?(导出最大支持一万条)",
smfChargingID: '计费ID', smfChargingID: '计费ID',
@@ -658,6 +661,7 @@ export default {
kpiEnable: '上报', kpiEnable: '上报',
kpiTimer: '上报周期', kpiTimer: '上报周期',
kpiTimerPlease: '请输入上报周期(单位秒)', kpiTimerPlease: '请输入上报周期(单位秒)',
omcIP: 'OMC IP',
}, },
backConf: { backConf: {
export: '配置导出', export: '配置导出',
@@ -668,9 +672,8 @@ export default {
local:'本地文件', local:'本地文件',
localUpload:'本地上传', localUpload:'本地上传',
exportTip:'确认要导出网元配置信息到文件?', exportTip:'确认要导出网元配置信息到文件?',
exportMsg:'导出成功,请到【备份管理】进行下载', exportMsg:'导出网元配置信息到文件成功',
filePlease: "请上传文件", pathPlease: '未发现文件',
fileNamePlease: '请选择服务器文件',
}, },
}, },
neHost: { neHost: {
@@ -736,11 +739,11 @@ export default {
upgrade: "升级到新版本", upgrade: "升级到新版本",
upgradeTip: "确认要升级到新版本吗?", upgradeTip: "确认要升级到新版本吗?",
upgradeTipEmpty: "当前没有可用的新版本", upgradeTipEmpty: "当前没有可用的新版本",
upgradeTipEqual: "当前版本与新版本相同", upgradeTipEqual: "当前版本与新版本相同,确认要进行更新吗?",
rollback: '切换到上一个版本', rollback: '切换到上一个版本',
rollbackTip: "确认切换到上一个版本吗?", rollbackTip: "确认切换到上一个版本吗?",
rollbackTipEmpty: "目前没有可用的上一个版本", rollbackTipEmpty: "目前没有可用的上一个版本",
rollbackTipEqual: '当前版本与之前版本相同', rollbackTipEqual: '当前版本与之前版本相同,确认要进行切换吗?',
version: "当前版本", version: "当前版本",
preVersion: "上一个版本", preVersion: "上一个版本",
newVersion: "新版本", newVersion: "新版本",
@@ -770,18 +773,38 @@ export default {
uploadChangeOk: '网元更新许可证成功,正在后台校验!', uploadChangeOk: '网元更新许可证成功,正在后台校验!',
uploadChangeFail: "部分网元更新许可证失败,请检查服务终端环境是否可用!", uploadChangeFail: "部分网元更新许可证失败,请检查服务终端环境是否可用!",
}, },
neConfPara5G: { neConfig: {
headerTip: '进行网元安装前检查并保存网元公共参数属性,确认应用为最新参数属性', treeTitle: "配置导航",
headerTipToPage: '跳转安装', treeSelectTip: "左侧配置导航中选择配置项信息!",
save: '保存', neType: "网元类型",
reload: '刷新', neTypePleace: "请选择网元类型",
title: '保存信息', noConfigData: "暂无配置项数据",
sync: '同步到网元', updateValue: "【 {num} 】 属性值修改成功",
syncNe: '选择网元', updateValueErr: "属性值修改失败",
syncNeDone: '同步到网元终端完成', updateItem: "修改 Index 为 {num} 记录成功",
saveOk: '保存成功!', updateItemErr: "记录修改失败",
delItemOk: "删除 Index 为 {num} 记录成功",
addItemOk: "新增 Index 为 {num} 记录成功",
addItemErr: "记录新增失败",
requireUn: "【 {display} 】输入值是未知类型",
requireString: "【 {display} 】参数值不合理",
requireInt: "【 {display} 】参数值不在合理范围 {filter}",
requireIpv4: "【 {display} 】不是合法的IPV4地址",
requireIpv6: "【 {display} 】不是合法的IPV6地址",
requireEnum: "【 {display} 】不是合理的枚举值",
requireBool: "【 {display} 】不是合理的布尔类型的值",
editOkTip: "确认更新该【 {num} 】属性值吗?",
updateItemTip: "确认更新Index为 【{num}】 的数据项?",
delItemTip: "确认删除Index为 【{num}】 的数据项?",
arrayMore: "展开",
},
neConfigBackup: {
name: "名称",
downTip: '确认要下载备份文件【{txt}】吗?',
title: "修改备份信息 {txt}",
}, },
neQuickSetup: { neQuickSetup: {
reloadPara5G: '刷新',
stepPrev: '上一步', stepPrev: '上一步',
stepPrevTip: '确认要放弃当前变更返回上一步吗?', stepPrevTip: '确认要放弃当前变更返回上一步吗?',
stepNext: '下一步', stepNext: '下一步',
@@ -854,9 +877,10 @@ export default {
imsiTip3: 'MSIN=移动客户识别码采用等长10位数字构成', imsiTip3: 'MSIN=移动客户识别码采用等长10位数字构成',
amfTip: '鉴权管理域,参数最大长度为 4', amfTip: '鉴权管理域,参数最大长度为 4',
algoIndexTip: '算法索引介于0到15之间', algoIndexTip: '算法索引介于0到15之间',
kiTip: '用户签权密钥信息,最大长度为32', kiTip: '用户签权密钥信息,长度只能是32',
opcTip: '鉴权秘钥OPC是由Ki和OP经过计算得来的OP为运营商的根秘钥ki是鉴权秘钥,最大长度为32', opcTip: '鉴权秘钥OPC是由Ki和OP经过计算得来的OP为运营商的根秘钥ki是鉴权秘钥,最大长度为32',
delSure:'确认删除IMSI编号为: {imsi} 的用户吗?', delSure:'确认删除IMSI编号为: {imsi} 的用户吗?',
imsiConfirm:'IMSI的长度必须为15',
}, },
sub: { sub: {
subInfo:'签约信息', subInfo:'签约信息',
@@ -888,6 +912,7 @@ export default {
micoTip: '签约的 MICO 业务标志位', micoTip: '签约的 MICO 业务标志位',
rfspTip:'RFSP 索引,在 NG-RAN 中,特定 RRM 配置的索引,参数介于0到127之间', rfspTip:'RFSP 索引,在 NG-RAN 中,特定 RRM 配置的索引,参数介于0到127之间',
ueTypeTip: '运营商定义的用户 UE Usage Type整型参数介于0到127之间', ueTypeTip: '运营商定义的用户 UE Usage Type整型参数介于0到127之间',
cnFlag: '是否开启 5G Core Network 服务',
epsFlagTip: '是否开启 4G EPS 服务', epsFlagTip: '是否开启 4G EPS 服务',
contextIdTip: '签约APN 上下文ID必须从APN Context list 中选择。', contextIdTip: '签约APN 上下文ID必须从APN Context list 中选择。',
apnContextTip: '手机可用的APN列表最多六个在HSS中定义。', apnContextTip: '手机可用的APN列表最多六个在HSS中定义。',
@@ -1027,6 +1052,7 @@ export default {
}, },
customTarget:{ customTarget:{
kpiId:'自定义指标项', kpiId:'自定义指标项',
kpiIdTip:'该网元没有自定义指标',
period:'颗粒度', period:'颗粒度',
title:'自定义指标项标题', title:'自定义指标项标题',
objectType:'对象类型', objectType:'对象类型',
@@ -1038,6 +1064,13 @@ export default {
addCustom:'添加自定义指标', addCustom:'添加自定义指标',
editCustom:'编辑自定义指标', editCustom:'编辑自定义指标',
errorCustomInfo: '获取信息失败', errorCustomInfo: '获取信息失败',
status: '状态',
active:'正常',
inactive:'停用',
symbol:"符号",
element:'元素',
granularity:'颗粒度',
unit:'单位',
} }
}, },
traceManage: { traceManage: {
@@ -1063,18 +1096,23 @@ export default {
pcap: { pcap: {
capArgPlease: '请输入tcpdump -i any支持参数', capArgPlease: '请输入tcpdump -i any支持参数',
cmd: '命令', cmd: '命令',
execCmd: "通用tcpdump抓包命令", execCmd: "通用命令选项",
execCmdsSctp: "过滤sctp和port命令", execCmd2: "过滤协议端口命令",
execUPFCmdA: '适合其他网元异常UPF配合抓包的情况', execCmd3: "分割文件按时间单位秒 (-G 10 ),最多生成文件数量 (-W 7)",
execUPFCmdB: '适合UPF异常需要抓包分析的情况', execUPFCmdA: '标准版-UPF配合其他网元异常抓包分析',
execUPFCmdB: '标准版-UPF异常需要抓包分析',
batchOper: '批量操作',
batchStartText: '批量开始',
batchStopText: '批量停止',
batchDownText: '批量下载',
fileView: '历史抓包文件',
fileUPF: '标准版',
fileUPFTip: 'UPF内部抓包分析包',
textStart: "开始", textStart: "开始",
textStartBatch: "批量开始",
textStop: "停止", textStop: "停止",
textStopBatch: "批量停止",
textLog: "日志", textLog: "日志",
textLogMsg: "日志信息", textLogMsg: "日志信息",
textDown: "下载", textDown: "下载",
textDownBatch: "批量下载",
downTip: "确认要下载 {title} 抓包数据文件吗?", downTip: "确认要下载 {title} 抓包数据文件吗?",
downOk: "{title} 文件下载完成", downOk: "{title} 文件下载完成",
downErr: "{title} 文件下载异常", downErr: "{title} 文件下载异常",
@@ -1088,12 +1126,13 @@ export default {
stopNotRun: "{title} 任务未运行", stopNotRun: "{title} 任务未运行",
}, },
task: { task: {
neTypePlease: '请选择网元类型', traceId: '跟踪编号',
neType: '网元类型',
neID: '网元内部标识',
trackType: '跟踪类型', trackType: '跟踪类型',
trackTypePlease: '请选择跟踪类型', trackTypePlease: '请选择跟踪类型',
creater: '创建人', creater: '创建人',
textStop: "停止",
status: '状态',
time: '时间',
startTime: '开始时间', startTime: '开始时间',
endTime: '结束时间', endTime: '结束时间',
msisdn: 'MSISDN', msisdn: 'MSISDN',
@@ -1112,17 +1151,22 @@ export default {
interfacesPlease: '请输入信令接口', interfacesPlease: '请输入信令接口',
signalPort: '信令端口', signalPort: '信令端口',
signalPortPlease: '请输入信令端口', signalPortPlease: '请输入信令端口',
signalPortTip: '接口对应的端口', signalPortTip: '目标IP地址或源IP地址对应一方的端口',
rangePicker: '开始结束时间', rangePicker: '开始结束时间',
rangePickerPlease: '请选择任务时间开始结束时间', rangePickerPlease: '请选择任务时间开始结束时间',
comment: '任务说明', remark: '说明',
commentPlease: '可输入任务说明', remarkPlease: '可输入任务说明',
addTask: '添加任务', addTask: '添加任务',
editTask: '修改任务', editTask: '修改任务',
viewTask: '查看任务', viewTask: '查看任务',
errorTaskInfo: '获取任务信息失败', errorTaskInfo: '获取任务信息失败',
delTask: '成功删除任务 {num}', delTaskTip: '确认删除记录编号为 {id} 的数据项?',
delTaskTip: '确认删除记录编号为 {num} 的数据项?', stopTask: '成功停止任务 {id}',
stopTaskTip: '确认停止记录编号为 {id} 的任务?',
pcapView: "跟踪数据分析",
traceFile: "跟踪文件",
errMsg: "错误信息",
imsiORmsisdn: "imsi 或 msisdn 是空值,不能开始任务",
}, },
}, },
faultManage: { faultManage: {
@@ -1235,7 +1279,20 @@ export default {
downTip: "确认下载文件名为 【{fileName}】 文件?", downTip: "确认下载文件名为 【{fileName}】 文件?",
downTipErr: "文件获取失败", downTipErr: "文件获取失败",
dirCd: "进入目录", dirCd: "进入目录",
viewAs: '查看操作',
reload: "重载",
follow: '监视内容变化',
tailChar: '末尾字数',
tailLines: '末尾行数',
}, },
exportFile:{
fileName:'文件来源',
downTip: "确认下载文件名为 【{fileName}】 文件?",
downTipErr: "文件获取失败",
deleteTip: "确认删除文件名为 【{fileName}】 文件?",
deleteTipErr: "文件删除失败",
selectTip:"请选择文件名",
}
}, },
monitor: { monitor: {
session: { session: {
@@ -1947,6 +2004,7 @@ export default {
stepNeInfoStepNext: '确认要下一步进行网元配置参数?', stepNeInfoStepNext: '确认要下一步进行网元配置参数?',
stepPara5GTitle: "网元配置参数", stepPara5GTitle: "网元配置参数",
stepPara5GDesc: "设置网元全局参数信息", stepPara5GDesc: "设置网元全局参数信息",
savePara5GOk: '保存成功!',
stepPara5GStepPrev: '确认要放弃当前变更返回上一步吗?', stepPara5GStepPrev: '确认要放弃当前变更返回上一步吗?',
stepPara5GStepNext: '确认要下一步进行网元服务安装吗?', stepPara5GStepNext: '确认要下一步进行网元服务安装吗?',
stepInstallTitle: "网元服务安装", stepInstallTitle: "网元服务安装",
@@ -2047,6 +2105,30 @@ export default {
hostSelectMore: "加载更多 {num}", hostSelectMore: "加载更多 {num}",
hostSelectHeader: "主机列表", hostSelectHeader: "主机列表",
}, },
ps:{
realTimeHigh:"高",
realTimeLow:"低",
realTimeRegular:"常规",
realTimeStop:"已暂停",
realTime:"实时更新速度",
pid:"PID",
name:"应用名称",
username:"用户名",
runTime:"运行时间",
numThreads:"线程数",
cpuPercent:"CPU使用率",
diskRead:"磁盘读取",
diskWrite:"磁盘写入",
},
net:{
PID:"PID",
name:"名称",
localAddr:"localAddr",
remoteAddr:"remoteAddr",
status:"状态",
type:"类型",
port:"接口",
},
}, },
}, },
}; };

View File

@@ -59,7 +59,7 @@ watch(
// 路由地址含有内嵌地址标识又是隐藏菜单需要处理打开高亮菜单栏 // 路由地址含有内嵌地址标识又是隐藏菜单需要处理打开高亮菜单栏
if (v.path.includes(MENU_PATH_INLINE) && v.meta.hideInMenu) { if (v.path.includes(MENU_PATH_INLINE) && v.meta.hideInMenu) {
const idx = v.path.lastIndexOf(MENU_PATH_INLINE); const idx = v.path.lastIndexOf(MENU_PATH_INLINE);
layoutState.openKeys.splice(-1); layoutState.openKeys = layoutState.selectedKeys.slice(0, -1);
layoutState.selectedKeys[matched.length - 1] = v.path.slice(0, idx); layoutState.selectedKeys[matched.length - 1] = v.path.slice(0, idx);
} }
}, },
@@ -176,6 +176,7 @@ function fnCheckAppNameOverflow() {
if (!text) return; if (!text) return;
if (text.offsetWidth > container.offsetWidth) { if (text.offsetWidth > container.offsetWidth) {
text.classList.add('app-name_scrollable'); text.classList.add('app-name_scrollable');
text.setAttribute('data-content', text.innerText);
} else { } else {
text.classList.remove('app-name_scrollable'); text.classList.remove('app-name_scrollable');
} }
@@ -342,11 +343,17 @@ onUnmounted(() => {
:href="appStore.officialUrl" :href="appStore.officialUrl"
target="_blank" target="_blank"
size="small" size="small"
v-perms:has="['system:setting:official']"
v-if="appStore.officialUrl !== '#'" v-if="appStore.officialUrl !== '#'"
> >
{{ t('loayouts.basic.officialUrl') }} {{ t('loayouts.basic.officialUrl') }}
</a-button> </a-button>
<a-button type="link" size="small" @click="fnClickHelpDoc()"> <a-button
type="link"
size="small"
v-perms:has="['system:setting:doc']"
@click="fnClickHelpDoc()"
>
{{ t('loayouts.basic.helpDoc') }} {{ t('loayouts.basic.helpDoc') }}
</a-button> </a-button>
</a-space> </a-space>
@@ -382,20 +389,26 @@ onUnmounted(() => {
// text-overflow: ellipsis; // text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
width: 148px; width: 148px;
> .app-name_scrollable {
&_scrollable { padding-right: 12px;
// padding-left: 100%;
display: inline-block; display: inline-block;
animation: scrollable-animation linear 6s infinite both; animation: scrollable-animation linear 6s infinite both;
&::after {
content: attr(data-content);
position: absolute;
top: 0;
right: -100%;
transition: right 0.3s ease;
} }
@keyframes scrollable-animation { @keyframes scrollable-animation {
0% { 0% {
transform: translate3d(0, 0, 0); transform: translateX(0);
} }
100% { 100% {
transform: translate3d(-100%, 0, 0); transform: translateX(calc(-100% - 12px));
}
} }
} }
} }
@@ -422,7 +435,8 @@ onUnmounted(() => {
} }
& #serverTimeDom { & #serverTimeDom {
color: #00000085; color: inherit;
opacity: 0.85;
transition: all 0.3s; transition: all 0.3s;
} }
} }

View File

@@ -57,16 +57,6 @@ function fnClickAlarm() {
router.push({ name: 'ActiveAlarm_2088' }); router.push({ name: 'ActiveAlarm_2088' });
} }
/**系统使用手册跳转 */
function fnClickHelpDoc(language?: string) {
const routeData = router.resolve({ name: 'HelpDoc' });
let href = routeData.href;
if (language) {
href = `${routeData.href}?language=${language}`;
}
window.open(href, '_blank');
}
/**改变多语言 */ /**改变多语言 */
function fnChangeLocale(e: any) { function fnChangeLocale(e: any) {
changeLocale(e.key); changeLocale(e.key);
@@ -75,6 +65,8 @@ function fnChangeLocale(e: any) {
<template> <template>
<a-space :size="12" align="center"> <a-space :size="12" align="center">
<a-tooltip placement="bottom">
<template #title>{{ t('loayouts.rightContent.alarm') }}</template>
<a-button type="text" style="color: inherit" @click="fnClickAlarm"> <a-button type="text" style="color: inherit" @click="fnClickAlarm">
<template #icon> <template #icon>
<a-badge <a-badge
@@ -87,8 +79,10 @@ function fnChangeLocale(e: any) {
</a-badge> </a-badge>
</template> </template>
</a-button> </a-button>
</a-tooltip>
<!-- 锁屏操作 --> <!-- 锁屏操作 -->
<span v-perms:has="['system:setting:lock']">
<a-tooltip placement="bottom"> <a-tooltip placement="bottom">
<template #title>{{ t('loayouts.rightContent.lock') }}</template> <template #title>{{ t('loayouts.rightContent.lock') }}</template>
<a-button type="text" style="color: inherit" @click="fnClickLock()"> <a-button type="text" style="color: inherit" @click="fnClickLock()">
@@ -123,15 +117,7 @@ function fnChangeLocale(e: any) {
</a-space> </a-space>
</ProModal> </ProModal>
</a-tooltip> </a-tooltip>
</span>
<a-tooltip placement="bottom">
<template #title>{{ t('loayouts.rightContent.helpDoc') }}</template>
<a-button type="text" style="color: inherit" @click="fnClickHelpDoc()">
<template #icon>
<QuestionCircleOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip placement="bottom"> <a-tooltip placement="bottom">
<template #title>{{ t('loayouts.rightContent.fullscreen') }}</template> <template #title>{{ t('loayouts.rightContent.fullscreen') }}</template>

View File

@@ -13,10 +13,13 @@ import {
import { import {
APP_REQUEST_HEADER_CODE, APP_REQUEST_HEADER_CODE,
APP_REQUEST_HEADER_VERSION, APP_REQUEST_HEADER_VERSION,
APP_DATA_API_KEY,
} from '@/constants/app-constants'; } from '@/constants/app-constants';
import { import {
RESULT_CODE_ENCRYPT,
RESULT_CODE_ERROR, RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS, RESULT_CODE_SUCCESS,
RESULT_MSG_ENCRYPT,
RESULT_MSG_ERROR, RESULT_MSG_ERROR,
RESULT_MSG_NOT_TYPE, RESULT_MSG_NOT_TYPE,
RESULT_MSG_SERVER_ERROR, RESULT_MSG_SERVER_ERROR,
@@ -25,11 +28,12 @@ import {
RESULT_MSG_URL_NOTFOUND, RESULT_MSG_URL_NOTFOUND,
RESULT_MSG_URL_RESUBMIT, RESULT_MSG_URL_RESUBMIT,
} from '@/constants/result-constants'; } from '@/constants/result-constants';
import { decryptAES, encryptAES } from '@/utils/encrypt-utils';
/**响应结果类型 */ /**响应结果类型 */
export type ResultType = { export type ResultType = {
/**响应码 */ /**响应码 */
code: number | 1 | 0; code: number;
/**信息 */ /**信息 */
msg: string; msg: string;
/**数据 */ /**数据 */
@@ -76,6 +80,8 @@ type OptionsType = {
body?: BodyInit; body?: BodyInit;
/**防止数据重复提交 */ /**防止数据重复提交 */
repeatSubmit?: boolean; repeatSubmit?: boolean;
/**接口数据加密 */
crypto?: boolean;
/**携带授权Token请求头 */ /**携带授权Token请求头 */
whithToken?: boolean; whithToken?: boolean;
/**中断控制信号timeout不会生效 */ /**中断控制信号timeout不会生效 */
@@ -165,26 +171,44 @@ function beforeRequest(options: OptionsType): OptionsType | Promise<any> {
} }
} }
// get请求拼接地址栏参数 // 请求拼接地址栏参数
if (options.method === 'get' && options.params) { if (options.params) {
let paramStr = '';
const params = options.params; const params = options.params;
const queryParams: string[] = [];
for (const key in params) { for (const key in params) {
const value = params[key]; const value = params[key];
// 空字符或未定义的值不作为参数发送 // 空字符或未定义的值不作为参数发送
if (value === '' || value === undefined) continue; if (value === '' || value === undefined) continue;
paramStr += `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`; const str = `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
queryParams.push(str);
}
const paramStr = queryParams.join('&');
if (paramStr) {
const separator = options.url.includes('?') ? '&' : '?';
// 请求加密
if (options.crypto) {
debugger;
const data = encryptAES(JSON.stringify(paramStr), APP_DATA_API_KEY);
options.url += `${separator}data=${encodeURIComponent(data)}`;
} else {
options.url += `${separator}${paramStr}`;
} }
if (paramStr && paramStr.startsWith('&')) {
options.url = `${options.url}?${paramStr.substring(1)}`;
} }
} }
if (options.method === 'get') return options;
// 非get参数提交 // 非get参数提交
if (options.data instanceof FormData) { let body = options.data
options.body = options.data; if (body instanceof FormData) {
} else { options.body = body;
options.body = JSON.stringify(options.data); } else if (body) {
// 请求加密
if (options.crypto) {
const data = encryptAES(JSON.stringify(body), APP_DATA_API_KEY);
body = { data };
}
options.body = JSON.stringify(body);
} }
return options; return options;
} }
@@ -199,6 +223,28 @@ function interceptorResponse(res: ResultType): ResultType | Promise<any> {
window.location.reload(); window.location.reload();
} }
// 响应数据解密
if (res.code === RESULT_CODE_ENCRYPT) {
const str = decryptAES(res.data, APP_DATA_API_KEY);
let data = {};
try {
data = JSON.parse(str);
} catch (error) {
console.error(error);
}
if (Object.keys(data).length === 0) {
return Promise.resolve({
code: RESULT_CODE_ERROR,
msg: RESULT_MSG_ENCRYPT[language],
});
}
return Promise.resolve({
code: RESULT_CODE_SUCCESS,
msg: RESULT_MSG_SUCCESS[language],
data,
});
}
// 风格处理 // 风格处理
if (!Reflect.has(res, 'code')) { if (!Reflect.has(res, 'code')) {
return Promise.resolve({ return Promise.resolve({
@@ -266,7 +312,7 @@ export async function request(options: OptionsType): Promise<ResultType> {
case 'text': // 文本数据 case 'text': // 文本数据
const str = await res.text(); const str = await res.text();
return { return {
code: 1, code: RESULT_CODE_SUCCESS,
msg: str, msg: str,
}; };
case 'json': // json格式数据 case 'json': // json格式数据

124
src/plugins/wk-worker.ts Normal file
View File

@@ -0,0 +1,124 @@
/**连接参数类型 */
export type OptionsType = {
/**
* 线路工作服务地址
*
* self.onmessage = error => {} - 接收消息
*
* self.postMessage() -回复消息
*/
url: string;
/**message事件的回调函数 */
onmessage: (data: any) => void;
/**error事件的回调函数 */
onerror: Function;
/**close事件的回调函数 */
onclose?: (code: number) => void;
};
/**
* Web Worker 使用方法
*
* import { WK, OptionsType } from '@/plugins/wk-worker';
*
* const wk = new WK();
*
* 创建连接
* const options: OptionsType = { };
* wk.connect(options);
*
* 手动关闭
* wk.close();
*/
export class WK {
/**wk 实例 */
private wk: Worker | null = null;
/**wk 连接参数 */
private options: OptionsType | null = null;
/**
* 构造函数
* @param {object} params 构造函数参数
*/
constructor(options?: OptionsType) {
if (!window.Worker) {
// 检测浏览器支持
console.error('Sorry! Browser does not support Web Workers');
return;
}
options && this.connect(options);
}
/**
* 创建链接
* @param {object} options 连接参数
*/
public connect(options: OptionsType) {
this.options = options;
try {
const wk = new Worker(options.url);
// 用于收到来自其消息时的回调函数。
wk.onmessage = ev => {
// 解析文本消息
if (ev.type === 'message') {
try {
if (typeof options.onmessage === 'function') {
options.onmessage(ev.data);
}
} catch (error) {
console.error('worker message formatting error', error);
}
}
};
// 用于发生错误关闭后的回调函数。
wk.onmessageerror = ev => {
console.error('worker message error anomaly', ev);
if (typeof options.onclose === 'function') {
options.onerror(ev);
}
};
// 用于发生错误后的回调函数。
wk.onerror = ev => {
console.error('worker error anomaly', ev);
if (typeof options.onerror === 'function') {
options.onerror(ev);
}
};
this.wk = wk;
} catch (error) {
if (typeof options.onerror === 'function') {
options.onerror(error);
}
}
}
/**
* 发送消息
* @param data JSON数据
* @returns
*/
public send(data: Record<string, any>): boolean {
if (!this.wk) {
console.warn('worker unavailable');
return false;
}
this.wk.postMessage(data);
return true;
}
// 手动关闭会被立即终止
public close() {
if (!this.wk) {
console.warn('worker unavailable');
return;
}
this.wk.terminate();
// 用于关闭后的回调函数。
if (this.options && typeof this.options.onclose === 'function') {
this.options.onclose(1000);
}
}
}

View File

@@ -27,7 +27,7 @@ export type OptionsType = {
/** /**
* WebSocket 使用方法 * WebSocket 使用方法
* *
* import WS from '@/plugins/ws-websocket.ts'; * import { OptionsType, WS } from '@/plugins/ws-websocket';
* *
* const ws = new WS(); * const ws = new WS();
* *

View File

@@ -87,6 +87,12 @@ const constantRoutes: RouteRecordRaw[] = [
meta: { title: 'router.helpDoc' }, meta: { title: 'router.helpDoc' },
component: () => import('@/views/tool/help/index.vue'), component: () => import('@/views/tool/help/index.vue'),
}, },
{
path: '/trace-task-hlr',
name: 'TraceTaskHLR',
meta: { title: 'router.traceTaskHLR' },
component: () => import('@/views/traceManage/task-hlr/index.vue'),
},
{ {
path: '/quick-start', path: '/quick-start',
name: 'QuickStart', name: 'QuickStart',
@@ -154,6 +160,7 @@ const WHITE_LIST: string[] = [
'/help', '/help',
'/register', '/register',
'/quick-start', '/quick-start',
'/trace-task-hlr',
]; ];
/**全局路由-前置守卫 */ /**全局路由-前置守卫 */
@@ -170,6 +177,10 @@ router.beforeEach(async (to, from, next) => {
if (appStore.bootloader && to.path !== '/quick-start') { if (appStore.bootloader && to.path !== '/quick-start') {
next({ name: 'QuickStart' }); next({ name: 'QuickStart' });
} }
// 不重复引导
if (!appStore.bootloader && to.path === '/quick-start') {
next({ name: 'Index' });
}
const token = getToken(); const token = getToken();

View File

@@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { listAllNeInfo } from '@/api/ne/neInfo'; import { listAllNeInfo } from '@/api/ne/neInfo';
import { parseDataToOptions } from '@/utils/parse-tree-utils'; import { parseDataToOptions } from '@/utils/parse-tree-utils';
import { getNeTraceInterfaceAll } from '@/api/traceManage/task'; import { getNeTraceInterfaceAll } from '@/api/trace/task';
import { getNePerformanceList } from '@/api/perfManage/taskManage'; import { getNePerformanceList } from '@/api/perfManage/taskManage';
/**网元信息类型 */ /**网元信息类型 */

View File

@@ -0,0 +1,50 @@
import CryptoJS from 'crypto-js';
import { isValid, decode } from 'js-base64';
/**
* AES 加密并转为 base64
* @param plaintext 数据字符串
* @param aeskey 密钥
* @returns 加密字符串
*/
export function encryptAES(plaintext: string, aeskey: string): string {
const nowRoaund = new Date().getTime().toString(6);
const key = CryptoJS.enc.Utf8.parse(aeskey);
const iv = CryptoJS.enc.Utf8.parse(nowRoaund);
const encrypted = CryptoJS.AES.encrypt(`${nowRoaund}${plaintext}`, key, {
iv: iv,
blockSize: 16,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
format: CryptoJS.format.OpenSSL,
});
return encrypted.toString();
}
/**
* AES 解密
* @param ciphertext 加密字符串
* @param aeskey 密钥
* @returns 数据字符串
*/
export function decryptAES(ciphertext: string, aeskey: string): string {
const nowRoaund = new Date().getTime().toString(6);
const key = CryptoJS.enc.Utf8.parse(aeskey);
const iv = CryptoJS.enc.Utf8.parse(nowRoaund);
const decrypted = CryptoJS.AES.decrypt(ciphertext, key, {
iv: iv,
blockSize: 16,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
format: CryptoJS.format.OpenSSL,
});
const base64Str = decrypted.toString(CryptoJS.enc.Base64);
if (isValid(base64Str)) {
const str = decode(base64Str);
const idx = str.indexOf(':)', 10);
if (idx > 10) {
return str.substring(idx + 2);
}
}
return '';
}

View File

@@ -37,7 +37,10 @@ export function parseStrLineToHump(str: string): string {
*/ */
export function parseStrHumpToLine(str: string): string { export function parseStrHumpToLine(str: string): string {
if (!str) return str; if (!str) return str;
return str.replace(/([A-Z])/g, '_$1').toLowerCase(); return str
.replace(/([A-Z])/g, '_$1')
.toLowerCase()
.replace(/^_/, '');
} }
/** /**
@@ -92,6 +95,22 @@ export function parseObjLineToHump(obj: any): any {
return obj; return obj;
} }
/**
* 格式化文件大小
* @param bytes 字节数
* @param decimalPlaces 保留小数位,默认2位
* @returns 单位 xB
*/
export function parseSizeFromFile(bytes: number, decimalPlaces: number = 2) {
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
let i = 0;
while (bytes >= 1024 && i < units.length - 1) {
bytes /= 1024;
i++;
}
return `${bytes.toFixed(decimalPlaces || 1)} ${units[i]}`;
}
/** /**
* 转换磁盘容量 * 转换磁盘容量
* @param size 数值大小 * @param size 数值大小

View File

@@ -42,7 +42,7 @@ let queryParams = reactive({
/**网元类型 */ /**网元类型 */
neType: 'AMF', neType: 'AMF',
neId: '001', neId: '001',
eventType: 'auth-result', eventType: '',
imsi: '', imsi: '',
sortField: 'timestamp', sortField: 'timestamp',
sortOrder: 'desc', sortOrder: 'desc',
@@ -58,9 +58,9 @@ let queryParams = reactive({
/**查询参数重置 */ /**查询参数重置 */
function fnQueryReset() { function fnQueryReset() {
eventTypes.value = ['auth-result']; eventTypes.value = [];
queryParams = Object.assign(queryParams, { queryParams = Object.assign(queryParams, {
eventType: 'auth-result', eventType: '',
imsi: '', imsi: '',
startTime: '', startTime: '',
endTime: '', endTime: '',
@@ -74,7 +74,7 @@ function fnQueryReset() {
} }
/**记录类型 */ /**记录类型 */
const eventTypes = ref<string[]>(['auth-result']); const eventTypes = ref<string[]>([]);
/**查询记录类型变更 */ /**查询记录类型变更 */
function fnQueryEventTypeChange(value: any) { function fnQueryEventTypeChange(value: any) {
@@ -607,10 +607,7 @@ onBeforeUnmount(() => {
:value="record.eventJSON.authCode" :value="record.eventJSON.authCode"
/> />
</span> </span>
<span <span v-if="record.eventType === 'detach'">
v-if="record.eventType === 'detach'"
:title="record.eventJSON.detachTime"
>
<span>{{ t('views.dashboard.ue.resultOk') }}</span> <span>{{ t('views.dashboard.ue.resultOk') }}</span>
</span> </span>
<span v-if="record.eventType === 'cm-state'"> <span v-if="record.eventType === 'cm-state'">
@@ -706,7 +703,7 @@ onBeforeUnmount(() => {
/> />
</span> </span>
<span v-if="record.eventType === 'detach'"> <span v-if="record.eventType === 'detach'">
{{ t('views.dashboard.ue.resultOK') }} {{ t('views.dashboard.ue.resultOk') }}
</span> </span>
<span v-if="record.eventType === 'cm-state'"> <span v-if="record.eventType === 'cm-state'">
<DictTag <DictTag

View File

@@ -11,6 +11,7 @@ import {
RESULT_CODE_SUCCESS, RESULT_CODE_SUCCESS,
} from '@/constants/result-constants'; } from '@/constants/result-constants';
import useDictStore from '@/store/modules/dict'; import useDictStore from '@/store/modules/dict';
import useNeInfoStore from '@/store/modules/neinfo';
import { import {
delIMSDataCDR, delIMSDataCDR,
exportIMSDataCDR, exportIMSDataCDR,
@@ -36,6 +37,9 @@ let dict: {
cdrCallType: [], cdrCallType: [],
}); });
/**网元可选 */
let neOtions = ref<Record<string, any>[]>([]);
/**开始结束时间 */ /**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']); let queryRangePicker = ref<[string, string]>(['', '']);
@@ -44,7 +48,7 @@ let queryParams = reactive({
/**网元类型 */ /**网元类型 */
neType: 'IMS', neType: 'IMS',
neId: '001', neId: '001',
recordType: 'MOC', recordType: '',
callerParty: '', callerParty: '',
calledParty: '', calledParty: '',
sortField: 'timestamp', sortField: 'timestamp',
@@ -61,9 +65,9 @@ let queryParams = reactive({
/**查询参数重置 */ /**查询参数重置 */
function fnQueryReset() { function fnQueryReset() {
recordTypes.value = ['MOC']; recordTypes.value = [];
queryParams = Object.assign(queryParams, { queryParams = Object.assign(queryParams, {
recordType: 'MOC', recordType: '',
callerParty: '', callerParty: '',
calledParty: '', calledParty: '',
startTime: '', startTime: '',
@@ -78,7 +82,7 @@ function fnQueryReset() {
} }
/**记录类型 */ /**记录类型 */
const recordTypes = ref<string[]>(['MOC']); const recordTypes = ref<string[]>([]);
/**查询记录类型变更 */ /**查询记录类型变更 */
function fnQueryRecordTypeChange(value: any) { function fnQueryRecordTypeChange(value: any) {
@@ -135,17 +139,6 @@ let tableColumns: ColumnsType = [
align: 'left', align: 'left',
width: 100, width: 100,
}, },
{
title: t('views.dashboard.cdr.called'),
dataIndex: 'cdrJSON',
key: 'calledParty',
align: 'left',
width: 120,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.calledParty;
},
},
{ {
title: t('views.dashboard.cdr.caller'), title: t('views.dashboard.cdr.caller'),
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
@@ -157,6 +150,17 @@ let tableColumns: ColumnsType = [
return cdrJSON.callerParty; return cdrJSON.callerParty;
}, },
}, },
{
title: t('views.dashboard.cdr.called'),
dataIndex: 'cdrJSON',
key: 'calledParty',
align: 'left',
width: 120,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.calledParty;
},
},
{ {
title: t('views.dashboard.cdr.duration'), title: t('views.dashboard.cdr.duration'),
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
@@ -375,6 +379,7 @@ const realTimeData = ref<boolean>(false);
function fnRealTime() { function fnRealTime() {
realTimeData.value = !realTimeData.value; realTimeData.value = !realTimeData.value;
if (realTimeData.value) { if (realTimeData.value) {
tableState.seached = false;
// 建立链接 // 建立链接
const options: OptionsType = { const options: OptionsType = {
url: '/ws', url: '/ws',
@@ -383,7 +388,7 @@ function fnRealTime() {
* *
* IMS_CDR会话事件(GroupID:1005) * IMS_CDR会话事件(GroupID:1005)
*/ */
subGroupID: '1005', subGroupID: `1005_${queryParams.neId}`,
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: wsError, onerror: wsError,
@@ -391,6 +396,8 @@ function fnRealTime() {
ws.connect(options); ws.connect(options);
} else { } else {
ws.close(); ws.close();
tableState.seached = true;
fnGetList(1);
} }
} }
@@ -413,7 +420,7 @@ function wsMessage(res: Record<string, any>) {
return; return;
} }
// cdrEvent CDR会话事件 // cdrEvent CDR会话事件
if (data.groupId === '1005') { if (data.groupId === `1005_${queryParams.neId}`) {
const cdrEvent = data.data; const cdrEvent = data.data;
queue.add(async () => { queue.add(async () => {
modalState.maxId += 1; modalState.maxId += 1;
@@ -436,14 +443,39 @@ function wsMessage(res: Record<string, any>) {
onMounted(() => { onMounted(() => {
// 初始字典数据 // 初始字典数据
Promise.allSettled([getDict('cdr_sip_code'), getDict('cdr_call_type')]) Promise.allSettled([getDict('cdr_sip_code'), getDict('cdr_call_type')]).then(
.then(resArr => { resArr => {
if (resArr[0].status === 'fulfilled') { if (resArr[0].status === 'fulfilled') {
dict.cdrSipCode = resArr[0].value; dict.cdrSipCode = resArr[0].value;
} }
if (resArr[1].status === 'fulfilled') { if (resArr[1].status === 'fulfilled') {
dict.cdrCallType = resArr[1].value; dict.cdrCallType = resArr[1].value;
} }
}
);
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
let arr: Record<string, any>[] = [];
res.data.forEach(i => {
if (i.neType === 'IMS') {
arr.push({ value: i.neId, label: i.neName });
}
});
neOtions.value = arr;
if (arr.length > 0) {
queryParams.neId = arr[0].value;
}
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
}) })
.finally(() => { .finally(() => {
// 获取列表数据 // 获取列表数据
@@ -468,6 +500,53 @@ onBeforeUnmount(() => {
<!-- 表格搜索栏 --> <!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal"> <a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="IMS" name="neId ">
<a-select
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.called')"
name="calledParty"
>
<a-input
v-model:value="queryParams.calledParty"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.caller')"
name="callerParty "
>
<a-input
v-model:value="queryParams.callerParty"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="4" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
<a-col :lg="8" :md="12" :xs="24"> <a-col :lg="8" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.dashboard.cdr.recordType')" :label="t('views.dashboard.cdr.recordType')"
@@ -484,30 +563,6 @@ onBeforeUnmount(() => {
></a-select> ></a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="4" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.called')"
name="calledParty "
>
<a-input
v-model:value="queryParams.calledParty"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="4" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.caller')"
name="callerParty "
>
<a-input
v-model:value="queryParams.callerParty"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="8" :md="12" :xs="24"> <a-col :lg="8" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.dashboard.cdr.time')" :label="t('views.dashboard.cdr.time')"
@@ -524,20 +579,6 @@ onBeforeUnmount(() => {
></a-range-picker> ></a-range-picker>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="4" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row> </a-row>
</a-form> </a-form>
</a-card> </a-card>
@@ -595,6 +636,7 @@ onBeforeUnmount(() => {
:checked-children="t('common.switch.show')" :checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')" :un-checked-children="t('common.switch.hide')"
size="small" size="small"
:disabled="realTimeData"
/> />
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
@@ -639,7 +681,7 @@ onBeforeUnmount(() => {
:data-source="tableState.data" :data-source="tableState.data"
:size="tableState.size" :size="tableState.size"
:pagination="tablePagination" :pagination="tablePagination"
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }" :scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
:row-selection="{ :row-selection="{
type: 'checkbox', type: 'checkbox',
columnWidth: '48px', columnWidth: '48px',
@@ -663,7 +705,7 @@ onBeforeUnmount(() => {
/> />
</span> </span>
<span v-else> <span v-else>
{{ t('views.dashboard.overview.userActivity.resultOK') }} {{ t('views.dashboard.cdr.resultOk') }}
</span> </span>
</template> </template>
<template v-if="column.key === 'id'"> <template v-if="column.key === 'id'">
@@ -734,7 +776,7 @@ onBeforeUnmount(() => {
/> />
</span> </span>
<span v-else> <span v-else>
{{ t('views.dashboard.overview.userActivity.resultOK') }} {{ t('views.dashboard.cdr.resultOk') }}
</span> </span>
</div> </div>
</div> </div>

View File

@@ -11,6 +11,7 @@ import {
RESULT_CODE_SUCCESS, RESULT_CODE_SUCCESS,
} from '@/constants/result-constants'; } from '@/constants/result-constants';
import useDictStore from '@/store/modules/dict'; import useDictStore from '@/store/modules/dict';
import useNeInfoStore from '@/store/modules/neinfo';
import { listMMEDataUE, delMMEDataUE, exportMMEDataUE } from '@/api/neData/mme'; import { listMMEDataUE, delMMEDataUE, exportMMEDataUE } from '@/api/neData/mme';
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr } from '@/utils/date-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket'; import { OptionsType, WS } from '@/plugins/ws-websocket';
@@ -21,6 +22,9 @@ const { getDict } = useDictStore();
const ws = new WS(); const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true }); const queue = new PQueue({ concurrency: 1, autoStart: true });
/**网元可选 */
let neOtions = ref<Record<string, any>[]>([]);
/**字典数据 */ /**字典数据 */
let dict: { let dict: {
/**UE 事件认证代码类型 */ /**UE 事件认证代码类型 */
@@ -43,7 +47,7 @@ let queryParams = reactive({
/**网元类型 */ /**网元类型 */
neType: 'MME', neType: 'MME',
neId: '001', neId: '001',
eventType: 'auth-result', eventType: '',
imsi: '', imsi: '',
sortField: 'timestamp', sortField: 'timestamp',
sortOrder: 'desc', sortOrder: 'desc',
@@ -59,9 +63,9 @@ let queryParams = reactive({
/**查询参数重置 */ /**查询参数重置 */
function fnQueryReset() { function fnQueryReset() {
eventTypes.value = ['auth-result']; eventTypes.value = [];
queryParams = Object.assign(queryParams, { queryParams = Object.assign(queryParams, {
eventType: 'auth-result', eventType: '',
imsi: '', imsi: '',
startTime: '', startTime: '',
endTime: '', endTime: '',
@@ -75,7 +79,7 @@ function fnQueryReset() {
} }
/**记录类型 */ /**记录类型 */
const eventTypes = ref<string[]>(['auth-result']); const eventTypes = ref<string[]>([]);
/**查询记录类型变更 */ /**查询记录类型变更 */
function fnQueryEventTypeChange(value: any) { function fnQueryEventTypeChange(value: any) {
@@ -337,6 +341,7 @@ const realTimeData = ref<boolean>(false);
function fnRealTime() { function fnRealTime() {
realTimeData.value = !realTimeData.value; realTimeData.value = !realTimeData.value;
if (realTimeData.value) { if (realTimeData.value) {
tableState.seached = false;
// 建立链接 // 建立链接
const options: OptionsType = { const options: OptionsType = {
url: '/ws', url: '/ws',
@@ -345,7 +350,7 @@ function fnRealTime() {
* *
* MME_UE会话事件(GroupID:1011) * MME_UE会话事件(GroupID:1011)
*/ */
subGroupID: '1011', subGroupID: `1011_${queryParams.neId}`,
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: wsError, onerror: wsError,
@@ -353,6 +358,8 @@ function fnRealTime() {
ws.connect(options); ws.connect(options);
} else { } else {
ws.close(); ws.close();
tableState.seached = true;
fnGetList(1);
} }
} }
@@ -375,7 +382,7 @@ function wsMessage(res: Record<string, any>) {
return; return;
} }
// ueEvent MME_UE会话事件 // ueEvent MME_UE会话事件
if (data.groupId === '1011') { if (data.groupId === `1011_${queryParams.neId}`) {
const ueEvent = data.data; const ueEvent = data.data;
queue.add(async () => { queue.add(async () => {
modalState.maxId += 1; modalState.maxId += 1;
@@ -403,17 +410,46 @@ onMounted(() => {
getDict('ue_auth_code'), getDict('ue_auth_code'),
getDict('ue_event_type'), getDict('ue_event_type'),
getDict('ue_event_cm_state'), getDict('ue_event_cm_state'),
]) ]).then(resArr => {
.then(resArr => {
if (resArr[0].status === 'fulfilled') { if (resArr[0].status === 'fulfilled') {
dict.ueAauthCode = resArr[0].value; dict.ueAauthCode = resArr[0].value;
} }
if (resArr[1].status === 'fulfilled') { if (resArr[1].status === 'fulfilled') {
dict.ueEventType = resArr[1].value; dict.ueEventType = resArr[1].value.map(item => {
if (item.value === 'cm-state') {
item.label = item.label.replace('CM', 'ECM');
}
return item;
});
} }
if (resArr[2].status === 'fulfilled') { if (resArr[2].status === 'fulfilled') {
dict.ueEventCmState = resArr[2].value; dict.ueEventCmState = resArr[2].value;
} }
});
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
let arr: Record<string, any>[] = [];
res.data.forEach(i => {
if (i.neType === 'MME') {
arr.push({ value: i.neId, label: i.neName });
}
});
neOtions.value = arr;
if (arr.length > 0) {
queryParams.neId = arr[0].value;
}
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
}) })
.finally(() => { .finally(() => {
// 获取列表数据 // 获取列表数据
@@ -438,6 +474,15 @@ onBeforeUnmount(() => {
<!-- 表格搜索栏 --> <!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal"> <a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="MME" name="neId ">
<a-select
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.dashboard.ue.eventType')" :label="t('views.dashboard.ue.eventType')"
@@ -548,6 +593,7 @@ onBeforeUnmount(() => {
:checked-children="t('common.switch.show')" :checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')" :un-checked-children="t('common.switch.hide')"
size="small" size="small"
:disabled="realTimeData"
/> />
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
@@ -670,7 +716,7 @@ onBeforeUnmount(() => {
/> />
</span> </span>
<span v-if="record.eventType === 'detach'"> <span v-if="record.eventType === 'detach'">
{{ t('views.dashboard.ue.resultOK') }} {{ t('views.dashboard.ue.resultOk') }}
</span> </span>
<span v-if="record.eventType === 'cm-state'"> <span v-if="record.eventType === 'cm-state'">
<DictTag <DictTag

View File

@@ -237,7 +237,7 @@ function handleRanderChart(
function fnChangeData(data: any[], itemID: string) { function fnChangeData(data: any[], itemID: string) {
let info = data.find((item: any) => item.id === itemID); let info = data.find((item: any) => item.id === itemID);
if (!info.neState.online) return; if (!info || !info.neState.online) return;
// if (!info.neState.online) { // if (!info.neState.online) {
// info = data.find((item: any) => item.id === itemID); // info = data.find((item: any) => item.id === itemID);
// graphNodeClickID.value = itemID; // graphNodeClickID.value = itemID;
@@ -251,36 +251,27 @@ function fnChangeData(data: any[], itemID: string) {
let nfCpuUsage = 0; let nfCpuUsage = 0;
if (info.neState.cpu) { if (info.neState.cpu) {
nfCpuUsage = info.neState.cpu.nfCpuUsage; nfCpuUsage = info.neState.cpu.nfCpuUsage;
if (nfCpuUsage > 100) {
const nfCpu = +(info.neState.cpu.nfCpuUsage / 100); const nfCpu = +(info.neState.cpu.nfCpuUsage / 100);
if (nfCpu > 100) {
nfCpuUsage = 100;
} else {
nfCpuUsage = +nfCpu.toFixed(2); nfCpuUsage = +nfCpu.toFixed(2);
} if (nfCpuUsage > 100) {
nfCpuUsage = 100;
} }
sysCpuUsage = info.neState.cpu.sysCpuUsage; sysCpuUsage = info.neState.cpu.sysCpuUsage;
if (sysCpuUsage > 100) { let sysCpu = +(info.neState.cpu.sysCpuUsage / 100);
const sysCpu = +(info.neState.cpu.sysCpuUsage / 100);
if (sysCpu > 100) {
sysCpuUsage = 100;
} else {
sysCpuUsage = +sysCpu.toFixed(2); sysCpuUsage = +sysCpu.toFixed(2);
} if (sysCpuUsage > 100) {
sysCpuUsage = 100;
} }
} }
let sysMemUsage = 0; let sysMemUsage = 0;
if (info.neState.mem) { if (info.neState.mem) {
let men = info.neState.mem.sysMemUsage; const men = info.neState.mem.sysMemUsage;
if (men > 100) { sysMemUsage = +(men / 100).toFixed(2);
men = +(men / 100).toFixed(2); if (sysMemUsage > 100) {
sysMemUsage = 100;
} }
if (men > 100) {
men = 100;
}
sysMemUsage = men;
} }
let sysDiskUsage = 0; let sysDiskUsage = 0;

View File

@@ -203,17 +203,14 @@ function handleRanderChart() {
/**查询初始UPF数据 */ /**查询初始UPF数据 */
function fnGetInitData() { function fnGetInitData() {
// 查询10分钟前的 // 查询5分钟前的
const nowDate: Date = new Date(); const nowDate = new Date().getTime();
const tenMinutesAgo = new Date(nowDate.getTime() - 5 * 60 * 1000);
listKPIData({ listKPIData({
neType: 'UPF', neType: 'UPF',
neId: '001', neId: '001',
startTime: parseDateToStr(tenMinutesAgo), startTime: nowDate - 5 * 60 * 1000,
endTime: parseDateToStr(nowDate), endTime: nowDate,
// startTime: '2024-03-20 19:50:00',
// endTime: '2024-03-20 19:55:00',
interval: 5, // 5秒 interval: 5, // 5秒
sortField: 'timeGroup', sortField: 'timeGroup',
sortOrder: 'asc', sortOrder: 'asc',

View File

@@ -186,7 +186,14 @@ onMounted(() => {
<div class="card-ue-item"> <div class="card-ue-item">
<div> <div>
{{ t('views.dashboard.overview.userActivity.type') }}:&nbsp; {{ t('views.dashboard.overview.userActivity.type') }}:&nbsp;
<span> <span v-if="item.type === 'cm-state'">
{{
dict.ueEventType
.find(s => s.value === item.type)
?.label.replace('CM', 'ECM')
}}
</span>
<span v-else>
<DictTag :options="dict.ueEventType" :value="item.type" /> <DictTag :options="dict.ueEventType" :value="item.type" />
</span> </span>
</div> </div>

View File

@@ -159,14 +159,14 @@
/* 概览区域 衍生基站信息 */ /* 概览区域 衍生基站信息 */
.skim.base { .skim.base {
height: 20.6%; height: 12%;
} }
.skim.base .inner .data { .skim.base .inner .data {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
height: 45%; height: 75%;
} }
.skim.base .inner .data .item { .skim.base .inner .data .item {
display: flex; display: flex;
@@ -178,7 +178,7 @@
/* 用户行为 */ /* 用户行为 */
.userActivity { .userActivity {
/* min-height: 35.8rem; */ /* min-height: 35.8rem; */
height: 60%; height: 54.6%;
} }
.userActivity .inner .chart { .userActivity .inner .chart {
width: 100%; width: 100%;

View File

@@ -22,7 +22,7 @@ export const notNeNodes = [
/**图状态 */ /**图状态 */
export const graphState = reactive<Record<string, any>>({ export const graphState = reactive<Record<string, any>>({
/**当前图组名 */ /**当前图组名 */
group: '5GC System Architecture5', group: '5GC System Architecture',
/**图数据 */ /**图数据 */
data: { data: {
combos: [], combos: [],

View File

@@ -1,3 +1,4 @@
import { parseDateToStr } from '@/utils/date-utils';
import { parseSizeFromBits, parseSizeFromKbs } from '@/utils/parse-utils'; import { parseSizeFromBits, parseSizeFromKbs } from '@/utils/parse-utils';
import { ref } from 'vue'; import { ref } from 'vue';
@@ -22,7 +23,7 @@ export const upfFlowData = ref<FDType>({
/**UPF-流量数据 数据解析 */ /**UPF-流量数据 数据解析 */
export function upfFlowParse(data: Record<string, string>) { export function upfFlowParse(data: Record<string, string>) {
upfFlowData.value.lineXTime.push(data['timeGroup']); upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup']));
const upN3 = parseSizeFromKbs(+data['UPF.03'], 5); const upN3 = parseSizeFromKbs(+data['UPF.03'], 5);
upfFlowData.value.lineYUp.push(upN3[0]); upfFlowData.value.lineYUp.push(upN3[0]);
const downN6 = parseSizeFromKbs(+data['UPF.06'], 5); const downN6 = parseSizeFromKbs(+data['UPF.06'], 5);

View File

@@ -50,37 +50,37 @@ export default function useWS() {
// 普通信息 // 普通信息
switch (requestId) { switch (requestId) {
// AMF_UE会话事件 // AMF_UE会话事件
case '1010': case 'amf_1010_001':
if (Array.isArray(data.rows)) { if (Array.isArray(data.rows)) {
eventListParse('amf_ue', data); eventListParse('amf_ue', data);
} }
break; break;
// MME_UE会话事件 // MME_UE会话事件
case '1011': case 'mme_1011_001':
if (Array.isArray(data.rows)) { if (Array.isArray(data.rows)) {
eventListParse('mme_ue', data); eventListParse('mme_ue', data);
} }
break; break;
// IMS_CDR会话事件 // IMS_CDR会话事件
case '1005': case 'ims_1005_001':
if (Array.isArray(data.rows)) { if (Array.isArray(data.rows)) {
eventListParse('ims_cdr', data); eventListParse('ims_cdr', data);
} }
break; break;
//UPF-总流量数 //UPF-总流量数
case '1030_0': case 'upf_001_0':
const v0 = upfTFParse(data); const v0 = upfTFParse(data);
upfTotalFlow.value[0].up = v0.up; upfTotalFlow.value[0].up = v0.up;
upfTotalFlow.value[0].down = v0.down; upfTotalFlow.value[0].down = v0.down;
upfTotalFlow.value[0].requestFlag = false; upfTotalFlow.value[0].requestFlag = false;
break; break;
case '1030_7': case 'upf_001_7':
const v7 = upfTFParse(data); const v7 = upfTFParse(data);
upfTotalFlow.value[1].up = v7.up; upfTotalFlow.value[1].up = v7.up;
upfTotalFlow.value[1].down = v7.down; upfTotalFlow.value[1].down = v7.down;
upfTotalFlow.value[1].requestFlag = false; upfTotalFlow.value[1].requestFlag = false;
break; break;
case '1030_30': case 'upf_001_30':
const v30 = upfTFParse(data); const v30 = upfTFParse(data);
upfTotalFlow.value[2].up = v30.up; upfTotalFlow.value[2].up = v30.up;
upfTotalFlow.value[2].down = v30.down; upfTotalFlow.value[2].down = v30.down;
@@ -94,25 +94,25 @@ export default function useWS() {
} }
switch (data.groupId) { switch (data.groupId) {
// kpiEvent 指标UPF // kpiEvent 指标UPF
case '12': case '12_001':
if (data.data) { if (data.data) {
upfFlowParse(data.data); upfFlowParse(data.data);
} }
break; break;
// AMF_UE会话事件 // AMF_UE会话事件
case '1010': case '1010_001':
if (data.data) { if (data.data) {
queue.add(() => eventItemParseAndPush('amf_ue', data.data)); queue.add(() => eventItemParseAndPush('amf_ue', data.data));
} }
break; break;
// MME_UE会话事件 // MME_UE会话事件
case '1011': case '1011_001':
if (data.data) { if (data.data) {
queue.add(() => eventItemParseAndPush('mme_ue', data.data)); queue.add(() => eventItemParseAndPush('mme_ue', data.data));
} }
break; break;
// IMS_CDR会话事件 // IMS_CDR会话事件
case '1005': case '1005_001':
if (data.data) { if (data.data) {
queue.add(() => eventItemParseAndPush('ims_cdr', data.data)); queue.add(() => eventItemParseAndPush('ims_cdr', data.data));
} }
@@ -137,7 +137,7 @@ export default function useWS() {
upfTotalFlow.value[index].requestFlag = true; upfTotalFlow.value[index].requestFlag = true;
ws.send({ ws.send({
requestId: `1030_${day}`, requestId: `upf_001_${day}`,
type: 'upf_tf', type: 'upf_tf',
data: { data: {
neType: 'UPF', neType: 'UPF',
@@ -151,7 +151,7 @@ export default function useWS() {
function userActivitySend() { function userActivitySend() {
// AMF_UE会话事件 // AMF_UE会话事件
ws.send({ ws.send({
requestId: '1010', requestId: 'amf_1010_001',
type: 'amf_ue', type: 'amf_ue',
data: { data: {
neType: 'AMF', neType: 'AMF',
@@ -164,7 +164,7 @@ export default function useWS() {
}); });
// MME_UE会话事件 // MME_UE会话事件
ws.send({ ws.send({
requestId: '1011', requestId: 'mme_1011_001',
type: 'mme_ue', type: 'mme_ue',
data: { data: {
neType: 'MME', neType: 'MME',
@@ -177,7 +177,7 @@ export default function useWS() {
}); });
// IMS_CDR会话事件 // IMS_CDR会话事件
ws.send({ ws.send({
requestId: '1005', requestId: 'ims_1005_001',
type: 'ims_cdr', type: 'ims_cdr',
data: { data: {
neType: 'IMS', neType: 'IMS',
@@ -197,12 +197,12 @@ export default function useWS() {
params: { params: {
/**订阅通道组 /**订阅通道组
* *
* 指标UPF (GroupID:12) * 指标UPF (GroupID:12_neId)
* AMF_UE会话事件(GroupID:1010) * AMF_UE会话事件(GroupID:1010_neId)
* MME_UE会话事件(GroupID:1011) * MME_UE会话事件(GroupID:1011_neId)
* IMS_CDR会话事件(GroupID:1005) * IMS_CDR会话事件(GroupID:1005_neId)
*/ */
subGroupID: '12,1010,1011,1005', subGroupID: '12_001,1010_001,1011_001,1005_001',
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: wsError, onerror: wsError,

View File

@@ -270,7 +270,7 @@ onBeforeUnmount(() => {
<div class="skim panel base"> <div class="skim panel base">
<div class="inner"> <div class="inner">
<h3> <h3>
<GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp; <GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp; 5G
{{ t('views.dashboard.overview.skim.baseTitle') }} {{ t('views.dashboard.overview.skim.baseTitle') }}
</h3> </h3>
<div class="data"> <div class="data">
@@ -302,6 +302,14 @@ onBeforeUnmount(() => {
<span>{{ t('views.dashboard.overview.skim.gnbUeNum') }}</span> <span>{{ t('views.dashboard.overview.skim.gnbUeNum') }}</span>
</div> </div>
</div> </div>
</div>
</div>
<div class="skim panel base">
<div class="inner">
<h3>
<GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp; 4G
{{ t('views.dashboard.overview.skim.baseTitle') }}
</h3>
<div class="data"> <div class="data">
<div <div
class="item toRouter" class="item toRouter"

View File

@@ -5,6 +5,7 @@ import { Modal, message } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider'; import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'; import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/lib/table'; import { ColumnsType } from 'ant-design-vue/lib/table';
import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { import {
RESULT_CODE_ERROR, RESULT_CODE_ERROR,
@@ -23,6 +24,9 @@ const { t } = useI18n();
const ws = new WS(); const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true }); const queue = new PQueue({ concurrency: 1, autoStart: true });
/**网元可选 */
let neOtions = ref<Record<string, any>[]>([]);
/**开始结束时间 */ /**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']); let queryRangePicker = ref<[string, string]>(['', '']);
@@ -197,7 +201,7 @@ let tableColumns: ColumnsType = [
title: t('views.dashboard.cdr.smfInvocationTime'), // 调用时间 title: t('views.dashboard.cdr.smfInvocationTime'), // 调用时间
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
align: 'left', align: 'left',
width: 250, width: 200,
customRender(opt) { customRender(opt) {
const cdrJSON = opt.value; const cdrJSON = opt.value;
return cdrJSON.invocationTimestamp; return cdrJSON.invocationTimestamp;
@@ -391,6 +395,7 @@ const realTimeData = ref<boolean>(false);
function fnRealTime() { function fnRealTime() {
realTimeData.value = !realTimeData.value; realTimeData.value = !realTimeData.value;
if (realTimeData.value) { if (realTimeData.value) {
tableState.seached = false;
// 建立链接 // 建立链接
const options: OptionsType = { const options: OptionsType = {
url: '/ws', url: '/ws',
@@ -399,7 +404,7 @@ function fnRealTime() {
* *
* CDR会话事件-SMF (GroupID:1006) * CDR会话事件-SMF (GroupID:1006)
*/ */
subGroupID: '1006', subGroupID: `1006_${queryParams.neId}`,
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: wsError, onerror: wsError,
@@ -407,6 +412,8 @@ function fnRealTime() {
ws.connect(options); ws.connect(options);
} else { } else {
ws.close(); ws.close();
tableState.seached = true;
fnGetList(1);
} }
} }
@@ -429,7 +436,7 @@ function wsMessage(res: Record<string, any>) {
return; return;
} }
// cdrEvent CDR会话事件 // cdrEvent CDR会话事件
if (data.groupId === '1006') { if (data.groupId === `1006_${queryParams.neId}`) {
const cdrEvent = data.data; const cdrEvent = data.data;
queue.add(async () => { queue.add(async () => {
modalState.maxId += 1; modalState.maxId += 1;
@@ -451,9 +458,35 @@ function wsMessage(res: Record<string, any>) {
} }
onMounted(() => { onMounted(() => {
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
let arr: Record<string, any>[] = [];
res.data.forEach(i => {
if (i.neType === 'SMF') {
arr.push({ value: i.neId, label: i.neName });
}
});
neOtions.value = arr;
if (arr.length > 0) {
queryParams.neId = arr[0].value;
}
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
// 获取列表数据 // 获取列表数据
fnGetList(); fnGetList();
}); });
});
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (ws.state() !== -1) { if (ws.state() !== -1) {
@@ -472,10 +505,19 @@ onBeforeUnmount(() => {
<!-- 表格搜索栏 --> <!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal"> <a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="SMF" name="neId ">
<a-select
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.dashboard.cdr.smfSubscriptionIDData')" :label="t('views.dashboard.cdr.smfSubscriptionIDData')"
name="calledParty " name="subscriberID"
> >
<a-input <a-input
v-model:value="queryParams.subscriberID" v-model:value="queryParams.subscriberID"
@@ -572,6 +614,7 @@ onBeforeUnmount(() => {
:checked-children="t('common.switch.show')" :checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')" :un-checked-children="t('common.switch.hide')"
size="small" size="small"
:disabled="realTimeData"
/> />
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
@@ -616,7 +659,7 @@ onBeforeUnmount(() => {
:data-source="tableState.data" :data-source="tableState.data"
:size="tableState.size" :size="tableState.size"
:pagination="tablePagination" :pagination="tablePagination"
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }" :scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
:row-selection="{ :row-selection="{
type: 'checkbox', type: 'checkbox',
columnWidth: '48px', columnWidth: '48px',

View File

@@ -0,0 +1,760 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, ref, onBeforeUnmount } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/lib/table';
import useDictStore from '@/store/modules/dict';
import useI18n from '@/hooks/useI18n';
import {
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import useNeInfoStore from '@/store/modules/neinfo';
import {
delSMSCDataCDR,
exportSMSCDataCDR,
listSMSCDataCDR,
} from '@/api/neData/smsc';
import { parseDateToStr } from '@/utils/date-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import saveAs from 'file-saver';
import PQueue from 'p-queue';
const { getDict } = useDictStore();
const { t } = useI18n();
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
/**字典数据 */
let dict: {
/**CDR 响应原因代码类别类型 */
cdrCauseCode: DictType[];
} = reactive({
cdrCauseCode: [],
});
/**网元可选 */
let neOtions = ref<Record<string, any>[]>([]);
/**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']);
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: 'SMSC',
neId: '001',
recordType: '',
callerParty: '',
calledParty: '',
sortField: 'timestamp',
sortOrder: 'desc',
/**开始时间 */
startTime: '',
/**结束时间 */
endTime: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
recordTypes.value = [];
queryParams = Object.assign(queryParams, {
recordType: '',
callerParty: '',
calledParty: '',
startTime: '',
endTime: '',
pageNum: 1,
pageSize: 20,
});
queryRangePicker.value = ['', ''];
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**记录类型 */
const recordTypes = ref<string[]>([]);
/**查询记录类型变更 */
function fnQueryRecordTypeChange(value: any) {
if (Array.isArray(value)) {
queryParams.recordType = value.join(',');
}
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: object[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: true,
data: [],
selectedRowKeys: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('common.rowId'),
dataIndex: 'id',
align: 'left',
width: 100,
},
{
title: t('views.dashboard.cdr.recordType'),
dataIndex: 'cdrJSON',
align: 'left',
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.recordType;
},
},
{
title: t('views.dashboard.cdr.type'),
dataIndex: 'cdrJSON',
align: 'left',
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.serviceType;
},
},
{
title: t('views.dashboard.cdr.caller'),
dataIndex: 'cdrJSON',
key: 'callerParty',
align: 'left',
width: 120,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.callerParty;
},
},
{
title: t('views.dashboard.cdr.called'),
dataIndex: 'cdrJSON',
key: 'calledParty',
align: 'left',
width: 120,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.calledParty;
},
},
{
title: t('views.dashboard.cdr.result'),
dataIndex: 'cdrJSON',
key: 'cause',
align: 'left',
width: 200,
},
{
title: t('views.dashboard.cdr.time'),
dataIndex: 'cdrJSON',
align: 'left',
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
return parseDateToStr(+cdrJSON.updateTime * 1000);
},
},
{
title: t('common.operate'),
key: 'id',
align: 'left',
},
];
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**确定按钮 loading */
confirmLoading: boolean;
/**最大ID值 */
maxId: number;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
confirmLoading: false,
maxId: 0,
});
/**
* 记录删除
* @param id 编号
*/
function fnRecordDelete(id: string) {
if (!id || modalState.confirmLoading) return;
let msg = id;
if (id === '0') {
msg = `${id}... ${tableState.selectedRowKeys.length}`;
id = tableState.selectedRowKeys.join(',');
}
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.dashboard.cdr.delTip', { msg }),
onOk() {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
delSMSCDataCDR(id)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
fnGetList(1);
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
},
});
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
if (!queryRangePicker.value) {
queryRangePicker.value = ['', ''];
}
queryParams.startTime = queryRangePicker.value[0];
queryParams.endTime = queryRangePicker.value[1];
listSMSCDataCDR(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选
if (tableState.selectedRowKeys.length > 0) {
tableState.selectedRowKeys = [];
}
tablePagination.total = res.total;
// 遍历处理cdr字符串数据
tableState.data = res.rows.map(item => {
let cdrJSON = item.cdrJSON;
if (!cdrJSON) {
Reflect.set(item, 'cdrJSON', {});
}
try {
cdrJSON = JSON.parse(cdrJSON);
Reflect.set(item, 'cdrJSON', cdrJSON);
} catch (error) {
console.error(error);
Reflect.set(item, 'cdrJSON', {});
}
return item;
});
// 取最大值ID用作实时累加
if (res.total > 0) {
modalState.maxId = Number(res.rows[0].id);
}
}
tableState.loading = false;
});
}
/**列表导出 */
function fnExportList() {
if (modalState.confirmLoading) return;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.dashboard.cdr.exportTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
const querys = toRaw(queryParams);
querys.pageSize = 10000;
exportSMSCDataCDR(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
saveAs(res.data, `smsc_cdr_event_export_${Date.now()}.xlsx`);
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
},
});
}
/**实时数据开关 */
const realTimeData = ref<boolean>(false);
/**
* 实时数据
*/
function fnRealTime() {
realTimeData.value = !realTimeData.value;
if (realTimeData.value) {
tableState.seached = false;
// 建立链接
const options: OptionsType = {
url: '/ws',
params: {
/**订阅通道组
*
* SMSC_CDR会话事件(GroupID:1007_neId)
*/
subGroupID: `1007_${queryParams.neId}`,
},
onmessage: wsMessage,
onerror: wsError,
};
ws.connect(options);
} else {
ws.close();
tableState.seached = true;
fnGetList(1);
}
}
/**接收数据后回调 */
function wsError(ev: any) {
// 接收数据后回调
console.error(ev);
}
/**接收数据后回调 */
function wsMessage(res: Record<string, any>) {
const { code, requestId, data } = res;
if (code === RESULT_CODE_ERROR) {
console.warn(res.msg);
return;
}
// 订阅组信息
if (!data?.groupId) {
return;
}
// cdrEvent CDR会话事件
if (data.groupId === `1007_${queryParams.neId}`) {
const cdrEvent = data.data;
queue.add(async () => {
modalState.maxId += 1;
tableState.data.unshift({
id: modalState.maxId,
neType: cdrEvent.neType,
neName: cdrEvent.neName,
rmUID: cdrEvent.rmUID,
timestamp: cdrEvent.timestamp,
cdrJSON: cdrEvent.CDR,
});
tablePagination.total += 1;
if (tableState.data.length > 100) {
tableState.data.pop();
}
await new Promise(resolve => setTimeout(resolve, 800));
});
}
}
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('cdr_cause_code')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.cdrCauseCode = resArr[0].value;
}
});
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
let arr: Record<string, any>[] = [];
res.data.forEach(i => {
if (i.neType === 'SMSC') {
arr.push({ value: i.neId, label: i.neName });
}
});
neOtions.value = arr;
if (arr.length > 0) {
queryParams.neId = arr[0].value;
}
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
// 获取列表数据
fnGetList();
});
});
onBeforeUnmount(() => {
if (ws.state() !== -1) {
ws.close();
}
});
</script>
<template>
<PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="SMSC" name="neId ">
<a-select
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.called')"
name="calledParty"
>
<a-input
v-model:value="queryParams.calledParty"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.caller')"
name="callerParty "
>
<a-input
v-model:value="queryParams.callerParty"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="4" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.recordType')"
name="recordType"
>
<a-select
v-model:value="recordTypes"
mode="multiple"
:options="['MOSM', 'MTSM'].map(v => ({ value: v }))"
:placeholder="t('common.selectPlease')"
@change="fnQueryRecordTypeChange"
></a-select>
</a-form-item>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.time')"
name="queryRangePicker"
>
<a-range-picker
v-model:value="queryRangePicker"
allow-clear
bordered
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
value-format="x"
style="width: 100%"
></a-range-picker>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
<a-popconfirm
placement="bottomLeft"
:title="
!realTimeData
? t('views.dashboard.cdr.realTimeDataStart')
: t('views.dashboard.cdr.realTimeDataStop')
"
ok-text="Yes"
cancel-text="No"
@confirm="fnRealTime()"
>
<a-button type="primary" :danger="realTimeData">
<template #icon><FundOutlined /> </template>
{{
!realTimeData
? t('views.dashboard.cdr.realTimeDataStart')
: t('views.dashboard.cdr.realTimeDataStop')
}}
</a-button>
</a-popconfirm>
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading"
@click.prevent="fnRecordDelete('0')"
>
<template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }}
</a-button>
<a-button type="dashed" @click.prevent="fnExportList()">
<template #icon><ExportOutlined /></template>
{{ t('common.export') }}
</a-button>
</a-space>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
:disabled="realTimeData"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click" placement="bottomRight">
<a-button type="text">
<template #icon><ColumnHeightOutlined /></template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
<a-menu-item key="middle">
{{ t('common.size.middle') }}
</a-menu-item>
<a-menu-item key="small">
{{ t('common.size.small') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'cause'">
<span v-if="record.cdrJSON.result === 0">
{{ t('views.dashboard.cdr.resultFail') }},
<DictTag
:options="dict.cdrCauseCode"
:value="record.cdrJSON.cause"
value-default="0"
/>
</span>
<span v-else>
{{ t('views.dashboard.cdr.resultOk') }}
</span>
</template>
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.deleteText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordDelete(record.id)"
>
<template #icon>
<DeleteOutlined />
</template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
<template #expandedRowRender="{ record }">
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
<a-divider orientation="left">
{{ t('views.dashboard.cdr.cdrInfo') }}
</a-divider>
<div>
<span>{{ t('views.ne.common.neName') }}: </span>
<span>{{ record.neName }}</span>
</div>
<div>
<span>{{ t('views.ne.common.rmUid') }}: </span>
<span>{{ record.rmUID }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.time') }}: </span>
<span>{{ parseDateToStr(+record.timestamp * 1000) }}</span>
</div>
<a-divider orientation="left">
{{ t('views.dashboard.cdr.rowInfo') }}
</a-divider>
<div>
<span>{{ t('views.dashboard.cdr.type') }}: </span>
<span>{{ record.cdrJSON.serviceType }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.caller') }}: </span>
<span>{{ record.cdrJSON.callerParty }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.called') }}: </span>
<span>{{ record.cdrJSON.calledParty }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.result') }}: </span>
<span v-if="record.cdrJSON.result === 0">
{{ t('views.dashboard.cdr.resultFail') }},
<DictTag
:options="dict.cdrCauseCode"
:value="record.cdrJSON.cause"
value-default="0"
/>
</span>
<span v-else>
{{ t('views.dashboard.cdr.resultOk') }}
</span>
</div>
</div>
</template>
</a-table>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -142,30 +142,6 @@ let alarmTableState: TabeStateType = reactive({
/**表格字段列 */ /**表格字段列 */
let tableColumns: ColumnsType = [ let tableColumns: ColumnsType = [
{
title: t('views.faultManage.activeAlarm.alarmId'),
dataIndex: 'alarmId',
align: 'center',
width: 5,
},
{
title: t('views.faultManage.activeAlarm.neId'),
dataIndex: 'neId',
align: 'center',
width: 5,
},
{
title: t('views.faultManage.activeAlarm.neName'),
dataIndex: 'neName',
align: 'center',
width: 5,
},
{
title: t('views.faultManage.activeAlarm.neType'),
dataIndex: 'neType',
align: 'center',
width: 5,
},
{ {
title: t('views.faultManage.activeAlarm.origLevel'), title: t('views.faultManage.activeAlarm.origLevel'),
align: 'center', align: 'center',
@@ -173,18 +149,18 @@ let tableColumns: ColumnsType = [
dataIndex: 'origSeverity', dataIndex: 'origSeverity',
width: 5, width: 5,
}, },
{
title: t('views.faultManage.activeAlarm.alarmCode'),
dataIndex: 'alarmCode',
align: 'center',
width: 5,
},
{ {
title: t('views.faultManage.activeAlarm.alarmTitle'), title: t('views.faultManage.activeAlarm.alarmTitle'),
dataIndex: 'alarmTitle', dataIndex: 'alarmTitle',
align: 'left', align: 'left',
width: 5, width: 5,
}, },
{
title: t('views.faultManage.activeAlarm.neType'),
dataIndex: 'neType',
align: 'center',
width: 5,
},
{ {
title: t('views.faultManage.activeAlarm.eventTime'), title: t('views.faultManage.activeAlarm.eventTime'),
dataIndex: 'eventTime', dataIndex: 'eventTime',
@@ -192,6 +168,12 @@ let tableColumns: ColumnsType = [
sorter: (a: any, b: any) => 1, sorter: (a: any, b: any) => 1,
width: 5, width: 5,
}, },
{
title: t('views.faultManage.activeAlarm.alarmCode'),
dataIndex: 'alarmCode',
align: 'center',
width: 5,
},
{ {
title: t('views.faultManage.activeAlarm.alarmType'), title: t('views.faultManage.activeAlarm.alarmType'),
dataIndex: 'alarmType', dataIndex: 'alarmType',
@@ -199,12 +181,31 @@ let tableColumns: ColumnsType = [
align: 'left', align: 'left',
width: 5, width: 5,
}, },
{
title: t('views.faultManage.activeAlarm.neName'),
dataIndex: 'neName',
align: 'center',
width: 5,
},
{
title: t('views.faultManage.activeAlarm.neId'),
dataIndex: 'neId',
align: 'center',
width: 5,
},
{ {
title: t('views.faultManage.activeAlarm.pvFlag'), title: t('views.faultManage.activeAlarm.pvFlag'),
dataIndex: 'pvFlag', dataIndex: 'pvFlag',
align: 'center', align: 'center',
width: 5, width: 5,
}, },
{
title: t('views.faultManage.activeAlarm.alarmId'),
dataIndex: 'alarmId',
align: 'center',
width: 5,
},
{ {
title: t('views.faultManage.activeAlarm.ackState'), title: t('views.faultManage.activeAlarm.ackState'),
dataIndex: 'ackState', dataIndex: 'ackState',
@@ -610,6 +611,17 @@ function fnShowSet() {
}); });
} }
// key替换中文title
function mapKeysWithReduce(data: any, titleMapping: any) {
return data.map((item:any) => {
return Object.keys(item).reduce((newItem:any, key:any) => {
const title = titleMapping[key] || key;
newItem[title] = item[key];
return newItem;
});
});
}
/** /**
* 导出全部 * 导出全部
*/ */
@@ -621,39 +633,56 @@ function fnExportAll() {
const key = 'exportAlarm'; const key = 'exportAlarm';
message.loading({ content: t('common.loading'), key }); message.loading({ content: t('common.loading'), key });
let sortArr: any = []; let sortArr: any = [];
let writeSheetOpt: any = [];
let mappArr: any = {};
tableColumnsDnd.value.forEach((item: any) => { tableColumnsDnd.value.forEach((item: any) => {
if (item.dataIndex) sortArr.push(item.dataIndex); if (item.dataIndex) {
sortArr.push(item.dataIndex);
writeSheetOpt.push(item.title);
mappArr[item.dataIndex] = item.title;
}
}); });
//提供给writeSheet排序参数
// 排序字段
const sortData = { const sortData = {
header: sortArr, header: writeSheetOpt,
}; };
exportAll(queryParams).then(res => { exportAll(queryParams).then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
res.data = res.data.map((objA: any) => { res.data = res.data.map((objA: any) => {
let filteredObj: any = {}; let filteredObj: any = {};
//sort Data
sortArr.forEach((key: any) => { sortArr.forEach((key: any) => {
if (objA.hasOwnProperty(key)) { if (objA.hasOwnProperty(key)) {
filteredObj[key] = objA[key]; filteredObj[key] = objA[key];
} }
}); });
dict.activeAckState.map((item: any) => { const mapProps = (dict: any, key: string) => {
if (item.value === `${filteredObj.ackState}`) const item = dict.find(
filteredObj.ackState = item.label; (item: any) => item.value === `${filteredObj[key]}`
}); );
if (item) {
filteredObj[key] = item.label;
}
};
mapProps(dict.activeAckState, 'ackState');
mapProps(dict.activeClearType, 'clearType');
mapProps(dict.activeAlarmSeverity, 'origSeverity');
mapProps(dict.activeAlarmType, 'alarmType');
return filteredObj; return filteredObj;
}); });
res.data = mapKeysWithReduce(res.data, mappArr);
writeSheet(res.data, 'alarm', sortData).then(fileBlob => {
saveAs(fileBlob, `alarm_${Date.now()}.xlsx`);
message.success({ message.success({
content: t('common.msgSuccess', { msg: t('common.export') }), content: t('common.msgSuccess', { msg: t('common.export') }),
key, key,
duration: 3, duration: 3,
}); });
writeSheet(res.data, 'alarm', sortData).then(fileBlob => });
saveAs(fileBlob, `alarm_${Date.now()}.xlsx`)
);
} else { } else {
message.error({ message.error({
content: `${res.msg}`, content: `${res.msg}`,

View File

@@ -308,6 +308,17 @@ const onSelectChange = (
state.selectedRow = record; state.selectedRow = record;
}; };
// key替换中文title
function mapKeysWithReduce(data: any, titleMapping: any) {
return data.map((item:any) => {
return Object.keys(item).reduce((newItem:any, key:any) => {
const title = titleMapping[key] || key;
newItem[title] = item[key];
return newItem;
});
});
}
/** /**
* 导出全部 * 导出全部
*/ */
@@ -319,13 +330,18 @@ function fnExportAll() {
const key = 'exportAlarm'; const key = 'exportAlarm';
message.loading({ content: t('common.loading'), key }); message.loading({ content: t('common.loading'), key });
let sortArr: any = []; let sortArr: any = [];
let writeSheetOpt: any = [];
let mappArr: any = {};
tableColumnsDnd.value.forEach((item: any) => { tableColumnsDnd.value.forEach((item: any) => {
if (item.dataIndex) sortArr.push(item.dataIndex); if (item.dataIndex) {
sortArr.push(item.dataIndex);
writeSheetOpt.push(item.title);
mappArr[item.dataIndex] = item.title;
}
}); });
// 排序字段 // 排序字段
const sortData = { const sortData = {
header: sortArr, header: writeSheetOpt,
}; };
exportAll(queryParams).then(res => { exportAll(queryParams).then(res => {
@@ -337,21 +353,20 @@ function fnExportAll() {
filteredObj[key] = objA[key]; filteredObj[key] = objA[key];
} }
}); });
dict.activeAckState.map((item: any) => {
if (item.value === `${filteredObj.ackState}`)
filteredObj.ackState = item.label;
});
return filteredObj; return filteredObj;
}); });
res.data = mapKeysWithReduce(res.data, mappArr);
writeSheet(res.data, 'alarm', sortData).then(fileBlob => {
saveAs(fileBlob, `evnet_${Date.now()}.xlsx`);
message.success({ message.success({
content: t('common.msgSuccess', { msg: t('common.export') }), content: t('common.msgSuccess', { msg: t('common.export') }),
key, key,
duration: 3, duration: 3,
}); });
writeSheet(res.data, 'alarm', sortData).then(fileBlob => });
saveAs(fileBlob, `evnet_${Date.now()}.xlsx`)
);
} else { } else {
message.error({ message.error({
content: `${res.msg}`, content: `${res.msg}`,
@@ -625,11 +640,6 @@ onMounted(() => {
:loading="tableState.loading" :loading="tableState.loading"
:data-source="tableState.data" :data-source="tableState.data"
:size="tableState.size" :size="tableState.size"
:row-selection="{
columnWidth: 2,
selectedRowKeys: state.selectedRowKeys,
onChange: onSelectChange,
}"
:pagination="tablePagination" :pagination="tablePagination"
:scroll="{ x: 2500, y: 400 }" :scroll="{ x: 2500, y: 400 }"
> >

View File

@@ -426,6 +426,17 @@ function fnCancelConfirm() {
}); });
} }
// key替换中文title
function mapKeysWithReduce(data: any, titleMapping: any) {
return data.map((item:any) => {
return Object.keys(item).reduce((newItem:any, key:any) => {
const title = titleMapping[key] || key;
newItem[title] = item[key];
return newItem;
});
});
}
/** /**
* 导出全部 * 导出全部
*/ */
@@ -437,12 +448,18 @@ function fnExportAll() {
const key = 'exportAlarmHis'; const key = 'exportAlarmHis';
message.loading({ content: t('common.loading'), key }); message.loading({ content: t('common.loading'), key });
let sortArr: any = []; let sortArr: any = [];
let writeSheetOpt: any = [];
let mappArr: any = {};
tableColumnsDnd.value.forEach((item: any) => { tableColumnsDnd.value.forEach((item: any) => {
if (item.dataIndex) sortArr.push(item.dataIndex); if (item.dataIndex) {
sortArr.push(item.dataIndex);
writeSheetOpt.push(item.title);
mappArr[item.dataIndex] = item.title;
}
}); });
// 排序字段 //提供给writeSheet排序参数
const sortData = { const sortData = {
header: sortArr, header: writeSheetOpt,
}; };
exportAll(queryParams).then(res => { exportAll(queryParams).then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
@@ -470,14 +487,17 @@ function fnExportAll() {
return filteredObj; return filteredObj;
}); });
res.data = mapKeysWithReduce(res.data, mappArr);
writeSheet(res.data, 'alarm', sortData).then(fileBlob => {
saveAs(fileBlob, `history-alarm_${Date.now()}.xlsx`);
message.success({ message.success({
content: t('common.msgSuccess', { msg: t('common.export') }), content: t('common.msgSuccess', { msg: t('common.export') }),
key, key,
duration: 3, duration: 3,
}); });
writeSheet(res.data, 'alarm', sortData).then(fileBlob => });
saveAs(fileBlob, `history-alarm_${Date.now()}.xlsx`)
);
} else { } else {
message.error({ message.error({
content: `${res.msg}`, content: `${res.msg}`,

View File

@@ -153,11 +153,10 @@ type nfStateType = {
/**网元详细信息 */ /**网元详细信息 */
let pronInfo: nfStateType = reactive({ let pronInfo: nfStateType = reactive({
hostName: '5gc', hostName: '5gc',
osInfo: osInfo: 'Linux 5gc 4.15.0-112-generic 2020 x86_64 GNU/Linux',
'Linux 5gc 4.15.0-112-generic #113-Ubuntu SMP Thu Jul 9 23:41:39 UTC 2020 x86_64 GNU/Linux', dbInfo: 'db v9.9.9',
dbInfo: 'adb v1.0.1',
ipAddress: '-', ipAddress: '-',
port: 3030, port: 33030,
version: '-', version: '-',
cpuUse: '-', cpuUse: '-',
memoryUse: '-', memoryUse: '-',

View File

@@ -0,0 +1,362 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { ColumnsType } from 'ant-design-vue/lib/table';
import { Modal, message } from 'ant-design-vue/lib';
import { parseDateToStr } from '@/utils/date-utils';
import {
getBakFile,
getBakFileList,
downFile,
delFile,
} from '@/api/logManage/exportFile';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useI18n from '@/hooks/useI18n';
import saveAs from 'file-saver';
const { t } = useI18n();
/**网元参数 */
let logSelect = ref<string[]>([]);
/**文件列表 */
let fileList = ref<any>([]);
/**查询参数 */
let queryParams = reactive({
/**读取路径 */
path: '',
/**表名 */
tableName: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**记录数据 */
data: object[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'small',
data: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('views.logManage.neFile.fileMode'),
dataIndex: 'fileMode',
align: 'center',
width: 150,
},
{
title: t('views.logManage.neFile.owner'),
dataIndex: 'owner',
align: 'left',
width: 100,
},
{
title: t('views.logManage.neFile.group'),
dataIndex: 'group',
align: 'left',
width: 100,
},
{
title: t('views.logManage.neFile.size'),
dataIndex: 'size',
align: 'left',
width: 100,
},
{
title: t('views.logManage.neFile.modifiedTime'),
dataIndex: 'modifiedTime',
align: 'left',
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value * 1000);
},
width: 150,
},
{
title: t('views.logManage.neFile.fileName'),
dataIndex: 'fileName',
align: 'left',
},
{
title: t('common.operate'),
key: 'fileName',
align: 'center',
width: 100,
},
];
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**下载触发等待 */
let downLoading = ref<boolean>(false);
/**删除触发等待 */
let delLoading = ref<boolean>(false);
/**信息文件下载 */
function fnDownloadFile(row: Record<string, any>) {
if (downLoading.value) return;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.logManage.exportFile.downTip', {
fileName: row.fileName,
}),
onOk() {
downLoading.value = true;
const hide = message.loading(t('common.loading'), 0);
downFile({
path: queryParams.path,
fileName: row.fileName,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.msgSuccess', {
msg: t('common.downloadText'),
}),
duration: 2,
});
saveAs(res.data, `${row.fileName}`);
} else {
message.error({
content: t('views.logManage.exportFile.downTipErr'),
duration: 2,
});
}
})
.finally(() => {
hide();
downLoading.value = false;
});
},
});
}
function fnRecordDelete(row: Record<string, any>) {
if (delLoading.value) return;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.logManage.exportFile.deleteTip', {
fileName: row.fileName,
}),
onOk() {
const key = 'delFile';
delLoading.value = true;
message.loading({ content: t('common.loading'), key });
delFile({
fileName: row.fileName,
path: queryParams.path,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.system.user.delSuss'),
key,
duration: 2,
});
fnGetList();
} else {
message.error({
content: `${res.msg}`,
key: key,
duration: 2,
});
}
})
.finally(() => {
delLoading.value = false;
});
},
});
}
/**网元类型选择对应修改 */
function fnNeChange(keys: any, opt: any) {
queryParams.tableName = keys;
queryParams.path = opt.path;
fnGetList(1);
}
/**查询备份信息列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (queryParams.tableName === '') {
message.warning({
content: t('views.logManage.exportFile.selectTip'),
duration: 2,
});
return;
}
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
getBakFileList(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
tablePagination.total = res.total;
tableState.data = res.data;
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
} else {
message.error(res.msg, 3);
tablePagination.total = 0;
tableState.data = [];
}
tableState.loading = false;
});
}
onMounted(() => {
getBakFile().then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
res.data.forEach((item: any) => {
fileList.value.push({
value: item.tableName,
label: item.tableDisplay,
path: item.filePath,
});
});
}
});
// .finally(() => {
// fnGetList();
// });
});
</script>
<template>
<PageContainer>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="8" :md="12" :xs="24">
<a-form-item
:label="t('views.logManage.exportFile.fileName')"
name="fileName"
style="margin-bottom: 0"
>
<a-select
v-model:value="logSelect"
:options="fileList"
@change="fnNeChange"
:allow-clear="false"
/>
</a-form-item>
</a-col>
<a-col :lg="16" :md="18" :xs="24" v-if="queryParams.path">
<a-form-item
:label="t('views.logManage.neFile.nePath')"
name="configName"
style="margin-bottom: 0"
>
<a-breadcrumb>
<a-breadcrumb-item>
{{ queryParams.path }}
</a-breadcrumb-item>
</a-breadcrumb>
</a-form-item>
</a-col>
</a-row>
</a-form>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="fileName"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: 800 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'fileName'">
<a-space :size="8" align="center">
<a-button
type="link"
:loading="downLoading"
@click.prevent="fnDownloadFile(record)"
v-if="record.fileType === 'file'"
>
<template #icon><DownloadOutlined /></template>
</a-button>
<a-button
type="link"
:loading="delLoading"
@click.prevent="fnRecordDelete(record)"
v-if="record.fileType === 'file'"
>
<template #icon><DeleteOutlined /></template>
</a-button>
</a-space>
</template>
</template>
</a-table>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,174 @@
<script setup lang="ts">
import { reactive, onMounted, watch, ref, nextTick } from 'vue';
import { ProModal } from 'antdv-pro-modal';
import TerminalSSHView from '@/components/TerminalSSHView/index.vue';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({
visible: {
type: Boolean,
default: false,
required: true,
},
/**文件地址 */
filePath: {
type: String,
default: '',
required: true,
},
/**网元类型 */
neType: {
type: String,
default: '',
required: true,
},
/**网元ID */
neId: {
type: String,
default: '',
required: true,
},
});
/**对话框对象信息状态类型 */
type StateType = {
/**框是否显示 */
visible: boolean;
/**标题 */
title: string;
/**查看命令 */
form: Record<string, any>;
};
/**对话框对象信息状态 */
let state: StateType = reactive({
visible: false,
title: '文件查看',
form: {
follow: true,
showType: 'lines', // lines/char
lines: 10,
char: 0,
},
});
function onClose() {
state.visible = false;
emit('cancel');
emit('update:visible', false);
}
/**监听是否显示,初始数据 */
watch(
() => props.visible,
val => {
if (val) {
if (props.neType && props.neId) {
const filePath = props.filePath;
const fileName = filePath.substring(filePath.lastIndexOf('/') + 1);
state.title = fileName;
state.visible = true;
}
}
}
);
/**终端实例 */
const viewTerminal = ref();
/**终端初始连接 */
function fnInit() {
setTimeout(fnReload, 1_500);
}
/**重载查看方式 */
function fnReload() {
if (!viewTerminal.value) return;
viewTerminal.value.ctrlC();
if (state.form.showType !== 'lines') {
state.form.lines = 10;
} else {
state.form.char = 0;
}
viewTerminal.value.clear();
viewTerminal.value.send('tail', {
filePath: props.filePath,
lines: state.form.lines,
char: state.form.char,
follow: state.form.follow,
});
}
</script>
<template>
<ProModal
:drag="true"
:fullscreen="true"
:borderDraw="true"
:min-width="800"
:min-height="500"
:center-y="true"
:destroyOnClose="true"
:keyboard="false"
:mask-closable="false"
:visible="state.visible"
:title="state.title"
:body-style="{ padding: '12px', overflow: 'hidden' }"
:footer="null"
@cancel="onClose"
>
<TerminalSSHView
ref="viewTerminal"
:id="`V${Date.now()}`"
style="height: calc(100% - 36px)"
:ne-type="neType"
:ne-id="neId"
@connect="fnInit()"
></TerminalSSHView>
<!-- 命令控制属性 -->
<a-form name="form" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.logManage.neFile.viewAs')">
<a-input-group compact>
<a-select v-model:value="state.form.showType" style="width: 50%">
<a-select-option value="lines">
{{ t('views.logManage.neFile.tailLines') }}
</a-select-option>
<a-select-option value="char">
{{ t('views.logManage.neFile.tailChar') }}
</a-select-option>
</a-select>
<a-input-number
style="width: 25%"
v-model:value="state.form[state.form.showType]"
:min="0"
:max="1000"
:placeholder="t('common.inputPlease')"
></a-input-number>
<a-button type="primary" style="width: 25%" @click="fnReload()">
{{ t('views.logManage.neFile.reload') }}
</a-button>
</a-input-group>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.logManage.neFile.follow')"
name="follow"
>
<a-switch v-model:checked="state.form.follow" />
</a-form-item>
</a-col>
</a-row>
</a-form>
</ProModal>
</template>
<style lang="less" scoped>
.ant-form :deep(.ant-form-item) {
margin-bottom: 0;
}
</style>

View File

@@ -3,12 +3,13 @@ import { reactive, ref, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import { SizeType } from 'ant-design-vue/lib/config-provider'; import { SizeType } from 'ant-design-vue/lib/config-provider';
import { ColumnsType } from 'ant-design-vue/lib/table'; import { ColumnsType } from 'ant-design-vue/lib/table';
import { Modal, message } from 'ant-design-vue/lib';
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr } from '@/utils/date-utils';
import { getNeFile, listNeFiles } from '@/api/tool/neFile'; import { getNeFile, listNeFiles } from '@/api/tool/neFile';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useNeInfoStore from '@/store/modules/neinfo'; import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { Modal, message } from 'ant-design-vue/lib'; import ViewDrawer from './components/ViewDrawer.vue';
import saveAs from 'file-saver'; import saveAs from 'file-saver';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
const neInfoStore = useNeInfoStore(); const neInfoStore = useNeInfoStore();
@@ -19,11 +20,7 @@ const route = useRoute();
const routeParams = route.query as Record<string, any>; const routeParams = route.query as Record<string, any>;
/**网元参数 */ /**网元参数 */
let neType = ref<string[]>([]); let neTypeSelect = ref<string[]>([]);
/**下载触发等待 */
let loading = ref(false);
/**访问路径 */
let nePathArr = ref<string[]>([]);
/**查询参数 */ /**查询参数 */
let queryParams = reactive({ let queryParams = reactive({
@@ -134,20 +131,24 @@ let tablePagination = reactive({
}, },
}); });
/**下载触发等待 */
let downLoading = ref<boolean>(false);
/**信息文件下载 */ /**信息文件下载 */
function fnDownloadFile(row: Record<string, any>) { function fnDownloadFile(row: Record<string, any>) {
if (loading.value) return; if (downLoading.value) return;
Modal.confirm({ Modal.confirm({
title: t('common.tipTitle'), title: t('common.tipTitle'),
content: t('views.logManage.neFile.downTip', { fileName: row.fileName }), content: t('views.logManage.neFile.downTip', { fileName: row.fileName }),
onOk() { onOk() {
loading.value = true; downLoading.value = true;
const hide = message.loading(t('common.loading'), 0); const hide = message.loading(t('common.loading'), 0);
getNeFile({ getNeFile({
neType: queryParams.neType, neType: queryParams.neType,
neId: queryParams.neId, neId: queryParams.neId,
path: queryParams.path, path: queryParams.path,
fileName: row.fileName, fileName: row.fileName,
delTemp: true,
}) })
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
@@ -167,12 +168,15 @@ function fnDownloadFile(row: Record<string, any>) {
}) })
.finally(() => { .finally(() => {
hide(); hide();
loading.value = false; downLoading.value = false;
}); });
}, },
}); });
} }
/**访问路径 */
let nePathArr = ref<string[]>([]);
/**进入目录 */ /**进入目录 */
function fnDirCD(dir: string, index?: number) { function fnDirCD(dir: string, index?: number) {
if (index === undefined) { if (index === undefined) {
@@ -235,15 +239,42 @@ function fnGetList(pageNum?: number) {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
tablePagination.total = res.total; tablePagination.total = res.total;
tableState.data = res.rows; tableState.data = res.rows;
if (tablePagination.total <=(queryParams.pageNum - 1) * tablePagination.pageSize &&queryParams.pageNum !== 1) { if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false; tableState.loading = false;
fnGetList(queryParams.pageNum - 1); fnGetList(queryParams.pageNum - 1);
} }
} else {
message.error(res.msg, 3);
tablePagination.total = 0;
tableState.data = [];
} }
tableState.loading = false; tableState.loading = false;
}); });
} }
/**抽屉状态 */
const viewDrawerState = reactive({
visible: false,
/**文件路径 /var/log/amf.log */
filePath: '',
/**网元类型 */
neType: '',
/**网元ID */
neId: '',
});
/**打开抽屉查看 */
function fnDrawerOpen(row: Record<string, any>) {
viewDrawerState.filePath = [...nePathArr.value, row.fileName].join('/');
viewDrawerState.neType = neTypeSelect.value[0];
viewDrawerState.neId = neTypeSelect.value[1];
viewDrawerState.visible = !viewDrawerState.visible;
}
onMounted(() => { onMounted(() => {
// 获取网元网元列表 // 获取网元网元列表
neInfoStore.fnNelist().then(res => { neInfoStore.fnNelist().then(res => {
@@ -254,8 +285,8 @@ onMounted(() => {
duration: 2, duration: 2,
}); });
} else if (routeParams.neType) { } else if (routeParams.neType) {
neType.value = [routeParams.neType, routeParams.neId] neTypeSelect.value = [routeParams.neType, routeParams.neId];
fnNeChange(neType.value, undefined); fnNeChange(neTypeSelect.value, undefined);
} }
} }
}); });
@@ -276,12 +307,12 @@ onMounted(() => {
style="margin-bottom: 0" style="margin-bottom: 0"
> >
<a-cascader <a-cascader
v-model:value="neType" v-model:value="neTypeSelect"
:options="neInfoStore.getNeCascaderOptions" :options="neInfoStore.getNeCascaderOptions"
@change="fnNeChange" @change="fnNeChange"
:allow-clear="false" :allow-clear="false"
:placeholder="t('views.logManage.neFile.neTypePlease')" :placeholder="t('views.logManage.neFile.neTypePlease')"
:disabled="loading || tableState.loading" :disabled="downLoading || tableState.loading"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
@@ -332,8 +363,15 @@ onMounted(() => {
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'fileName'"> <template v-if="column.key === 'fileName'">
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-tooltip v-if="record.fileType === 'file'">
<template #title>{{ t('common.viewText') }}</template>
<a-button type="link" @click.prevent="fnDrawerOpen(record)">
<template #icon><ProfileOutlined /></template>
</a-button>
</a-tooltip>
<a-button <a-button
type="link" type="link"
:loading="downLoading"
@click.prevent="fnDownloadFile(record)" @click.prevent="fnDownloadFile(record)"
v-if="record.fileType === 'file'" v-if="record.fileType === 'file'"
> >
@@ -342,6 +380,7 @@ onMounted(() => {
</a-button> </a-button>
<a-button <a-button
type="link" type="link"
:loading="downLoading"
@click.prevent="fnDirCD(record.fileName)" @click.prevent="fnDirCD(record.fileName)"
v-if="record.fileType === 'dir'" v-if="record.fileType === 'dir'"
> >
@@ -353,6 +392,14 @@ onMounted(() => {
</template> </template>
</a-table> </a-table>
</a-card> </a-card>
<!-- 文件内容查看抽屉 -->
<ViewDrawer
v-model:visible="viewDrawerState.visible"
:file-path="viewDrawerState.filePath"
:ne-type="viewDrawerState.neType"
:ne-id="viewDrawerState.neId"
></ViewDrawer>
</PageContainer> </PageContainer>
</template> </template>

View File

@@ -134,16 +134,6 @@ onMounted(() => {
function fnChangeLocale(e: any) { function fnChangeLocale(e: any) {
changeLocale(e.key); changeLocale(e.key);
} }
/**系统使用手册跳转 */
function fnClickHelpDoc(language?: string) {
const routeData = router.resolve({ name: 'HelpDoc' });
let href = routeData.href;
if (language) {
href = `${routeData.href}?language=${language}`;
}
window.open(href, '_blank');
}
</script> </script>
<template> <template>
@@ -166,23 +156,6 @@ function fnClickHelpDoc(language?: string) {
<img :src="logoUrl" class="logo-brand" :alt="appStore.appName" /> <img :src="logoUrl" class="logo-brand" :alt="appStore.appName" />
</template> </template>
</div> </div>
<div class="header-right">
<a-space direction="horizontal" :size="8">
<a-button
type="link"
:href="appStore.officialUrl"
target="_blank"
size="small"
v-if="appStore.officialUrl !== '#'"
>
{{ t('loayouts.basic.officialUrl') }}
</a-button>
<a-button type="link" size="small" @click="fnClickHelpDoc()">
{{ t('loayouts.basic.helpDoc') }}
</a-button>
</a-space>
</div>
</header> </header>
<a-card :bordered="false" class="login-card"> <a-card :bordered="false" class="login-card">

View File

@@ -34,7 +34,7 @@ const graphG6Dom = ref<HTMLElement | undefined>(undefined);
/**图状态 */ /**图状态 */
const graphState = reactive<Record<string, any>>({ const graphState = reactive<Record<string, any>>({
/**当前图组名 */ /**当前图组名 */
group: '5GC System Architecture5', group: '5GC System Architecture',
/**图数据 */ /**图数据 */
data: { data: {
combos: [], combos: [],

View File

@@ -1,296 +0,0 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message } from 'ant-design-vue/lib';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useI18n from '@/hooks/useI18n';
import {
getPara5GFilee,
listNeInfo,
savePara5GFile,
updateNeInfo,
} from '@/api/ne/neInfo';
import useNeInfoStore from '@/store/modules/neinfo';
import Para5GForm from './components/Para5GForm.vue';
const { t } = useI18n();
/**对象信息信息状态类型 */
type StateType = {
/**保存选择同步到网元窗 */
visible: boolean;
/**网元选择 */
neSelectOtions: any[];
/**同步到网元 */
sync: boolean;
syncNe: string[];
syncMsg: string;
/**表单数据 */
from: Record<string, any>;
/**OMC信息需修改当前的IP */
omcInfo: Record<string, any>;
/**根据网元显示配置项 */
hasNE: {
amf: boolean;
upf: boolean;
ims: boolean;
mme: boolean;
};
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对象信息状态 */
let state: StateType = reactive({
visible: false,
neSelectOtions: [],
sync: false,
syncNe: [],
syncMsg: '',
from: {},
omcInfo: {},
hasNE: {
amf: false,
upf: false,
ims: false,
mme: false,
},
confirmLoading: false,
});
/**对话框弹出确认执行函数*/
function fnModalOk() {
if (state.confirmLoading) return;
state.confirmLoading = true;
savePara5GFile({
content: toRaw(state.from),
syncNe: state.sync ? state.syncNe : [],
})
.then(res => {
if (state.sync) {
if (res.code === RESULT_CODE_SUCCESS) {
state.syncMsg = t('views.ne.neConfPara5G.syncNeDone');
} else {
state.syncMsg = res.msg;
}
} else {
message.success(t('views.ne.neConfPara5G.saveOk'));
// 更新omc_ip
state.omcInfo.ip = state.from.sbi.omc_ip;
updateNeInfo(toRaw(state.omcInfo));
}
})
.finally(() => {
state.confirmLoading = false;
});
}
/**对话框弹出关闭执行函数*/
function fnModalCancel() {
state.visible = false;
state.sync = false;
state.syncNe = [];
state.syncMsg = '';
}
/**保存文件数据*/
function fnSaveData() {
state.visible = true;
}
/**获取文件数据*/
function fnGetData() {
state.confirmLoading = true;
Promise.all([
getPara5GFilee(),
listNeInfo({
pageNum: 1,
pageSize: 20,
}),
]).then(resArr => {
// 已保存的配置
if (resArr[0].code === RESULT_CODE_SUCCESS) {
Object.assign(state.from, resArr[0].data);
}
// 填充固定网元类型的ip
if (
resArr[1].code === RESULT_CODE_SUCCESS &&
Array.isArray(resArr[1].rows)
) {
for (const item of resArr[1].rows) {
switch (item.neType) {
case 'OMC':
// state.from.sbi.omc_ip = item.ip;
Object.assign(state.omcInfo, item); // 主动改OMC_IP
break;
case 'IMS':
state.from.sbi.ims_ip = item.ip;
// state.from.external.ims_sip_ip = item.ip;
state.hasNE.ims = true;
break;
case 'AMF':
state.from.sbi.amf_ip = item.ip;
state.hasNE.amf = true;
break;
case 'AUSF':
state.from.sbi.ausf_ip = item.ip;
break;
case 'UDM':
state.from.sbi.udm_ip = item.ip;
state.from.sbi.adb_ip = '0.0.0.0';
break;
case 'SMF':
state.from.sbi.smf_ip = item.ip;
break;
case 'PCF':
state.from.sbi.pcf_ip = item.ip;
break;
case 'NSSF':
state.from.sbi.nssf_ip = item.ip;
break;
case 'NRF':
state.from.sbi.nrf_ip = item.ip;
break;
case 'UPF':
state.from.sbi.upf_ip = item.ip;
state.hasNE.upf = true;
break;
case 'LMF':
state.from.sbi.lmf_ip = item.ip;
break;
case 'NEF':
state.from.sbi.nef_ip = item.ip;
break;
case 'MME':
state.from.sbi.mme_ip = item.ip;
if (item.ip.includes('.')) {
state.from.external.mmes11_ip = item.ip + '/24';
}
state.hasNE.mme = true;
break;
case 'N3IWF':
state.from.sbi.n3iwf_ip = item.ip;
break;
}
}
}
state.confirmLoading = false;
});
}
onMounted(() => {
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
for (const row of res.data) {
state.neSelectOtions.push({
label: `[${row.neType} ${row.neId}] ${row.neName}`,
value: `${row.neType}@${row.neId}`,
});
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
// 获取文件数据
fnGetData();
});
});
</script>
<template>
<PageContainer>
<template #content>
{{ t('views.ne.neConfPara5G.headerTip') }}
<RouterLink :to="{ name: 'NeQuickSetup_2142' }">
{{ t('views.ne.neConfPara5G.headerTipToPage') }}
</RouterLink>
</template>
<a-card :bordered="false">
<!-- 公共参数表单 -->
<Para5GForm v-model:data="state.from" :ne="state.hasNE"></Para5GForm>
<div style="padding: 24px 12px 0; text-align: end">
<a-space :size="8" align="center">
<a-button
type="primary"
:loading="state.confirmLoading"
@click="fnSaveData()"
>
<template #icon><SaveOutlined /></template>
{{ t('views.ne.neConfPara5G.save') }}
</a-button>
<a-button
type="default"
:loading="state.confirmLoading"
@click.prevent="fnGetData()"
>
<template #icon><ReloadOutlined /></template>
{{ t('views.ne.neConfPara5G.reload') }}
</a-button>
</a-space>
</div>
</a-card>
<!-- 保存选择同步网元 -->
<ProModal
:drag="true"
:minHeight="0"
:keyboard="false"
:mask-closable="false"
:visible="state.visible"
:title="t('views.ne.neConfPara5G.title')"
:confirm-loading="state.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="syncNeModal"
layout="horizontal"
:label-col="{ span: 5 }"
:label-wrap="true"
>
<a-form-item :label="t('views.ne.neConfPara5G.sync')" name="sync">
<a-switch
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
v-model:checked="state.sync"
:disabled="state.confirmLoading"
></a-switch>
</a-form-item>
<a-form-item
:label="t('views.ne.neConfPara5G.syncNe')"
name="syncNe"
v-if="state.sync"
>
<a-select
v-model:value="state.syncNe"
mode="multiple"
:placeholder="t('common.selectPlease')"
:max-tag-count="3"
:options="state.neSelectOtions"
>
<template #maxTagPlaceholder="omittedValues">
<span>+ {{ omittedValues.length }} ...</span>
</template>
</a-select>
</a-form-item>
<a-form-item label="Sync Msg" name="syncMsg" v-if="state.syncMsg">
<a-textarea
:disabled="true"
:value="state.syncMsg"
:auto-size="{ minRows: 2, maxRows: 8 }"
style="background-color: transparent; color: rgba(0, 0, 0, 0.85)"
/>
</a-form-item>
</a-form>
</ProModal>
</PageContainer>
</template>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,412 @@
import {
addNeConfigData,
delNeConfigData,
editNeConfigData,
} from '@/api/ne/neConfig';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { Modal } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import message from 'ant-design-vue/lib/message';
import { reactive, watch } from 'vue';
/**
* 参数配置array类型
* @param param 父级传入 { t, treeState, neTypeSelect, fnActiveConfigNode, ruleVerification, modalState, fnModalCancel}
* @returns
*/
export default function useConfigArray({
t,
treeState,
neTypeSelect,
fnActiveConfigNode,
ruleVerification,
modalState,
fnModalCancel,
}: any) {
/**多列列表状态类型 */
type ArrayStateType = {
/**紧凑型 */
size: SizeType;
/**多列嵌套记录字段 */
columns: Record<string, any>[];
/**表格字段列排序 */
columnsDnd: Record<string, any>[];
/**多列记录数据 */
columnsData: Record<string, any>[];
/**多列嵌套展开key */
arrayChildExpandKeys: any[];
/**多列记录数据 */
data: Record<string, any>[];
/**多列记录规则 */
dataRule: Record<string, any>;
};
/**多列列表状态 */
let arrayState: ArrayStateType = reactive({
size: 'small',
columns: [],
columnsDnd: [],
columnsData: [],
arrayChildExpandKeys: [],
data: [],
dataRule: {},
});
/**多列表编辑 */
function arrayEdit(rowIndex: Record<string, any>) {
const item = arrayState.data.find((s: any) => s.key === rowIndex.value);
if (!item) return;
const from = arrayInitEdit(item, arrayState.dataRule);
// 处理信息
const row: Record<string, any> = {};
for (const v of from.record) {
if (Array.isArray(v.array)) {
continue;
}
row[v.name] = Object.assign({}, v);
}
// 特殊SMF-upfid选择
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(row, 'upfId')) {
const v = row.upfId.value;
if (typeof v === 'string') {
if (v === '') {
row.upfId.value = [];
} else if (v.includes(';')) {
row.upfId.value = v.split(';');
} else if (v.includes(',')) {
row.upfId.value = v.split(',');
} else {
row.upfId.value = [v];
}
}
}
modalState.from = row;
modalState.type = 'arrayEdit';
modalState.title = `${treeState.selectNode.paramDisplay} ${from.title}`;
modalState.key = from.key;
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
modalState.visible = true;
// 关闭嵌套
arrayState.arrayChildExpandKeys = [];
}
/**多列表编辑关闭 */
function arrayEditClose() {
arrayState.arrayChildExpandKeys = [];
fnModalCancel();
}
/**多列表编辑确认 */
function arrayEditOk(from: Record<string, any>) {
const loc = `${from['index']['value']}`;
// 特殊SMF-upfid选择
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(from, 'upfId')) {
const v = from.upfId.value;
if (Array.isArray(v)) {
from.upfId.value = v.join(';');
}
}
// 遍历提取属性和值
let data: Record<string, any> = {};
for (const key in from) {
// 子嵌套的不插入
if (from[key]['array']) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(from[key]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
data[key] = from[key]['value'];
}
// 发送
const hide = message.loading(t('common.loading'), 0);
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc: loc,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.ne.neConfig.updateItem', {
num: modalState.title,
}),
duration: 3,
});
fnActiveConfigNode('#');
} else {
message.warning({
content: t('views.ne.neConfig.updateItemErr'),
duration: 3,
});
}
})
.finally(() => {
hide();
arrayEditClose();
});
}
/**多列表删除单行 */
function arrayDelete(rowIndex: Record<string, any>) {
const loc = `${rowIndex.value}`;
const title = `${treeState.selectNode.paramDisplay} Index-${loc}`;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neConfig.delItemTip', {
num: title,
}),
onOk() {
delNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
loc: loc,
}).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.ne.neConfig.delItemOk', {
num: title,
}),
duration: 2,
});
arrayEditClose();
fnActiveConfigNode('#');
} else {
message.error({
content: `${res.msg}`,
duration: 2,
});
}
});
},
});
}
/**多列表新增单行 */
function arrayAdd() {
const from = arrayInitAdd(arrayState.data, arrayState.dataRule);
// 处理信息
const row: Record<string, any> = {};
for (const v of from.record) {
if (Array.isArray(v.array)) {
continue;
}
row[v.name] = Object.assign({}, v);
}
// 特殊SMF-upfid选择
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(row, 'upfId')) {
const v = row.upfId.value;
if (typeof v === 'string') {
if (v === '') {
row.upfId.value = [];
} else if (v.includes(';')) {
row.upfId.value = v.split(';');
} else if (v.includes(',')) {
row.upfId.value = v.split(',');
} else {
row.upfId.value = [v];
}
}
}
modalState.from = row;
modalState.type = 'arrayAdd';
modalState.title = `${treeState.selectNode.paramDisplay} ${from.title}`;
modalState.key = from.key;
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
modalState.visible = true;
}
/**多列表新增单行确认 */
function arrayAddOk(from: Record<string, any>) {
// 特殊SMF-upfid选择
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(from, 'upfId')) {
const v = from.upfId.value;
if (Array.isArray(v)) {
from.upfId.value = v.join(';');
}
}
// 遍历提取属性和值
let data: Record<string, any> = {};
for (const key in from) {
// 子嵌套的不插入
if (from[key]['array']) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(from[key]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
data[key] = from[key]['value'];
}
// 发送
const hide = message.loading(t('common.loading'), 0);
addNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc: `${from['index']['value']}`,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.ne.neConfig.addItemOk', {
num: modalState.title,
}),
duration: 3,
});
fnActiveConfigNode('#');
} else {
message.warning({
content: t('views.ne.neConfig.addItemErr'),
duration: 3,
});
}
})
.finally(() => {
hide();
arrayEditClose();
});
}
/**多列表编辑行数据初始化 */
function arrayInitEdit(data: Record<string, any>, dataRule: any) {
const dataFrom = data.record;
const ruleFrom = Object.assign({}, JSON.parse(JSON.stringify(dataRule)));
for (const row of ruleFrom.record) {
// 子嵌套的不初始
if (row.array) {
row.value = [];
continue;
}
// 查找项的值
const item = dataFrom.find((s: any) => s.name === row.name);
if (!item) {
continue;
}
// 可选的
row.optional = 'true';
// 根据规则类型转值
if (['enum', 'int'].includes(row.type)) {
row.value = Number(item.value);
} else if ('bool' === row.type) {
row.value = Boolean(item.value);
} else {
row.value = item.value;
}
}
ruleFrom.key = data.key;
ruleFrom.title = data.title;
return ruleFrom;
}
/**多列表新增行数据初始化 */
function arrayInitAdd(data: any[], dataRule: any) {
// 有数据时取得最后的index
let dataLastIndex = 0;
if (data.length !== 0) {
const lastFrom = Object.assign(
{},
JSON.parse(JSON.stringify(data.at(-1)))
);
if (lastFrom.record.length > 0) {
dataLastIndex = parseInt(lastFrom.key);
dataLastIndex += 1;
}
}
const ruleFrom = Object.assign({}, JSON.parse(JSON.stringify(dataRule)));
for (const row of ruleFrom.record) {
// 子嵌套的不初始
if (row.array) {
row.value = [];
continue;
}
// 可选的
row.optional = 'true';
// index值
if (row.name === 'index') {
let newIndex =
dataLastIndex !== 0 ? dataLastIndex : parseInt(row.value);
if (isNaN(newIndex)) {
newIndex = 0;
}
row.value = newIndex;
ruleFrom.key = newIndex;
ruleFrom.title = `Index-${newIndex}`;
continue;
}
// 根据规则类型转值
if (['enum', 'int'].includes(row.type)) {
row.value = Number(row.value);
}
if ('bool' === row.type) {
row.value = Boolean(row.value);
}
// 特殊SMF-upfid选择
if (neTypeSelect.value[0] === 'SMF' && row.name === 'upfId') {
const v = row.value;
if (typeof v === 'string') {
if (v === '') {
row.value = [];
} else if (v.includes(';')) {
row.value = v.split(';');
} else if (v.includes(',')) {
row.value = v.split(',');
} else {
row.value = [v];
}
}
}
}
return ruleFrom;
}
// 监听表格字段列排序变化关闭展开
watch(
() => arrayState.columnsDnd,
() => {
arrayEditClose();
}
);
return {
arrayState,
arrayEdit,
arrayEditClose,
arrayEditOk,
arrayDelete,
arrayAdd,
arrayAddOk,
arrayInitEdit,
arrayInitAdd,
};
}

View File

@@ -0,0 +1,353 @@
import {
addNeConfigData,
editNeConfigData,
delNeConfigData,
} from '@/api/ne/neConfig';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { Modal } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import message from 'ant-design-vue/lib/message';
import { nextTick, reactive } from 'vue';
/**
* 参数配置array类型的嵌套array
* @param param 父级传入 { t, treeState, neTypeSelect, fnActiveConfigNode, ruleVerification, modalState, arrayState, arrayInitEdit, arrayInitAdd, arrayEditClose}
* @returns
*/
export default function useConfigArrayChild({
t,
treeState,
neTypeSelect,
fnActiveConfigNode,
ruleVerification,
modalState,
arrayState,
arrayInitEdit,
arrayInitAdd,
arrayEditClose,
}: any) {
/**多列嵌套列表状态类型 */
type ArrayChildStateType = {
/**标题 */
title: string;
/**层级index */
loc: string;
/**紧凑型 */
size: SizeType;
/**多列嵌套记录字段 */
columns: Record<string, any>[];
/**表格字段列排序 */
columnsDnd: Record<string, any>[];
/**多列记录数据 */
columnsData: Record<string, any>[];
/**多列嵌套记录数据 */
data: Record<string, any>[];
/**多列嵌套记录规则 */
dataRule: Record<string, any>;
};
/**多列嵌套表格状态 */
let arrayChildState: ArrayChildStateType = reactive({
title: '',
loc: '',
size: 'small',
columns: [],
columnsDnd: [],
columnsData: [],
data: [],
dataRule: {},
});
/**多列表展开嵌套行 */
function arrayChildExpand(
indexRow: Record<string, any>,
row: Record<string, any>
) {
const loc = indexRow.value;
if (arrayChildState.loc === `${loc}/${row.name}`) {
arrayChildState.loc = '';
arrayState.arrayChildExpandKeys = [];
return;
}
arrayChildState.loc = '';
arrayState.arrayChildExpandKeys = [];
const from = Object.assign({}, JSON.parse(JSON.stringify(row)));
// 无数据时
if (!Array.isArray(from.value)) {
from.value = [];
}
const dataArr = Object.freeze(from.value);
const ruleArr = Object.freeze(from.array);
// 列表项数据
const dataArray: Record<string, any>[] = [];
for (const item of dataArr) {
const index = item['index'];
let record: Record<string, any>[] = [];
for (const key of Object.keys(item)) {
// 规则为准
for (const rule of ruleArr) {
if (rule['name'] === key) {
const ruleItem = Object.assign({ optional: 'true' }, rule, {
value: item[key],
});
record.push(ruleItem);
break;
}
}
}
// dataArray.push(record);
dataArray.push({ title: `Index-${index}`, key: index, record });
}
arrayChildState.data = dataArray;
// 无数据时,用于新增
arrayChildState.dataRule = {
title: `Index-0`,
key: 0,
record: ruleArr,
};
// 列表数据
const columnsData: Record<string, any>[] = [];
for (const v of arrayChildState.data) {
const row: Record<string, any> = {};
for (const item of v.record) {
row[item.name] = item;
}
columnsData.push(row);
}
arrayChildState.columnsData = columnsData;
// 列表字段
const columns: Record<string, any>[] = [];
for (const rule of arrayChildState.dataRule.record) {
columns.push({
title: rule.display,
dataIndex: rule.name,
align: 'left',
resizable: true,
width: 50,
minWidth: 50,
maxWidth: 250,
});
}
columns.push({
title: t('common.operate'),
dataIndex: 'index',
key: 'index',
align: 'center',
fixed: 'right',
width: 100,
});
arrayChildState.columns = columns;
nextTick(() => {
// 设置展开key
arrayState.arrayChildExpandKeys = [indexRow];
// 层级标识
arrayChildState.loc = `${loc}/${from['name']}`;
// 设置展开列表标题
arrayChildState.title = `${from['display']}`;
});
}
/**多列表嵌套行编辑 */
function arrayChildEdit(rowIndex: Record<string, any>) {
const item = arrayChildState.data.find(
(s: any) => s.key === rowIndex.value
);
if (!item) return;
const from = arrayInitEdit(item, arrayChildState.dataRule);
// 处理信息
const row: Record<string, any> = {};
for (const v of from.record) {
if (Array.isArray(v.array)) {
continue;
}
row[v.name] = Object.assign({}, v);
}
modalState.from = row;
modalState.type = 'arrayChildEdit';
modalState.title = `${arrayChildState.title} ${from.title}`;
modalState.key = from.key;
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
modalState.visible = true;
}
/**多列表嵌套行编辑确认 */
function arrayChildEditOk(from: Record<string, any>) {
const loc = `${arrayChildState.loc}/${from['index']['value']}`;
let data: Record<string, any> = {};
for (const key in from) {
// 子嵌套的不插入
if (from[key]['array']) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(from[key]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
data[key] = from[key]['value'];
}
// 发送
const hide = message.loading(t('common.loading'), 0);
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.ne.neConfig.updateItem', {
num: modalState.title,
}),
duration: 3,
});
fnActiveConfigNode('#');
} else {
message.warning({
content: t('views.ne.neConfig.updateItemErr'),
duration: 3,
});
}
})
.finally(() => {
hide();
arrayEditClose();
});
}
/**多列表嵌套行删除单行 */
function arrayChildDelete(rowIndex: Record<string, any>) {
const index = rowIndex.value;
const loc = `${arrayChildState.loc}/${index}`;
const title = `${arrayChildState.title} Index-${index}`;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neConfig.delItemTip', {
num: title,
}),
onOk() {
delNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
loc,
}).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.ne.neConfig.delItemOk', {
num: title,
}),
duration: 2,
});
arrayEditClose();
fnActiveConfigNode('#');
} else {
message.error({
content: `${res.msg}`,
duration: 2,
});
}
});
},
});
}
/**多列表嵌套行新增单行 */
function arrayChildAdd() {
const from = arrayInitAdd(arrayChildState.data, arrayChildState.dataRule);
// 处理信息
const row: Record<string, any> = {};
for (const v of from.record) {
if (Array.isArray(v.array)) {
continue;
}
row[v.name] = Object.assign({}, v);
}
modalState.from = row;
modalState.type = 'arrayChildAdd';
modalState.title = `${arrayChildState.title} ${from.title}`;
modalState.key = from.key;
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
modalState.visible = true;
}
/**多列表新增单行确认 */
function arrayChildAddOk(from: Record<string, any>) {
const loc = `${arrayChildState.loc}/${from['index']['value']}`;
let data: Record<string, any> = {};
for (const key in from) {
// 子嵌套的不插入
if (from[key]['array']) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(from[key]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
data[key] = from[key]['value'];
}
// 发送
const hide = message.loading(t('common.loading'), 0);
addNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.ne.neConfig.addItemOk', {
num: modalState.title,
}),
duration: 3,
});
fnActiveConfigNode('#');
} else {
message.warning({
content: t('views.ne.neConfig.addItemErr'),
duration: 3,
});
}
})
.finally(() => {
hide();
arrayEditClose();
});
}
return {
arrayChildState,
arrayChildExpand,
arrayChildEdit,
arrayChildEditOk,
arrayChildDelete,
arrayChildAdd,
arrayChildAddOk,
};
}

View File

@@ -0,0 +1,152 @@
import { editNeConfigData } from '@/api/ne/neConfig';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import message from 'ant-design-vue/lib/message';
import { reactive, toRaw } from 'vue';
/**
* list类型参数处理
* @param param 父级传入 {t, treeState, neTypeSelect, ruleVerification}
* @returns
*/
export default function useConfigList({
t,
treeState,
neTypeSelect,
ruleVerification,
}: any) {
/**单列表状态类型 */
type ListStateType = {
/**紧凑型 */
size: SizeType;
/**单列记录字段 */
columns: Record<string, any>[];
/**单列记录数据 */
data: Record<string, any>[];
/**编辑行记录 */
editRecord: Record<string, any>;
/**确认提交等待 */
confirmLoading: boolean;
};
/**单列表状态 */
let listState: ListStateType = reactive({
size: 'small',
columns: [
{
title: 'Key',
dataIndex: 'display',
align: 'left',
width: '30%',
},
{
title: 'Value',
dataIndex: 'value',
align: 'left',
width: '70%',
},
],
data: [],
confirmLoading: false,
editRecord: {},
});
/**单列表编辑 */
function listEdit(row: Record<string, any>) {
if (
listState.confirmLoading ||
['read-only', 'read', 'ro'].includes(row.access)
) {
return;
}
listState.editRecord = Object.assign({}, row);
}
/**单列表编辑关闭 */
function listEditClose() {
listState.confirmLoading = false;
listState.editRecord = {};
}
/**单列表编辑确认 */
function listEditOk() {
if (listState.confirmLoading) return;
const from = toRaw(listState.editRecord);
// 检查规则
const [ok, msg] = ruleVerification(from);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
// 发送
listState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: {
[from['name']]: from['value'],
},
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.ne.neConfig.updateValue', {
num: from['display'],
}),
duration: 3,
});
// 改变表格数据
const item = listState.data.find(
(item: Record<string, any>) => from['name'] === item['name']
);
if (item) {
Object.assign(item, listState.editRecord);
}
} else {
message.warning({
content: t('views.ne.neConfig.updateValueErr'),
duration: 3,
});
}
})
.finally(() => {
hide();
listState.confirmLoading = false;
listState.editRecord = {};
});
}
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 10,
/**默认的每页条数 */
defaultPageSize: 10,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: true,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
},
});
return { tablePagination, listState, listEdit, listEditClose, listEditOk };
}

View File

@@ -0,0 +1,192 @@
import { getNeConfigData } from '@/api/ne/neConfig';
import { regExpIPv4, regExpIPv6, validURL } from '@/utils/regular-utils';
import { ref } from 'vue';
/**
* 参数公共函数
* @param param 父级传入 {t}
* @returns
*/
export default function useOptions({ t }: any) {
/**规则校验 */
function ruleVerification(row: Record<string, any>): (string | boolean)[] {
let result = [true, ''];
const type = row.type;
const value = row.value;
const filter = row.filter;
const display = row.display;
// 子嵌套的不检查
if (row.array) {
return result;
}
// 可选的同时没有值不检查
if (row.optional === 'true' && !value) {
return result;
}
switch (type) {
case 'int':
// filter: "0~128"
if (filter && filter.indexOf('~') !== -1) {
const filterArr = filter.split('~');
const minInt = parseInt(filterArr[0]);
const maxInt = parseInt(filterArr[1]);
const valueInt = parseInt(value);
if (valueInt < minInt || valueInt > maxInt) {
return [
false,
t('views.ne.neConfig.requireInt', {
display,
filter,
}),
];
}
}
break;
case 'ipv4':
if (!regExpIPv4.test(value)) {
return [
false,
t('views.ne.neConfig.requireIpv4', { display }),
];
}
break;
case 'ipv6':
if (!regExpIPv6.test(value)) {
return [
false,
t('views.ne.neConfig.requireIpv6', { display }),
];
}
break;
case 'enum':
if (filter && filter.indexOf('{') === 1) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
if (!Object.keys(filterJson).includes(`${value}`)) {
return [
false,
t('views.ne.neConfig.requireEnum', { display }),
];
}
}
break;
case 'bool':
// filter: '{"0":"false", "1":"true"}'
if (filter && filter.indexOf('{') === 1) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
if (!Object.values(filterJson).includes(`${value}`)) {
return [
false,
t('views.ne.neConfig.requireBool', { display }),
];
}
}
break;
case 'string':
// filter: "0~128"
// 字符串长度判断
if (filter && filter.indexOf('~') !== -1) {
try {
const filterArr = filter.split('~');
let rule = new RegExp(
'^\\S{' + filterArr[0] + ',' + filterArr[1] + '}$'
);
if (!rule.test(value)) {
return [
false,
t('views.ne.neConfig.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
// 字符串http判断
if (value.startsWith('http')) {
try {
if (!validURL(value)) {
return [
false,
t('views.ne.neConfig.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
break;
case 'regex':
// filter: "^[0-9]{3}$"
if (filter) {
try {
let regex = new RegExp(filter);
if (!regex.test(value)) {
return [
false,
t('views.ne.neConfig.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
break;
default:
return [
false,
t('views.ne.neConfig.requireUn', { display }),
];
}
return result;
}
/**upfId可选择 */
const smfByUPFIdOptions = ref<{ value: string; label: string }[]>([]);
/**加载smf配置的upfId */
function smfByUPFIdLoadData(neId: string) {
getNeConfigData({
neType: 'SMF',
neId: neId,
paramName: 'upfConfig',
}).then(res => {
smfByUPFIdOptions.value = [];
for (const s of res.data) {
smfByUPFIdOptions.value.push({
value: s.id,
label: s.id,
});
}
});
}
return {
ruleVerification,
smfByUPFIdLoadData,
smfByUPFIdOptions,
};
}

View File

@@ -0,0 +1,951 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw, watch } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message } from 'ant-design-vue/lib';
import { DataNode } from 'ant-design-vue/lib/tree';
import useI18n from '@/hooks/useI18n';
import TableColumnsDnd from '@/components/TableColumnsDnd/index.vue';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useNeInfoStore from '@/store/modules/neinfo';
import useOptions from './hooks/useOptions';
import useConfigList from './hooks/useConfigList';
import useConfigArray from './hooks/useConfigArray';
import useConfigArrayChild from './hooks/useConfigArrayChild';
import { getAllNeConfig, getNeConfigData } from '@/api/ne/neConfig';
const neInfoStore = useNeInfoStore();
const { t } = useI18n();
const { ruleVerification, smfByUPFIdLoadData, smfByUPFIdOptions } = useOptions({
t,
});
/**网元类型_多neId */
let neCascaderOptions = ref<Record<string, any>[]>([]);
/**网元类型选择 type,id */
let neTypeSelect = ref<string[]>(['', '']);
/**左侧导航是否可收起 */
let collapsible = ref<boolean>(true);
/**改变收起状态 */
function changeCollapsible() {
collapsible.value = !collapsible.value;
}
/**对象信息状态类型 */
type TreeStateType = {
/**网元 loading */
loading: boolean;
/**网元配置 tree */
data: DataNode[];
/**选择对应Node一级 tree */
selectNode: {
title: string;
key: string;
// 可选属性数据
paramName: string;
paramDisplay: string;
paramType: string;
paramPerms: string[];
paramData: Record<string, any>[];
};
/**选择 loading */
selectLoading: boolean;
};
let treeState: TreeStateType = reactive({
loading: true,
data: [],
selectNode: {
paramName: '',
paramDisplay: '',
paramType: '',
paramPerms: [],
paramData: [],
// 树形节点需要有
title: '',
key: '',
},
selectLoading: true,
});
/**查询可选命令列表 */
function fnSelectConfigNode(_: any, info: any) {
const { key } = info.node;
if (treeState.selectNode.paramName == key) {
return;
}
fnActiveConfigNode(key);
}
/**列表项点击监听 */
function fnActiveConfigNode(key: string | number) {
listState.data = [];
arrayState.data = [];
treeState.selectLoading = true;
if (key === '#') {
key = treeState.selectNode.paramName;
} else {
treeState.selectNode.paramName = key as string;
}
const param = treeState.data.find(item => item.key === key);
if (!param) {
message.warning({
content: t('common.noData'),
duration: 3,
});
return;
}
treeState.selectNode = JSON.parse(JSON.stringify(param));
// 获取网元端的配置数据
getNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: key,
}).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
const ruleArr = param.paramData;
const dataArr = res.data;
if (param.paramType === 'list') {
// 列表项数据
const dataList = [];
for (const item of dataArr) {
for (const key in item) {
// 规则为准
for (const rule of ruleArr) {
if (rule['name'] === key) {
const ruleItem = Object.assign(rule, {
optional: 'true',
value: item[key],
});
dataList.push(ruleItem);
break;
}
}
}
}
listState.data = dataList;
tablePagination.current = 1;
listEditClose();
}
if (param.paramType === 'array') {
// 列表项数据
const dataArray = [];
for (const item of dataArr) {
const index = item['index'];
let record: Record<string, any>[] = [];
for (const key in item) {
// 规则为准
for (const rule of ruleArr) {
if (rule['name'] === key) {
const ruleItem = Object.assign({ optional: 'true' }, rule, {
value: item[key],
});
record.push(ruleItem);
break;
}
}
}
dataArray.push({ title: `Index-${index}`, key: index, record });
}
arrayState.data = dataArray;
// 无数据时,用于新增
arrayState.dataRule = { title: `Index-0`, key: 0, record: ruleArr };
// 列表数据
const columnsData: Record<string, any>[] = [];
for (const v of arrayState.data) {
const row: Record<string, any> = {};
for (const item of v.record) {
row[item.name] = item;
}
columnsData.push(row);
}
arrayState.columnsData = columnsData;
// 列表字段
const columns: Record<string, any>[] = [];
for (const rule of arrayState.dataRule.record) {
columns.push({
title: rule.display,
dataIndex: rule.name,
align: 'left',
resizable: true,
width: 150,
minWidth: 100,
maxWidth: 350,
});
}
columns.push({
title: t('common.operate'),
dataIndex: 'index',
key: 'index',
align: 'center',
fixed: 'right',
width: 100,
});
arrayState.columns = columns;
tablePagination.current = 1;
arrayEditClose();
}
setTimeout(() => {
treeState.selectLoading = false;
}, 300);
} else {
message.warning({
content: `${param.paramDisplay} ${t(
'views.configManage.configParamForm.noConfigData'
)}`,
duration: 3,
});
}
});
}
/**查询配置可选属性值列表 */
function fnGetNeConfig() {
const neType = neTypeSelect.value[0];
if (!neType) {
message.warning({
content: t('views.configManage.configParamForm.neTypePleace'),
duration: 3,
});
return;
}
treeState.loading = true;
// 获取数据
getAllNeConfig(neType).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
const arr = [];
for (const item of res.data) {
let paramPerms: string[] = [];
if (item.paramPerms) {
paramPerms = item.paramPerms.split(',');
} else {
paramPerms = ['post', 'put', 'delete'];
}
arr.push({
...item,
children: undefined,
title: item.paramDisplay,
key: item.paramName,
paramPerms,
});
}
treeState.data = arr;
treeState.loading = false;
// 取首个tag
if (res.data.length > 0) {
const item = JSON.parse(JSON.stringify(treeState.data[0]));
treeState.selectNode = item;
treeState.selectLoading = false;
fnActiveConfigNode(item.key);
}
}
});
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**添加框是否显示 */
visible: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
/**项类型 */
type: 'arrayAdd' | 'arrayEdit' | 'arrayChildAdd' | 'arrayChildEdit';
/**项Key */
key: string | number;
/**项数据 */
data: Record<string, any>[];
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
visible: false,
title: 'Item',
from: {},
confirmLoading: false,
type: 'arrayAdd',
key: '',
data: [],
});
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
const from = toRaw(modalState.from);
if (modalState.type === 'arrayAdd') {
arrayAddOk(from);
}
if (modalState.type === 'arrayEdit') {
arrayEditOk(from);
}
if (modalState.type === 'arrayChildAdd') {
arrayChildAddOk(from);
}
if (modalState.type === 'arrayChildEdit') {
arrayChildEditOk(from);
}
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.visible = false;
modalState.from = {};
modalState.type = 'arrayAdd';
modalState.key = '';
modalState.data = [];
}
// 监听新增编辑弹窗
watch(
() => modalState.visible,
val => {
// SMF需要选择配置的UPF id
if (val && neTypeSelect.value[0] === 'SMF') {
smfByUPFIdLoadData(neTypeSelect.value[1]);
}
}
);
const { tablePagination, listState, listEdit, listEditClose, listEditOk } =
useConfigList({ t, treeState, neTypeSelect, ruleVerification });
const {
arrayState,
arrayEdit,
arrayEditClose,
arrayEditOk,
arrayDelete,
arrayAdd,
arrayAddOk,
arrayInitEdit,
arrayInitAdd,
} = useConfigArray({
t,
treeState,
neTypeSelect,
fnActiveConfigNode,
ruleVerification,
modalState,
fnModalCancel,
});
const {
arrayChildState,
arrayChildExpand,
arrayChildEdit,
arrayChildEditOk,
arrayChildDelete,
arrayChildAdd,
arrayChildAddOk,
} = useConfigArrayChild({
t,
treeState,
neTypeSelect,
fnActiveConfigNode,
ruleVerification,
modalState,
arrayState,
arrayInitEdit,
arrayInitAdd,
arrayEditClose,
});
onMounted(() => {
// 获取网元网元列表
neInfoStore.fnNelist().then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
// 过滤不可用的网元
neCascaderOptions.value = neInfoStore.getNeSelectOtions.filter(
(item: any) => {
return !['LMF', 'NEF'].includes(item.value);
}
);
if (neCascaderOptions.value.length === 0) {
message.warning({
content: t('common.noData'),
duration: 2,
});
return;
}
// 默认选择AMF
const item = neCascaderOptions.value.find(s => s.value === 'AMF');
if (item && item.children) {
const info = item.children[0];
neTypeSelect.value = [info.neType, info.neId];
} else {
const info = neCascaderOptions.value[0].children[0];
neTypeSelect.value = [info.neType, info.neId];
}
fnGetNeConfig();
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
});
});
</script>
<template>
<PageContainer>
<a-row :gutter="16">
<a-col
:lg="6"
:md="6"
:xs="24"
style="margin-bottom: 24px"
v-show="collapsible"
>
<!-- 网元类型 -->
<a-card size="small" :bordered="false" :loading="treeState.loading">
<template #title>
{{ t('views.configManage.configParamForm.treeTitle') }}
</template>
<a-form layout="vertical" autocomplete="off">
<a-form-item name="neId ">
<a-cascader
v-model:value="neTypeSelect"
:options="neCascaderOptions"
:allow-clear="false"
@change="fnGetNeConfig"
/>
</a-form-item>
<a-form-item name="listeningPort">
<a-tree
:tree-data="treeState.data"
:selected-keys="[treeState.selectNode.paramName]"
@select="fnSelectConfigNode"
>
</a-tree>
</a-form-item>
</a-form>
</a-card>
</a-col>
<a-col :lg="collapsible ? 18 : 24" :md="collapsible ? 18 : 24" :xs="24">
<a-card
size="small"
:bordered="false"
:body-style="{ maxHeight: '650px', 'overflow-y': 'auto' }"
:loading="treeState.selectLoading"
>
<template #title>
<a-button
type="text"
size="small"
@click.prevent="changeCollapsible()"
>
<template #icon>
<MenuFoldOutlined v-show="collapsible" />
<MenuUnfoldOutlined v-show="!collapsible" />
</template>
</a-button>
<a-typography-text strong v-if="treeState.selectNode.paramDisplay">
{{ treeState.selectNode.paramDisplay }}
</a-typography-text>
<a-typography-text type="danger" v-else>
{{ t('views.configManage.configParamForm.treeSelectTip') }}
</a-typography-text>
</template>
<template #extra>
<a-space :size="8" align="center" v-show="!treeState.selectLoading">
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button
type="default"
size="small"
@click.prevent="fnActiveConfigNode('#')"
>
<template #icon>
<ReloadOutlined />
</template>
</a-button>
</a-tooltip>
</a-space>
</template>
<!-- 单列表格列表 -->
<a-table
v-if="treeState.selectNode.paramType === 'list'"
class="table"
row-key="name"
:size="listState.size"
:columns="listState.columns"
:data-source="listState.data"
:pagination="tablePagination"
:bordered="true"
:scroll="{ x: true, y: '500px' }"
>
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'value'">
<a-tooltip placement="topLeft">
<template #title v-if="record.comment">
{{ record.comment }}
</template>
<div class="editable-cell">
<div
v-if="
listState.editRecord['display'] === record['display']
"
class="editable-cell__input-wrapper"
>
<a-input-number
v-if="record['type'] === 'int'"
v-model:value="listState.editRecord['value']"
:disabled="listState.confirmLoading"
style="width: 100%"
></a-input-number>
<a-switch
v-else-if="record['type'] === 'bool'"
v-model:checked="listState.editRecord['value']"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
:disabled="listState.confirmLoading"
></a-switch>
<a-select
v-else-if="record['type'] === 'enum'"
v-model:value="listState.editRecord['value']"
:allow-clear="true"
:disabled="listState.confirmLoading"
style="width: 100%"
>
<a-select-option
:value="+v"
:key="+v"
v-for="(k, v) in JSON.parse(record['filter'])"
>
{{ k }}
</a-select-option>
</a-select>
<a-input
v-else
v-model:value="listState.editRecord['value']"
:disabled="listState.confirmLoading"
></a-input>
<a-space
:size="8"
align="center"
direction="horizontal"
style="margin-left: 18px"
>
<a-tooltip placement="bottomRight">
<template #title> {{ t('common.ok') }} </template>
<a-popconfirm
:title="
t(
'views.configManage.configParamForm.editOkTip',
{ num: record['display'] }
)
"
placement="topRight"
:disabled="listState.confirmLoading"
@confirm="listEditOk()"
>
<a-button
type="text"
class="editable-cell__icon-edit"
:disabled="listState.confirmLoading"
>
<template #icon>
<CheckOutlined />
</template>
</a-button>
</a-popconfirm>
</a-tooltip>
<a-tooltip placement="bottomRight">
<template #title> {{ t('common.cancel') }} </template>
<a-button
type="text"
class="editable-cell__icon-edit"
:disabled="listState.confirmLoading"
@click.prevent="listEditClose()"
>
<template #icon>
<CloseOutlined />
</template>
</a-button>
</a-tooltip>
</a-space>
</div>
<div
v-else
class="editable-cell__text-wrapper"
@dblclick="listEdit(record)"
>
<template v-if="record['type'] === 'enum'">
{{ JSON.parse(record['filter'])[text] || '&nbsp;' }}
</template>
<template v-else>{{ `${text}` || '&nbsp;' }}</template>
<EditOutlined
class="editable-cell__icon"
@click="listEdit(record)"
style="margin-left: 18px"
v-if="
!listState.confirmLoading &&
!['read-only', 'read', 'ro'].includes(record.access)
"
/>
</div>
</div>
</a-tooltip>
</template>
</template>
</a-table>
<!-- array类型 -->
<template v-if="treeState.selectNode.paramType === 'array'">
<a-table
class="table"
row-key="index"
:columns="treeState.selectNode.paramPerms.includes('get') ? arrayState.columnsDnd.filter((s:any)=>s.key !== 'index') : arrayState.columnsDnd"
:data-source="arrayState.columnsData"
:size="arrayState.size"
:pagination="tablePagination"
:bordered="true"
:scroll="{ x: arrayState.columnsDnd.length * 200, y: 480 }"
@resizeColumn="(w:number, col:any) => (col.width = w)"
:show-expand-column="false"
v-model:expanded-row-keys="arrayState.arrayChildExpandKeys"
>
<!-- 多列新增操作 -->
<template #title>
<a-space :size="16" align="center">
<a-button
type="primary"
@click.prevent="arrayAdd()"
size="small"
v-if="treeState.selectNode.paramPerms.includes('post')"
>
<template #icon> <PlusOutlined /> </template>
{{ t('common.addText') }}
</a-button>
<TableColumnsDnd
type="ghost"
:cache-id="treeState.selectNode.key"
:columns="treeState.selectNode.paramPerms.includes('get') ? [...arrayState.columns.filter((s:any)=>s.key !== 'index')] : arrayState.columns"
v-model:columns-dnd="arrayState.columnsDnd"
></TableColumnsDnd>
</a-space>
</template>
<!-- 多列数据渲染 -->
<template #bodyCell="{ column, text, record }">
<template v-if="column?.key === 'index'">
<a-space :size="16" align="center">
<a-tooltip
v-if="treeState.selectNode.paramPerms.includes('put')"
>
<template #title>{{ t('common.editText') }}</template>
<a-button type="link" @click.prevent="arrayEdit(text)">
<template #icon><FormOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip
v-if="
treeState.selectNode.paramPerms.includes('delete') &&
!(
neTypeSelect[0] === 'IMS' &&
treeState.selectNode.paramName === 'plmn' &&
text['value'] === 0
)
"
>
<template #title>{{ t('common.deleteText') }}</template>
<a-button type="link" @click.prevent="arrayDelete(text)">
<template #icon><DeleteOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
<template v-else-if="text">
<a-tooltip placement="topLeft">
<template #title v-if="text.comment">
{{ text.comment }}
</template>
<div class="editable-cell">
<template v-if="text.array">
<a-button
type="default"
size="small"
@click.prevent="
arrayChildExpand(record['index'], text)
"
>
<template #icon><BarsOutlined /></template>
{{
t('views.configManage.configParamForm.arrayMore')
}}
</a-button>
<!--特殊字段拓展显示-->
<span
v-if="
text.name === 'dnnList' && Array.isArray(text.value)
"
>
({{
text.value.length > 4
? `${text.value
.slice(0, 3)
.map((s: any) => s.dnn)
.join()}...${text.value.length}`
: text.value.map((s: any) => s.dnn).join()
}})
</span>
</template>
<div v-else class="editable-cell__text-wrapper">
<template v-if="text['type'] === 'enum'">
{{ JSON.parse(text['filter'])[text.value] }}
</template>
<template v-else>
{{ `${text.value}` || '&nbsp;' }}
</template>
</div>
</div>
</a-tooltip>
</template>
</template>
<!-- 多列嵌套类型 -->
<template #expandedRowRender>
<a-table
class="table"
row-key="index"
:columns="arrayChildState.columnsDnd"
:data-source="arrayChildState.columnsData"
:size="arrayChildState.size"
:pagination="tablePagination"
:bordered="true"
:scroll="{
x: arrayChildState.columnsDnd.length * 200,
y: 200,
}"
@resizeColumn="(w:number, col:any) => (col.width = w)"
>
<template #title>
<a-space :size="16" align="center">
<a-button
type="primary"
@click.prevent="arrayChildAdd"
size="small"
>
<template #icon> <PlusOutlined /> </template>
{{ t('common.addText') }} {{ arrayChildState.title }}
</a-button>
<TableColumnsDnd
type="ghost"
:cache-id="`${treeState.selectNode.key}:${arrayChildState.loc}`"
:columns="[...arrayChildState.columns]"
v-model:columns-dnd="arrayChildState.columnsDnd"
v-if="arrayChildState.loc"
></TableColumnsDnd>
</a-space>
</template>
<template #bodyCell="{ column, text, record }">
<template v-if="column?.key === 'index'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.editText') }}</template>
<a-button
type="link"
@click.prevent="arrayChildEdit(text)"
>
<template #icon><FormOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>
{{ t('common.deleteText') }}
</template>
<a-button
type="link"
@click.prevent="arrayChildDelete(text)"
>
<template #icon><DeleteOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
<template v-else-if="text">
<a-tooltip placement="topLeft">
<template #title v-if="text.comment">
{{ text.comment }}
</template>
<div class="editable-cell">
<template v-if="text.array">
<a-button type="default" size="small">
<template #icon><BarsOutlined /></template>
{{
t(
'views.configManage.configParamForm.arrayMore'
)
}}
</a-button>
</template>
<div v-else>
<template v-if="text['type'] === 'enum'">
{{ JSON.parse(text['filter'])[text.value] }}
</template>
<template v-else>
{{ `${text.value}` || '&nbsp;' }}
</template>
</div>
</div>
</a-tooltip>
</template>
</template>
</a-table>
</template>
</a-table>
</template>
</a-card>
</a-col>
</a-row>
<!-- 新增框或修改框 -->
<ProModal
:drag="true"
:width="800"
:destroyOnClose="true"
:body-style="{ maxHeight: '650px', 'overflow-y': 'auto' }"
:keyboard="false"
:mask-closable="false"
:visible="modalState.visible"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
class="form"
layout="horizontal"
autocomplete="off"
:validate-on-rule-change="false"
:validateTrigger="[]"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-form-item
v-for="item in modalState.data"
:label="item.display"
:name="item.name"
:required="item.optional === 'false'"
style="margin-bottom: 4px"
>
<a-tooltip placement="topLeft">
<template #title v-if="item.comment">
{{ item.comment }}
</template>
<div>
<div
v-if="
!Array.isArray(item.array) &&
modalState.from[item.name] !== undefined
"
>
<!-- 特殊SMF-upfid选择 -->
<a-select
v-if="
neTypeSelect[0] === 'SMF' &&
modalState.from[item.name]['name'] === 'upfId'
"
v-model:value="modalState.from[item.name]['value']"
:options="smfByUPFIdOptions"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
:token-separators="[',', ';']"
mode="multiple"
:max-tag-count="5"
:allow-clear="true"
style="width: 100%"
>
</a-select>
<!-- 常规 -->
<a-input-number
v-else-if="item['type'] === 'int'"
v-model:value="modalState.from[item.name]['value']"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
style="width: 100%"
></a-input-number>
<a-switch
v-else-if="item['type'] === 'bool'"
v-model:checked="modalState.from[item.name]['value']"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
></a-switch>
<a-select
v-else-if="item['type'] === 'enum'"
v-model:value="modalState.from[item.name]['value']"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
:allow-clear="true"
style="width: 100%"
>
<a-select-option
:value="+v"
:key="+v"
v-for="(k, v) in JSON.parse(item['filter'])"
>
{{ k }}
</a-select-option>
</a-select>
<a-input
v-else
v-model:value="modalState.from[item.name]['value']"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
></a-input>
</div>
<div v-else>
{{ `${item.value || '&nbsp;'}` }}
</div>
</div>
</a-tooltip>
</a-form-item>
</a-form>
</ProModal>
</PageContainer>
</template>
<style lang="less" scoped>
.editable-cell {
&__icon {
display: none;
cursor: pointer;
}
&__icon:hover {
color: var(--ant-primary-color);
}
&__icon-edit:hover {
color: var(--ant-primary-color);
}
&__text-wrapper {
font-size: 16px;
color: inherit;
}
&__text-wrapper:hover &__icon {
display: inline-block;
}
&__input-wrapper {
display: flex;
justify-content: start;
align-items: center;
}
}
</style>

View File

@@ -0,0 +1,605 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { Form, Modal, TableColumnsType, message } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n';
import useDictStore from '@/store/modules/dict';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { parseDateToStr } from '@/utils/date-utils';
import {
delNeConfigBackup,
downNeConfigBackup,
listNeConfigBackup,
updateNeConfigBackup,
} from '@/api/ne/neConfigBackup';
import saveAs from 'file-saver';
const { t } = useI18n();
const { getDict } = useDictStore();
/**字典数据-状态 */
let dictStatus = ref<DictType[]>([]);
/**网元参数 */
let neOtions = ref<Record<string, any>[]>([]);
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: undefined,
/**名称 */
name: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
neType: undefined,
name: '',
pageNum: 1,
pageSize: 20,
});
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: any[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: false,
data: [],
selectedRowKeys: [],
});
/**表格字段列 */
let tableColumns = ref<TableColumnsType>([
{
title: t('common.rowId'),
dataIndex: 'id',
align: 'left',
width: 100,
},
{
title: t('views.ne.common.neType'),
dataIndex: 'neType',
align: 'left',
width: 100,
},
{
title: t('views.ne.common.neId'),
dataIndex: 'neId',
align: 'left',
width: 100,
},
{
title: t('common.createTime'),
dataIndex: 'createTime',
align: 'center',
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value);
},
width: 150,
},
{
title: t('views.ne.neConfigBackup.name'),
dataIndex: 'name',
align: 'left',
width: 200,
resizable: true,
minWidth: 100,
maxWidth: 300,
ellipsis: true,
},
{
title: t('common.remark'),
dataIndex: 'remark',
key: 'remark',
align: 'left',
width: 150,
resizable: true,
minWidth: 100,
maxWidth: 300,
ellipsis: true,
},
{
title: t('common.operate'),
key: 'id',
align: 'left',
},
]);
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
listNeConfigBackup(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
tablePagination.total = res.total;
tableState.data = res.rows;
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
}
tableState.loading = false;
});
}
/**信息文件下载 */
function fnDownloadFile(row: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neConfigBackup.downTip', { txt: row.name }),
onOk() {
const hide = message.loading(t('common.loading'), 0);
downNeConfigBackup(row.id)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 2,
});
saveAs(res.data, `${row.name}`);
} else {
message.error({
content: `${res.msg}`,
duration: 2,
});
}
})
.finally(() => {
hide();
});
},
});
}
/**
* 记录删除
* @param id 编号
*/
function fnRecordDelete(id: string) {
if (!id || modalState.confirmLoading) return;
let msg = id;
if (id === '0') {
msg = `...${tableState.selectedRowKeys.length}`;
id = tableState.selectedRowKeys.join(',');
}
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.dashboard.ue.delTip', { msg }),
onOk() {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
delNeConfigBackup(id)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
fnGetList(1);
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
},
});
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
visibleByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
visibleByEdit: false,
title: '备份记录',
from: {
id: undefined,
name: '',
remark: '',
},
confirmLoading: false,
});
/**
* 对话框弹出显示为 新增或者修改
* @param noticeId 网元id, 不传为新增
*/
function fnModalVisibleByEdit(row: Record<string, any>) {
if (modalState.confirmLoading) return;
modalState.from.id = row.id;
modalState.from.name = row.name;
modalState.from.remark = row.remark;
modalState.title = t('views.ne.neConfigBackup.title', { txt: row.id });
modalState.visibleByEdit = true;
}
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
name: [
{
required: true,
message: '请输入名称',
},
],
})
);
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
modalStateFrom
.validate()
.then(e => {
modalState.confirmLoading = true;
const from = toRaw(modalState.from);
const hide = message.loading(t('common.loading'), 0);
updateNeConfigBackup(from)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.msgSuccess', { msg: modalState.title }),
duration: 3,
});
modalState.visibleByEdit = false;
modalStateFrom.resetFields();
fnGetList();
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
fnGetList();
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
})
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
});
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.visibleByEdit = false;
modalStateFrom.resetFields();
}
onMounted(() => {
// 初始字典数据
getDict('ne_license_status').then(res => {
dictStatus.value = res;
});
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
neOtions.value = useNeInfoStore().getNeSelectOtions;
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
// 获取列表数据
fnGetList();
});
});
</script>
<template>
<PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neType')" name="neType ">
<a-auto-complete
v-model:value="queryParams.neType"
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
:allow-clear="true"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item :label="t('views.ne.neConfigBackup.name')" name="name">
<a-input
v-model:value="queryParams.name"
:allow-clear="true"
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading"
@click.prevent="fnRecordDelete('0')"
v-perms:has="['ne:neConfigBackup:remove']"
>
<template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }}
</a-button>
</a-space>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip placement="topRight">
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown placement="bottomRight" trigger="click">
<a-button type="text">
<template #icon><ColumnHeightOutlined /></template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
<a-menu-item key="middle">
{{ t('common.size.middle') }}
</a-menu-item>
<a-menu-item key="small">
{{ t('common.size.small') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 180 }"
@resizeColumn="(w:number, col:any) => (col.width = w)"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.downloadText') }}</template>
<a-button type="link" @click.prevent="fnDownloadFile(record)">
<template #icon><DownloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.deleteText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordDelete(record.id)"
v-perms:has="['ne:neConfigBackup:remove']"
>
<template #icon><DeleteOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.editText') }}</template>
<a-button
type="link"
@click.prevent="fnModalVisibleByEdit(record)"
v-perms:has="['ne:neConfigBackup:edit']"
>
<template #icon><FormOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
</a-table>
</a-card>
<!-- 新增框或修改框 -->
<ProModal
:drag="true"
:width="512"
:destroyOnClose="true"
:keyboard="false"
:mask-closable="false"
:visible="modalState.visibleByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="modalStateFrom"
layout="horizontal"
:wrapper-col="{ span: 18 }"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-form-item
:label="t('views.ne.neConfigBackup.name')"
name="name"
v-bind="modalStateFrom.validateInfos.name"
>
<a-input
v-model:value="modalState.from.name"
:allow-clear="true"
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
<a-form-item
:label="t('common.remark')"
name="remark"
v-bind="modalStateFrom.validateInfos.remark"
>
<a-textarea
v-model:value="modalState.from.remark"
:auto-size="{ minRows: 2, maxRows: 6 }"
:maxlength="400"
:show-count="true"
/>
</a-form-item>
</a-form>
</ProModal>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -1,15 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, toRaw, watch } from 'vue'; import { reactive, toRaw, watch } from 'vue';
import { Form, Modal, Upload, message } from 'ant-design-vue/lib'; import { Form, Modal, Upload, message, notification } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface'; import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import { FileType } from 'ant-design-vue/lib/upload/interface'; import { FileType, UploadFile } from 'ant-design-vue/lib/upload/interface';
import { import {
exportSet, exportNeConfigBackup,
importFile, importNeConfigBackup,
listServerFile, listNeConfigBackup,
} from '@/api/configManage/neManage'; } from '@/api/ne/neConfigBackup';
import saveAs from 'file-saver';
import { uploadFile } from '@/api/tool/file';
const { t } = useI18n(); const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']); const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({ const props = defineProps({
@@ -28,32 +30,49 @@ const props = defineProps({
}, },
}); });
/**表格所需option */ /**导入状态数据 */
const neManageOption = reactive({ const importState = reactive({
importType: [ typeOption: [
{ label: t('views.ne.neInfo.backConf.server'), value: 'server' }, { label: t('views.ne.neInfo.backConf.server'), value: 'backup' },
{ label: t('views.ne.neInfo.backConf.local'), value: 'local' }, { label: t('views.ne.neInfo.backConf.local'), value: 'upload' },
], ],
serverFileName: <any[]>[], backupData: <any[]>[],
}); });
/**查询网元远程服务器备份文件 */ /**查询网元远程服务器备份文件 */
function typeChange(value: any) { function backupSearch(name?: string) {
if (value === 'server') { const { neType, neId } = modalState.from;
modalState.from.fileName = undefined; listNeConfigBackup({
listServerFile(modalState.from).then(res => { neType,
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { neId,
neManageOption.serverFileName = []; name,
res.data.forEach((item: any) => { pageNum: 1,
neManageOption.serverFileName.push({ pageSize: 20,
label: item.fileName, }).then(res => {
value: item.fileName, if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
importState.backupData = [];
res.rows.forEach((item: any) => {
importState.backupData.push({
label: item.name,
value: item.path,
}); });
}); });
} }
}); });
} else { }
modalState.from.file = null;
/**服务器备份文件选择切换 */
function backupChange(value: any) {
if (!value) {
backupSearch();
}
}
/**类型切换 */
function typeChange(value: any) {
modalState.from.path = undefined;
if (value === 'backup') {
backupSearch();
} }
} }
@@ -67,9 +86,8 @@ type ModalStateType = {
from: { from: {
neType: string; neType: string;
neId: string; neId: string;
importType: 'local' | 'server'; type: 'upload' | 'backup';
file: File | null; path: string | undefined;
fileName: string | undefined;
}; };
/**确定按钮 loading */ /**确定按钮 loading */
confirmLoading: boolean; confirmLoading: boolean;
@@ -84,9 +102,8 @@ let modalState: ModalStateType = reactive({
from: { from: {
neType: '', neType: '',
neId: '', neId: '',
importType: 'local', type: 'upload',
file: null, path: undefined,
fileName: undefined,
}, },
confirmLoading: false, confirmLoading: false,
uploadFiles: [], uploadFiles: [],
@@ -96,16 +113,10 @@ let modalState: ModalStateType = reactive({
const modalStateFrom = Form.useForm( const modalStateFrom = Form.useForm(
modalState.from, modalState.from,
reactive({ reactive({
file: [ path: [
{ {
required: true, required: true,
message: t('views.ne.neInfo.backConf.filePlease'), message: t('views.ne.neInfo.backConf.pathPlease'),
},
],
fileName: [
{
required: true,
message: t('views.ne.neInfo.backConf.fileNamePlease'),
}, },
], ],
}) })
@@ -117,21 +128,13 @@ const modalStateFrom = Form.useForm(
*/ */
function fnModalOk() { function fnModalOk() {
if (modalState.confirmLoading) return; if (modalState.confirmLoading) return;
const from = toRaw(modalState.from); const from = toRaw(modalState.from);
let validateName = ['importType'];
if (from.importType === 'local') {
validateName.push('file');
} else {
validateName.push('fileName');
}
modalStateFrom modalStateFrom
.validate(validateName) .validate()
.then(e => { .then(e => {
modalState.confirmLoading = true; modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0); const hide = message.loading(t('common.loading'), 0);
importFile(from) importNeConfigBackup(from)
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3); message.success(t('common.operateOk'), 3);
@@ -168,6 +171,12 @@ function fnModalCancel() {
emit('update:visible', false); emit('update:visible', false);
} }
/**表单上传前删除 */
function fnBeforeRemoveFile(file: UploadFile) {
modalState.from.path = undefined;
return true;
}
/**表单上传前检查或转换压缩 */ /**表单上传前检查或转换压缩 */
function fnBeforeUploadFile(file: FileType) { function fnBeforeUploadFile(file: FileType) {
if (modalState.confirmLoading) return false; if (modalState.confirmLoading) return false;
@@ -187,12 +196,30 @@ function fnBeforeUploadFile(file: FileType) {
/**表单上传文件 */ /**表单上传文件 */
function fnUploadFile(up: UploadRequestOption) { function fnUploadFile(up: UploadRequestOption) {
// 发送请求
const hide = message.loading(t('common.loading'), 0);
modalState.confirmLoading = true;
let formData = new FormData();
formData.append('file', up.file);
formData.append('subPath', 'import');
uploadFile(formData)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 改为完成状态 // 改为完成状态
const file = modalState.uploadFiles[0]; const file = modalState.uploadFiles[0];
file.percent = 100; file.percent = 100;
file.status = 'done'; file.status = 'done';
// 预置到表单 // 预置到表单
modalState.from.file = up.file as File; const { fileName } = res.data;
modalState.from.path = fileName;
} else {
message.error(res.msg, 3);
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
} }
/**监听是否显示,初始数据 */ /**监听是否显示,初始数据 */
@@ -220,10 +247,17 @@ function fnExportConf(neType: string, neId: string) {
content: t('views.ne.neInfo.backConf.exportTip'), content: t('views.ne.neInfo.backConf.exportTip'),
onOk() { onOk() {
const hide = message.loading(t('common.loading'), 0); const hide = message.loading(t('common.loading'), 0);
exportSet({ neType, neId }) exportNeConfigBackup({ neType, neId })
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('views.ne.neInfo.backConf.exportMsg'), 3); notification.success({
message: t('common.tipTitle'),
description: t('views.ne.neInfo.backConf.exportMsg'),
});
saveAs(
res.data,
`${neType}_${neId}_config_backup_${Date.now()}.zip`
);
} else { } else {
message.error(`${res.msg}`, 3); message.error(`${res.msg}`, 3);
} }
@@ -258,44 +292,44 @@ defineExpose({
<a-form name="modalStateFrom" layout="horizontal" :label-col="{ span: 6 }"> <a-form name="modalStateFrom" layout="horizontal" :label-col="{ span: 6 }">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item <a-form-item :label="t('views.ne.common.neType')" name="neType">
:label="t('views.ne.common.neType')"
name="neType"
v-bind="modalStateFrom.validateInfos.neType"
>
{{ modalState.from.neType }} {{ modalState.from.neType }}
</a-form-item> </a-form-item>
<a-form-item <a-form-item
:label="t('views.ne.neInfo.backConf.importType')" :label="t('views.ne.neInfo.backConf.importType')"
name="importType" name="type"
> >
<a-select <a-select
v-model:value="modalState.from.importType" v-model:value="modalState.from.type"
default-value="server" default-value="server"
:options="neManageOption.importType" :options="importState.typeOption"
@change="typeChange" @change="typeChange"
> >
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item <a-form-item :label="t('views.ne.common.neId')" name="neId">
:label="t('views.ne.common.neId')"
name="neId"
v-bind="modalStateFrom.validateInfos.neId"
>
{{ modalState.from.neId }} {{ modalState.from.neId }}
</a-form-item> </a-form-item>
<a-form-item <a-form-item
:label="t('views.ne.neInfo.backConf.server')" :label="t('views.ne.neInfo.backConf.server')"
name="fileName" name="fileName"
v-bind="modalStateFrom.validateInfos.fileName" v-bind="modalStateFrom.validateInfos.path"
v-if="modalState.from.importType === 'server'" v-if="modalState.from.type === 'backup'"
> >
<a-select <a-select
v-model:value="modalState.from.fileName" v-model:value="modalState.from.path"
:options="neManageOption.serverFileName" :options="importState.backupData"
:placeholder="t('common.selectPlease')" :placeholder="t('common.selectPlease')"
:show-search="true"
:default-active-first-option="false"
:show-arrow="false"
:allow-clear="true"
:filter-option="false"
:not-found-content="null"
@search="backupSearch"
@change="backupChange"
> >
</a-select> </a-select>
</a-form-item> </a-form-item>
@@ -303,8 +337,8 @@ defineExpose({
<a-form-item <a-form-item
:label="t('views.ne.neInfo.backConf.local')" :label="t('views.ne.neInfo.backConf.local')"
name="file" name="file"
v-bind="modalStateFrom.validateInfos.file" v-bind="modalStateFrom.validateInfos.path"
v-if="modalState.from.importType === 'local'" v-if="modalState.from.type === 'upload'"
> >
<a-upload <a-upload
name="file" name="file"
@@ -314,9 +348,10 @@ defineExpose({
:max-count="1" :max-count="1"
:show-upload-list="{ :show-upload-list="{
showPreviewIcon: false, showPreviewIcon: false,
showRemoveIcon: false, showRemoveIcon: true,
showDownloadIcon: false, showDownloadIcon: false,
}" }"
:remove="fnBeforeRemoveFile"
:before-upload="fnBeforeUploadFile" :before-upload="fnBeforeUploadFile"
:custom-request="fnUploadFile" :custom-request="fnUploadFile"
:disabled="modalState.confirmLoading" :disabled="modalState.confirmLoading"

View File

@@ -124,8 +124,8 @@ let modalState: ModalStateType = reactive({
addr: '', addr: '',
port: 22, port: 22,
user: 'omcuser', user: 'omcuser',
authMode: '2', authMode: '0',
password: '', password: 'a9tU53r',
privateKey: '', privateKey: '',
passPhrase: '', passPhrase: '',
remark: '', remark: '',
@@ -281,9 +281,7 @@ function fnModalCancel() {
emit('update:visible', false); emit('update:visible', false);
} }
/** /**表单修改网元类型 */
* 表单修改网元类型
*/
function fnNeTypeChange(v: any) { function fnNeTypeChange(v: any) {
const hostsLen = modalState.from.hosts.length; const hostsLen = modalState.from.hosts.length;
// 网元默认只含22和4100 // 网元默认只含22和4100
@@ -305,11 +303,18 @@ function fnNeTypeChange(v: any) {
remark: '', remark: '',
}); });
} }
modalState.from.rmUid = `4400HX${v}${modalState.from.neId}`; // 4400HX1AMF001
} }
/** /**表单修改网元neId */
* 表单修改网元IP function fnNeIdChange(e: any) {
*/ const v = e.target.value;
if (v.length < 1) return;
modalState.from.rmUid = `4400HX${modalState.from.neType}${v}`; // 4400HX1AMF001
}
/**表单修改网元IP */
function fnNeIPChange(e: any) { function fnNeIPChange(e: any) {
const v = e.target.value; const v = e.target.value;
if (v.length < 7) return; if (v.length < 7) return;
@@ -428,6 +433,7 @@ onMounted(() => {
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="32" :maxlength="32"
@change="fnNeIdChange"
> >
<template #prefix> <template #prefix>
<a-tooltip placement="topLeft"> <a-tooltip placement="topLeft">

View File

@@ -42,6 +42,7 @@ let modalState: ModalStateType = reactive({
title: 'OAM Configuration', title: 'OAM Configuration',
sync: true, sync: true,
from: { from: {
omcIP: '',
oamEnable: true, oamEnable: true,
oamPort: 33030, oamPort: 33030,
snmpEnable: true, snmpEnable: true,
@@ -77,6 +78,7 @@ function fnModalVisibleByTypeAndId(neType: string, neId: string) {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
const data = res.data; const data = res.data;
Object.assign(modalState.from, { Object.assign(modalState.from, {
omcIP: data.oamConfig[data.oamConfig.ipType],
oamEnable: data.oamConfig.enable, oamEnable: data.oamConfig.enable,
oamPort: data.oamConfig.port, oamPort: data.oamConfig.port,
snmpEnable: data.snmpConfig.enable, snmpEnable: data.snmpConfig.enable,
@@ -224,6 +226,17 @@ watch(
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<a-form-item
:label="t('views.ne.neInfo.oam.omcIP')"
name="omcIP"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-input
v-model:value="modalState.from.omcIP"
:maxlength="128"
></a-input>
</a-form-item>
</a-collapse-panel> </a-collapse-panel>
<a-collapse-panel header="SNMP"> <a-collapse-panel header="SNMP">
<a-row :gutter="16"> <a-row :gutter="16">

View File

@@ -233,9 +233,13 @@ function fnModalEditOk(from: Record<string, any>) {
if (item && res.code === RESULT_CODE_SUCCESS) { if (item && res.code === RESULT_CODE_SUCCESS) {
item.neType = from.neType; item.neType = from.neType;
item.neId = from.neId; item.neId = from.neId;
item.ip = from.ip; item.rmUid = from.rmUid;
item.neName = from.neName; item.neName = from.neName;
item.ip = from.ip;
item.port = from.port;
if (item.status !== '2') {
item.status = res.data.online ? '1' : '0'; item.status = res.data.online ? '1' : '0';
}
Object.assign(item.serverState, res.data); Object.assign(item.serverState, res.data);
const resouresUsage = parseResouresUsage(item.serverState); const resouresUsage = parseResouresUsage(item.serverState);
Reflect.set(item, 'resoures', resouresUsage); Reflect.set(item, 'resoures', resouresUsage);
@@ -281,10 +285,10 @@ function fnRecordDelete(id: string) {
message.success(t('common.operateOk'), 3); message.success(t('common.operateOk'), 3);
// 过滤掉删除的id // 过滤掉删除的id
tableState.data = tableState.data.filter(item => { tableState.data = tableState.data.filter(item => {
if (id.indexOf(',')) { if (id.indexOf(',') > -1) {
return !tableState.selectedRowKeys.includes(item.id); return !tableState.selectedRowKeys.includes(item.id);
} else { } else {
return item.id != id; return item.id !== id;
} }
}); });
// 刷新缓存 // 刷新缓存
@@ -362,8 +366,18 @@ function fnGetList(pageNum?: number) {
tablePagination.total = res.total; tablePagination.total = res.total;
// 遍历处理资源情况数值 // 遍历处理资源情况数值
tableState.data = res.rows.map(item => { tableState.data = res.rows.map(item => {
let resouresUsage = {
sysDiskUsage: 0,
sysMemUsage: 0,
sysCpuUsage: 0,
nfCpuUsage: 0,
};
const neState = item.serverState; const neState = item.serverState;
const resouresUsage = parseResouresUsage(neState); if (neState) {
resouresUsage = parseResouresUsage(neState);
} else {
item.serverState = { online: false };
}
Reflect.set(item, 'resoures', resouresUsage); Reflect.set(item, 'resoures', resouresUsage);
return item; return item;
}); });
@@ -378,33 +392,27 @@ function parseResouresUsage(neState: Record<string, any>) {
let nfCpuUsage = 0; let nfCpuUsage = 0;
if (neState.cpu) { if (neState.cpu) {
nfCpuUsage = neState.cpu.nfCpuUsage; nfCpuUsage = neState.cpu.nfCpuUsage;
if (nfCpuUsage > 100) { const nfCpu = +(nfCpuUsage / 100);
const nfCpu = +(neState.cpu.nfCpuUsage / 100);
if (nfCpu > 100) {
nfCpuUsage = 100;
} else {
nfCpuUsage = +nfCpu.toFixed(2); nfCpuUsage = +nfCpu.toFixed(2);
} if (nfCpuUsage > 100) {
nfCpuUsage = 100;
} }
sysCpuUsage = neState.cpu.sysCpuUsage; sysCpuUsage = neState.cpu.sysCpuUsage;
if (sysCpuUsage > 100) { const sysCpu = +(sysCpuUsage / 100);
const sysCpu = +(neState.cpu.sysCpuUsage / 100);
if (sysCpu > 100) {
sysCpuUsage = 100;
} else {
sysCpuUsage = +sysCpu.toFixed(2); sysCpuUsage = +sysCpu.toFixed(2);
} if (sysCpuUsage > 100) {
sysCpuUsage = 100;
} }
} }
let sysMemUsage = 0; let sysMemUsage = 0;
if (neState.mem) { if (neState.mem) {
let men = neState.mem.sysMemUsage; const men = neState.mem.sysMemUsage;
if (men > 100) { sysMemUsage = +(men / 100).toFixed(2);
men = +(men / 100).toFixed(2); if (sysMemUsage > 100) {
sysMemUsage = 100;
} }
sysMemUsage = men;
} }
let sysDiskUsage = 0; let sysDiskUsage = 0;
@@ -556,7 +564,7 @@ onMounted(() => {
:data-source="tableState.data" :data-source="tableState.data"
:size="tableState.size" :size="tableState.size"
:pagination="tablePagination" :pagination="tablePagination"
:scroll="{ x: tableColumns.length * 150 }" :scroll="{ x: tableColumns.length * 120 }"
:row-selection="{ :row-selection="{
type: 'checkbox', type: 'checkbox',
columnWidth: '48px', columnWidth: '48px',
@@ -623,7 +631,10 @@ onMounted(() => {
<DeleteOutlined /> <DeleteOutlined />
{{ t('common.deleteText') }} {{ t('common.deleteText') }}
</a-menu-item> </a-menu-item>
<a-menu-item key="oam"> <a-menu-item
key="oam"
v-if="!['OMC'].includes(record.neType)"
>
<FileTextOutlined /> <FileTextOutlined />
{{ t('views.ne.common.oam') }} {{ t('views.ne.common.oam') }}
</a-menu-item> </a-menu-item>

View File

@@ -157,11 +157,7 @@ type StateType = {
optionType: 'upload' | 'option'; optionType: 'upload' | 'option';
/**文件上传 */ /**文件上传 */
visibleByFile: boolean; visibleByFile: boolean;
/** /**网元拓展包列表类型 */
* 依赖包类型
* IMS-rtproxy/mf/adb
* UDM-adb
*/
depType: string[]; depType: string[];
/**软件包信息数据 */ /**软件包信息数据 */
from: { from: {

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted, watch } from 'vue'; import { ref, watch } from 'vue';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
const { t } = useI18n(); const { t } = useI18n();
const emit = defineEmits(['update:data']); const emit = defineEmits(['update:data']);
@@ -21,22 +21,8 @@ const props = defineProps({
}, },
}); });
/**对话框对象信息状态类型 */ /**表单信息状态 */
type StateType = { let fromState = ref({
/**表单数据 */
from: Record<string, any>;
/**根据网元显示配置项 */
hasNE: {
amf: boolean;
upf: boolean;
ims: boolean;
mme: boolean;
};
};
/**对话框对象信息状态 */
let state: StateType = reactive({
from: {
basic: { basic: {
plmnId: { plmnId: {
mcc: '001', mcc: '001',
@@ -74,7 +60,7 @@ let state: StateType = reactive({
amf_ip: '172.16.5.120', amf_ip: '172.16.5.120',
ausf_ip: '172.16.5.130', ausf_ip: '172.16.5.130',
udm_ip: '172.16.5.140', udm_ip: '172.16.5.140',
adb_ip: '0.0.0.0', db_ip: '0.0.0.0',
smf_ip: '172.16.5.150', smf_ip: '172.16.5.150',
pcf_ip: '172.16.5.160', pcf_ip: '172.16.5.160',
nssf_ip: '172.16.5.170', nssf_ip: '172.16.5.170',
@@ -85,45 +71,16 @@ let state: StateType = reactive({
mme_ip: '172.16.5.220', mme_ip: '172.16.5.220',
n3iwf_ip: '172.16.5.230', n3iwf_ip: '172.16.5.230',
}, },
},
hasNE: {
amf: false,
upf: false,
ims: false,
mme: false,
},
}); });
/**监听数据 */ /**监听数据 */
watch( watch(
() => props.data, () => fromState,
val => {
if (val) {
Object.assign(state.from, val);
}
},
{ deep: true }
);
watch(
() => props.ne,
val => {
if (val) {
Object.assign(state.hasNE, val);
}
},
{ deep: true }
);
watch(
() => state.from,
val => { val => {
if (val) emit('update:data', val); if (val) emit('update:data', val);
}, },
{ deep: true, immediate: true } { deep: true, immediate: true }
); );
onMounted(() => {});
</script> </script>
<template> <template>
@@ -140,7 +97,7 @@ onMounted(() => {});
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item label="DNN_DATA" name="basic.dnn_data"> <a-form-item label="DNN_DATA" name="basic.dnn_data">
<a-input <a-input
v-model:value="state.from.basic.dnn_data" v-model:value="fromState.basic.dnn_data"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -155,13 +112,13 @@ onMounted(() => {});
</a-form-item> </a-form-item>
<a-form-item label="MCC" name="basic.plmnId.mcc"> <a-form-item label="MCC" name="basic.plmnId.mcc">
<a-input <a-input
v-model:value="state.from.basic.plmnId.mcc" v-model:value="fromState.basic.plmnId.mcc"
placeholder="1-65535" placeholder="1-65535"
></a-input> ></a-input>
</a-form-item> </a-form-item>
<a-form-item label="SST" name="basic.snssai.sst"> <a-form-item label="SST" name="basic.snssai.sst">
<a-input-number <a-input-number
v-model:value="state.from.basic.snssai.sst" v-model:value="fromState.basic.snssai.sst"
:min="1" :min="1"
:max="3" :max="3"
placeholder="1-3" placeholder="1-3"
@@ -177,7 +134,7 @@ onMounted(() => {});
</a-form-item> </a-form-item>
<a-form-item label="TAC" name="basic.tac"> <a-form-item label="TAC" name="basic.tac">
<a-input <a-input
v-model:value="state.from.basic.tac" v-model:value="fromState.basic.tac"
placeholder="1-65535" placeholder="1-65535"
></a-input> ></a-input>
</a-form-item> </a-form-item>
@@ -185,7 +142,7 @@ onMounted(() => {});
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item label="DNN_IMS" name="basic.dnn_ims"> <a-form-item label="DNN_IMS" name="basic.dnn_ims">
<a-input <a-input
v-model:value="state.from.basic.dnn_ims" v-model:value="fromState.basic.dnn_ims"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -194,13 +151,13 @@ onMounted(() => {});
</a-form-item> </a-form-item>
<a-form-item label="MNC" name="basic.plmnId.mnc"> <a-form-item label="MNC" name="basic.plmnId.mnc">
<a-input <a-input
v-model:value="state.from.basic.plmnId.mnc" v-model:value="fromState.basic.plmnId.mnc"
placeholder="1-65535" placeholder="1-65535"
></a-input> ></a-input>
</a-form-item> </a-form-item>
<a-form-item label="SD" name="basic.snssai.sd"> <a-form-item label="SD" name="basic.snssai.sd">
<a-input <a-input
v-model:value="state.from.basic.snssai.sd" v-model:value="fromState.basic.snssai.sd"
placeholder="1-65535" placeholder="1-65535"
></a-input> ></a-input>
</a-form-item> </a-form-item>
@@ -220,7 +177,7 @@ onMounted(() => {});
:validateTrigger="[]" :validateTrigger="[]"
> >
<a-input <a-input
v-model:value="state.from.sbi.omc_ip" v-model:value="fromState.sbi.omc_ip"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -237,14 +194,13 @@ onMounted(() => {});
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<template v-if="props.ne.amf">
<template v-if="state.hasNE.amf">
<a-divider orientation="left">AMF</a-divider> <a-divider orientation="left">AMF</a-divider>
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="24" :md="24" :xs="24"> <a-col :lg="24" :md="24" :xs="24">
<a-form-item label="N2_IP" name="external.amfn2_ip"> <a-form-item label="N2_IP" name="external.amfn2_ip">
<a-input <a-input
v-model:value="state.from.external.amfn2_ip" v-model:value="fromState.external.amfn2_ip"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -262,7 +218,7 @@ onMounted(() => {});
</template> </template>
</a-col> </a-col>
<a-col :lg="16" :md="16" :xs="24" v-if="state.hasNE.upf"> <a-col :lg="16" :md="16" :xs="24" v-if="props.ne.upf">
<a-divider orientation="left">UPF</a-divider> <a-divider orientation="left">UPF</a-divider>
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
@@ -272,7 +228,7 @@ onMounted(() => {});
help="Install of Standard or Light" help="Install of Standard or Light"
> >
<a-select <a-select
v-model:value="state.from.external.upf_type" v-model:value="fromState.external.upf_type"
:placeholder="t('common.selectPlease')" :placeholder="t('common.selectPlease')"
> >
<a-select-option value="StandardUPF"> <a-select-option value="StandardUPF">
@@ -285,7 +241,7 @@ onMounted(() => {});
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item label="UE_POOL" name="external.ue_pool"> <a-form-item label="UE_POOL" name="external.ue_pool">
<a-input <a-input
v-model:value="state.from.external.ue_pool" v-model:value="fromState.external.ue_pool"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -305,7 +261,7 @@ onMounted(() => {});
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N3_IP" name="external.upfn3_ip"> <a-form-item label="N3_IP" name="external.upfn3_ip">
<a-input <a-input
v-model:value="state.from.external.upfn3_ip" v-model:value="fromState.external.upfn3_ip"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -320,7 +276,7 @@ onMounted(() => {});
</a-form-item> </a-form-item>
<a-form-item label="N3_GW" name="external.upfn3_gw"> <a-form-item label="N3_GW" name="external.upfn3_gw">
<a-input <a-input
v-model:value="state.from.external.upfn3_gw" v-model:value="fromState.external.upfn3_gw"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -338,11 +294,11 @@ onMounted(() => {});
:lg="12" :lg="12"
:md="12" :md="12"
:xs="24" :xs="24"
v-if="state.from.external.upf_type === 'StandardUPF'" v-if="fromState.external.upf_type === 'StandardUPF'"
> >
<a-form-item label="N3_PCI" name="external.upfn3_pci"> <a-form-item label="N3_PCI" name="external.upfn3_pci">
<a-input <a-input
v-model:value="state.from.external.upfn3_pci" v-model:value="fromState.external.upfn3_pci"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -359,7 +315,7 @@ onMounted(() => {});
</a-form-item> </a-form-item>
<a-form-item label="N3_MAC" name="external.upfn3_mac"> <a-form-item label="N3_MAC" name="external.upfn3_mac">
<a-input <a-input
v-model:value="state.from.external.upfn3_mac" v-model:value="fromState.external.upfn3_mac"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -376,12 +332,12 @@ onMounted(() => {});
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<template v-if="state.from.external.upf_type === 'StandardUPF'"> <template v-if="fromState.external.upf_type === 'StandardUPF'">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N6_IP" name="external.upfn6_ip"> <a-form-item label="N6_IP" name="external.upfn6_ip">
<a-input <a-input
v-model:value="state.from.external.upfn6_ip" v-model:value="fromState.external.upfn6_ip"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -390,7 +346,7 @@ onMounted(() => {});
</a-form-item> </a-form-item>
<a-form-item label="N6_GW" name="external.upfn6_gw"> <a-form-item label="N6_GW" name="external.upfn6_gw">
<a-input <a-input
v-model:value="state.from.external.upfn6_gw" v-model:value="fromState.external.upfn6_gw"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -401,7 +357,7 @@ onMounted(() => {});
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N6_PCI" name="external.upfn6_pci"> <a-form-item label="N6_PCI" name="external.upfn6_pci">
<a-input <a-input
v-model:value="state.from.external.upfn6_pci" v-model:value="fromState.external.upfn6_pci"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -410,7 +366,7 @@ onMounted(() => {});
</a-form-item> </a-form-item>
<a-form-item label="N6_MAC" name="external.upfn6_mac"> <a-form-item label="N6_MAC" name="external.upfn6_mac">
<a-input <a-input
v-model:value="state.from.external.upfn6_mac" v-model:value="fromState.external.upfn6_mac"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -423,13 +379,13 @@ onMounted(() => {});
</a-col> </a-col>
<a-col :lg="8" :md="8" :xs="24"> <a-col :lg="8" :md="8" :xs="24">
<template v-if="state.hasNE.ims"> <template v-if="props.ne.ims">
<a-divider orientation="left">IMS</a-divider> <a-divider orientation="left">IMS</a-divider>
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="24" :md="24" :xs="24"> <a-col :lg="24" :md="24" :xs="24">
<a-form-item label="SIP_IP" name="external.ims_sip_ip"> <a-form-item label="SIP_IP" name="external.ims_sip_ip">
<a-input <a-input
v-model:value="state.from.external.ims_sip_ip" v-model:value="fromState.external.ims_sip_ip"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -446,13 +402,13 @@ onMounted(() => {});
</a-row> </a-row>
</template> </template>
<template v-if="state.hasNE.mme"> <template v-if="props.ne.mme">
<a-divider orientation="left">MME</a-divider> <a-divider orientation="left">MME</a-divider>
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="24" :md="24" :xs="24"> <a-col :lg="24" :md="24" :xs="24">
<a-form-item label="S1_IP" name="external.mmes1_ip"> <a-form-item label="S1_IP" name="external.mmes1_ip">
<a-input <a-input
v-model:value="state.from.external.mmes1_ip" v-model:value="fromState.external.mmes1_ip"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -471,7 +427,7 @@ onMounted(() => {});
</a-form-item> </a-form-item>
<a-form-item label="S10_IP" name="external.mmes10_ip"> <a-form-item label="S10_IP" name="external.mmes10_ip">
<a-input <a-input
v-model:value="state.from.external.mmes10_ip" v-model:value="fromState.external.mmes10_ip"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -486,7 +442,7 @@ onMounted(() => {});
</a-form-item> </a-form-item>
<a-form-item label="S11_IP" name="external.mmes11_ip"> <a-form-item label="S11_IP" name="external.mmes11_ip">
<a-input <a-input
v-model:value="state.from.external.mmes11_ip" v-model:value="fromState.external.mmes11_ip"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"

View File

@@ -231,6 +231,17 @@ function fnHostAuthorized() {
}); });
} }
/**返回上一步 */
function fnStepPrev() {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neQuickSetup.stepPrevTip'),
onOk() {
fnToStepName('Para5G');
},
});
}
/**下一步操作 */ /**下一步操作 */
function fnStepNext() { function fnStepNext() {
if (!state.stepNext) return; if (!state.stepNext) return;
@@ -254,6 +265,7 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="ne">
<a-descriptions :column="{ lg: 3, md: 2, sm: 2, xs: 1 }" bordered> <a-descriptions :column="{ lg: 3, md: 2, sm: 2, xs: 1 }" bordered>
<a-descriptions-item :label="t('views.ne.neQuickSetup.addr')" :span="3"> <a-descriptions-item :label="t('views.ne.neQuickSetup.addr')" :span="3">
{{ state.info.addr }} {{ state.info.addr }}
@@ -299,14 +311,16 @@ onMounted(() => {
{{ t('views.ne.neQuickSetup.sshLink') }} {{ t('views.ne.neQuickSetup.sshLink') }}
</a-tag> </a-tag>
</a-descriptions-item> </a-descriptions-item>
<a-descriptions-item :span="2"> </a-descriptions>
<a-form <a-form
name="checkStateFrom" name="checkStateFrom"
layout="horizontal" layout="horizontal"
:label-col="{ span: 6 }" :label-col="{ span: 6 }"
:label-wrap="false" :label-wrap="true"
style="margin-top: 20px; width: 68%"
> >
<a-row :gutter="16"> <a-row :gutter="8">
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.ne.neHost.addr')" :label="t('views.ne.neHost.addr')"
@@ -415,7 +429,7 @@ onMounted(() => {
</a-form-item> </a-form-item>
</template> </template>
<a-form-item :wrapper-col="{ span: 10, offset: 3 }"> <a-form-item :wrapper-col="{ span: 8, offset: 3 }">
<a-space direction="horizontal" :size="18"> <a-space direction="horizontal" :size="18">
<a-button <a-button
type="primary" type="primary"
@@ -426,13 +440,6 @@ onMounted(() => {
> >
{{ t('views.ne.neHost.test') }} {{ t('views.ne.neHost.test') }}
</a-button> </a-button>
<a-button
type="primary"
@click="fnStepNext()"
:disabled="!state.stepNext"
>
{{ t('views.ne.neQuickSetup.stepNext') }}
</a-button>
<a-button <a-button
type="dashed" type="dashed"
@@ -453,8 +460,37 @@ onMounted(() => {
</a-space> </a-space>
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-descriptions-item>
</a-descriptions> <div class="ne-oper">
<a-space direction="horizontal" :size="18">
<a-button @click="fnStepPrev()">
{{ t('views.ne.neQuickSetup.stepPrev') }}
</a-button>
<a-button
type="primary"
@click="fnStepNext()"
:disabled="!state.stepNext"
>
{{ t('views.ne.neQuickSetup.stepNext') }}
</a-button>
</a-space>
</div>
</div>
</template> </template>
<style lang="less" scoped></style> <style lang="less" scoped>
.ne {
min-height: 400px;
display: flex;
flex-direction: column;
& .ant-form {
flex: 1;
}
&-oper {
text-align: end;
}
}
</style>

View File

@@ -0,0 +1,137 @@
import { reactive, toRaw } from 'vue';
import { getPara5GFilee, savePara5GFile, updateNeInfo } from '@/api/ne/neInfo';
import useNeInfoStore from '@/store/modules/neinfo';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
/**对象信息信息状态类型 */
type StateType = {
/**表单数据 */
from: Record<string, any>;
/**OMC信息需修改当前的IP */
omcInfo: Record<string, any>;
/**根据网元显示配置项 */
hasNE: {
amf: boolean;
upf: boolean;
ims: boolean;
mme: boolean;
};
/**确定按钮 loading */
confirmLoading: boolean;
};
export function usePara5G() {
/**对象信息状态 */
let state: StateType = reactive({
from: {},
omcInfo: {},
hasNE: {
amf: false,
upf: false,
ims: false,
mme: false,
},
confirmLoading: false,
});
/**载入数据*/
function fnReloadData() {
state.confirmLoading = true;
Promise.all([getPara5GFilee(), useNeInfoStore().fnRefreshNelist()]).then(
resArr => {
// 已保存的配置
if (resArr[0].code === RESULT_CODE_SUCCESS) {
Object.assign(state.from, resArr[0].data);
}
// 填充固定网元类型的ip
if (
resArr[1].code === RESULT_CODE_SUCCESS &&
Array.isArray(resArr[1].data)
) {
for (const item of resArr[1].data) {
// 公共配置文件sbi的各网元IP
switch (item.neType) {
case 'OMC':
state.from.sbi.omc_ip = item.ip;
Object.assign(state.omcInfo, item); // 主动改OMC_IP
break;
case 'IMS':
state.from.sbi.ims_ip = item.ip;
state.hasNE.ims = true;
break;
case 'AMF':
state.from.sbi.amf_ip = item.ip;
state.hasNE.amf = true;
break;
case 'AUSF':
state.from.sbi.ausf_ip = item.ip;
break;
case 'UDM':
state.from.sbi.udm_ip = item.ip;
state.from.sbi.db_ip = '0.0.0.0';
break;
case 'SMF':
state.from.sbi.smf_ip = item.ip;
break;
case 'PCF':
state.from.sbi.pcf_ip = item.ip;
break;
case 'NSSF':
state.from.sbi.nssf_ip = item.ip;
break;
case 'NRF':
state.from.sbi.nrf_ip = item.ip;
break;
case 'UPF':
state.from.sbi.upf_ip = item.ip;
state.hasNE.upf = true;
break;
case 'LMF':
state.from.sbi.lmf_ip = item.ip;
break;
case 'NEF':
state.from.sbi.nef_ip = item.ip;
break;
case 'MME':
state.from.sbi.mme_ip = item.ip;
if (item.ip.includes('.')) {
state.from.external.mmes11_ip = item.ip + '/24';
}
state.hasNE.mme = true;
break;
case 'N3IWF':
state.from.sbi.n3iwf_ip = item.ip;
break;
}
}
}
state.confirmLoading = false;
}
);
}
/**保存数据 */
async function fnSaveData() {
if (state.confirmLoading) return;
state.confirmLoading = true;
const res = await savePara5GFile({
content: toRaw(state.from),
syncNe: [],
});
if (res.code === RESULT_CODE_SUCCESS) {
// 更新omc_ip
if (state.omcInfo.id) {
state.omcInfo.ip = state.from.sbi.omc_ip;
await updateNeInfo(toRaw(state.omcInfo));
}
}
state.confirmLoading = false;
return res;
}
return {
state,
fnReloadData,
fnSaveData,
};
}

View File

@@ -1,4 +1,10 @@
import { reactive } from 'vue'; import {
defineAsyncComponent,
onMounted,
reactive,
shallowRef,
watch,
} from 'vue';
/**步骤信息状态类型 */ /**步骤信息状态类型 */
type StepStateType = { type StepStateType = {
@@ -16,7 +22,7 @@ type StepStateType = {
/**步骤信息状态 */ /**步骤信息状态 */
export const stepState: StepStateType = reactive({ export const stepState: StepStateType = reactive({
stepName: 'Start', stepName: 'Para5G',
steps: [ steps: [
{ {
title: '服务器环境', title: '服务器环境',
@@ -35,15 +41,15 @@ export const stepState: StepStateType = reactive({
description: '网元服务授权激活', description: '网元服务授权激活',
}, },
], ],
current: 0, current: -1,
neHost: {}, neHost: {},
neInfo: {}, neInfo: {},
}); });
/**步骤信息状态复位 */ /**步骤信息状态复位 */
export function fnRestStepState(t?: any) { export function fnRestStepState(t?: any) {
stepState.stepName = 'Start'; stepState.stepName = 'Para5G';
stepState.current = 0; stepState.current = -1;
stepState.neHost = {}; stepState.neHost = {};
stepState.neInfo = {}; stepState.neInfo = {};
// 多语言翻译 // 多语言翻译
@@ -80,3 +86,26 @@ export function fnToStepName(stepName: string) {
stepState.stepName = stepName; stepState.stepName = stepName;
} }
export function useStep({ t }: any) {
// 异步加载组件
const Start = defineAsyncComponent(() => import('../components/Start.vue'));
// 当前组件
const currentComponent = shallowRef(Start);
watch(
() => stepState.stepName,
v => {
const loadComponent = defineAsyncComponent(
() => import(`../components/${v}.vue`)
);
currentComponent.value = loadComponent;
}
);
onMounted(() => {
fnRestStepState(t);
});
return { currentComponent };
}

View File

@@ -1,34 +1,71 @@
<script lang="ts" setup> <script lang="ts" setup>
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import { defineAsyncComponent, watch, shallowRef, onMounted } from 'vue'; import Para5GForm from './components/Para5GForm.vue';
import { stepState, fnRestStepState } from './hooks/useStep'; import {
stepState,
fnToStepName,
fnRestStepState,
useStep,
} from './hooks/useStep';
import { usePara5G } from './hooks/usePara5G';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { onMounted, onUnmounted, watch } from 'vue';
const { t } = useI18n(); const { t } = useI18n();
const { currentComponent } = useStep(t);
// 异步加载组件 const { state, fnReloadData, fnSaveData } = usePara5G();
const Start = defineAsyncComponent(() => import('./components/Start.vue'));
// 当前组件
const currentComponent = shallowRef(Start);
watch( watch(
() => stepState.stepName, () => stepState.stepName,
v => { v => {
const loadComponent = defineAsyncComponent( if (v === 'Para5G') {
() => import(`./components/${v}.vue`) fnReloadData();
); }
currentComponent.value = loadComponent;
} }
); );
onMounted(() => { onMounted(() => {
fnReloadData();
});
onUnmounted(() => {
fnRestStepState(t); fnRestStepState(t);
}); });
/**公共参数保存前下一步进行网元安装 */
function fnNext() {
fnSaveData().then(() => {
fnToStepName('Start');
});
}
</script> </script>
<template> <template>
<PageContainer> <PageContainer>
<a-card :bordered="false"> <a-card :bordered="false" v-if="stepState.stepName === 'Para5G'">
<!-- 公共参数表单 -->
<Para5GForm v-model:data="state.from" :ne="state.hasNE"></Para5GForm>
<div style="padding: 24px 12px 0; text-align: end">
<a-space :size="8" align="center">
<a-button
type="default"
:disabled="state.confirmLoading"
@click.prevent="fnReloadData()"
>
<template #icon><ReloadOutlined /></template>
{{ t('views.ne.neQuickSetup.reloadPara5G') }}
</a-button>
<a-button
type="primary"
:loading="state.confirmLoading"
@click="fnNext()"
>
{{ t('views.ne.neQuickSetup.stepNext') }}
</a-button>
</a-space>
</div>
</a-card>
<a-card :bordered="false" v-else>
<!-- 插槽-卡片左侧 --> <!-- 插槽-卡片左侧 -->
<template #title> <template #title>
<!-- 步骤进度 --> <!-- 步骤进度 -->

View File

@@ -91,6 +91,9 @@ async function fnModalOk() {
if (expandFile['ADB']) { if (expandFile['ADB']) {
pkgArr.push(expandFile['ADB']); pkgArr.push(expandFile['ADB']);
} }
if (expandFile['KVDB']) {
pkgArr.push(expandFile['KVDB']);
}
if (expandFile['RTPROXY']) { if (expandFile['RTPROXY']) {
pkgArr.push(expandFile['RTPROXY']); pkgArr.push(expandFile['RTPROXY']);
} }
@@ -104,6 +107,8 @@ async function fnModalOk() {
const udm = from.find(s => s.neType === 'UDM'); const udm = from.find(s => s.neType === 'UDM');
if (udm && expandFile['ADB']) { if (udm && expandFile['ADB']) {
udm.path = [expandFile['ADB'], udm.path].join(','); udm.path = [expandFile['ADB'], udm.path].join(',');
} else if (udm && expandFile['KVDB']) {
udm.path = [expandFile['KVDB'], udm.path].join(',');
} }
// 安装带依赖包-指定网元时 // 安装带依赖包-指定网元时

View File

@@ -294,8 +294,7 @@ function fnRecordVersion(
return; return;
} }
if (row.newVersion === row.version) { if (row.newVersion === row.version) {
message.warning(t('views.ne.neVersion.upgradeTipEqual'), 3); contentTip = t('views.ne.neVersion.upgradeTipEqual');
return;
} }
} }
if (action === 'rollback') { if (action === 'rollback') {
@@ -305,8 +304,7 @@ function fnRecordVersion(
return; return;
} }
if (row.preVersion === row.version) { if (row.preVersion === row.version) {
message.warning(t('views.ne.neVersion.rollbackTipEqual'), 3); contentTip = t('views.ne.neVersion.rollbackTipEqual');
return;
} }
} }

View File

@@ -194,8 +194,6 @@ type ModalStateType = {
/**表单数据 */ /**表单数据 */
from: Record<string, any>; from: Record<string, any>;
/**表单数据 */ /**表单数据 */
BatchForm: Record<string, any>;
/**表单数据 */
BatchDelForm: Record<string, any>; BatchDelForm: Record<string, any>;
/**确定按钮 loading */ /**确定按钮 loading */
confirmLoading: boolean; confirmLoading: boolean;
@@ -210,15 +208,8 @@ let modalState: ModalStateType = reactive({
visibleByBatchDel: false, visibleByBatchDel: false,
title: 'UDM鉴权用户', title: 'UDM鉴权用户',
from: { from: {
id: '',
imsi: '',
amf: '8000',
ki: '',
algoIndex: 0,
opc: '',
},
BatchForm: {
num: 1, num: 1,
id: '',
imsi: '', imsi: '',
amf: '8000', amf: '8000',
ki: '', ki: '',
@@ -236,19 +227,6 @@ let modalState: ModalStateType = reactive({
/**对话框内表单属性和校验规则 */ /**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm( const modalStateFrom = Form.useForm(
modalState.from, modalState.from,
reactive({
imsi: [{ required: true, message: 'IMSI' + t('common.unableNull') }],
amf: [{ required: true, message: 'AMF' + t('common.unableNull') }],
ki: [{ required: true, message: 'KI' + t('common.unableNull') }],
algoIndex: [
{ required: true, message: 'algoIndex' + t('common.unableNull') },
],
})
);
/**对话框内批量添加表单属性和校验规则 */
const modalStateBatchFrom = Form.useForm(
modalState.BatchForm,
reactive({ reactive({
num: [ num: [
{ {
@@ -256,9 +234,15 @@ const modalStateBatchFrom = Form.useForm(
message: t('views.neUser.auth.numAdd') + t('common.unableNull'), message: t('views.neUser.auth.numAdd') + t('common.unableNull'),
}, },
], ],
imsi: [{ required: true, message: 'IMSI' + t('common.unableNull') }], imsi: [
{ required: true, message: 'IMSI' + t('common.unableNull') },
{ min: 15, max: 15, message: t('views.neUser.auth.imsiConfirm') },
],
amf: [{ required: true, message: 'AMF' + t('common.unableNull') }], amf: [{ required: true, message: 'AMF' + t('common.unableNull') }],
ki: [{ required: true, message: 'KI' + t('common.unableNull') }], ki: [
{ required: true, message: 'KI' + t('common.unableNull') },
{ min: 32, max: 32, message: t('views.neUser.auth.kiTip') },
],
algoIndex: [ algoIndex: [
{ required: true, message: 'algoIndex' + t('common.unableNull') }, { required: true, message: 'algoIndex' + t('common.unableNull') },
], ],
@@ -312,22 +296,15 @@ function fnModalVisibleByEdit(row?: Record<string, any>) {
} }
/** /**
* 对话框弹出显示为 批量新增,批量删除 * 对话框弹出显示为 批量删除
* @param noticeId 网元id, 不传为新增 * @param noticeId 网元id, 不传为新增
*/ */
function fnModalVisibleByBatch(batchFlag?: number) { function fnModalVisibleByBatch() {
if (batchFlag) { modalStateBatchDelFrom.resetFields(); //重置表单
modalStateBatchFrom.resetFields(); //重置表单
modalState.title =
t('views.neUser.auth.batchAddText') + t('views.neUser.auth.authInfo');
modalState.visibleByBatch = true;
} else {
modalStateBatchFrom.resetFields(); //重置表单
modalState.title = modalState.title =
t('views.neUser.auth.batchDelText') + t('views.neUser.auth.authInfo'); t('views.neUser.auth.batchDelText') + t('views.neUser.auth.authInfo');
modalState.visibleByBatchDel = true; modalState.visibleByBatchDel = true;
} }
}
/** /**
* 对话框弹出确认执行函数 * 对话框弹出确认执行函数
@@ -341,50 +318,24 @@ function fnModalOk() {
const from = toRaw(modalState.from); const from = toRaw(modalState.from);
from.neId = queryParams.neId || '-'; from.neId = queryParams.neId || '-';
from.algoIndex = `${from.algoIndex}`; from.algoIndex = `${from.algoIndex}`;
const result = from.id ? updateUDMAuth(from) : addUDMAuth(from); const result = from.id
? updateUDMAuth(from)
: from.num === 1
? addUDMAuth(from)
: batchAddUDMAuth(from, from.num);
const hide = message.loading(t('common.loading'), 0); const hide = message.loading(t('common.loading'), 0);
result result
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
if (from.num === 1) {
//新增时
message.success({ message.success({
content: t('common.msgSuccess', { msg: modalState.title }), content: t('common.msgSuccess', { msg: modalState.title }),
duration: 3, duration: 3,
}); });
modalState.visibleByEdit = false;
modalStateFrom.resetFields();
fnGetList(); fnGetList();
} else { } else {
message.error({ //批量新增时
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
})
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
});
}
/**
* 对话框弹出 批量新增操作确认执行函数
* 进行表达规则校验
*/
function fnBatchModalOk() {
modalStateBatchFrom
.validate()
.then(e => {
modalState.confirmLoading = true;
const from = toRaw(modalState.BatchForm);
from.neId = queryParams.neId || '-';
from.algoIndex = `${from.algoIndex}`;
const result = batchAddUDMAuth(from, from.num);
result.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
const timerS = Math.max( const timerS = Math.max(
Math.ceil(+from.num / 500), Math.ceil(+from.num / 500),
`${from.num}`.length `${from.num}`.length
@@ -395,19 +346,29 @@ function fnBatchModalOk() {
duration: timerS, duration: timerS,
}); });
setTimeout(() => { setTimeout(() => {
modalState.confirmLoading = false;
modalState.visibleByBatch = false;
modalStateBatchFrom.resetFields();
fnGetList(1); fnGetList(1);
}, timerS * 1000); }, timerS * 1000);
}
modalState.visibleByEdit = false;
modalStateFrom.resetFields();
} else {
if (from.num === 1) {
message.error({
content: `${res.msg}`,
duration: 3,
});
} else { } else {
modalState.confirmLoading = false;
notification.error({ notification.error({
message: modalState.title, message: modalState.title,
description: res.msg, description: res.msg,
duration: 3, duration: 3,
}); });
} }
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
}); });
}) })
.catch(e => { .catch(e => {
@@ -455,15 +416,6 @@ function fnBatchDelModalOk() {
}); });
} }
/**
* 批量添加对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnBatchModalCancel() {
modalState.visibleByBatch = false;
modalStateBatchFrom.resetFields();
}
/** /**
* 批量删除对话框弹出关闭执行函数 * 批量删除对话框弹出关闭执行函数
* 进行表达规则校验 * 进行表达规则校验
@@ -530,7 +482,7 @@ function fnRecordDelete(imsi: string) {
} }
/** /**
* UDM鉴权用户导出 * UDM鉴权用户勾选导出
*/ */
function fnRecordExport(type: string = 'txt') { function fnRecordExport(type: string = 'txt') {
const selectLen = tableState.selectedRowKeys.length; const selectLen = tableState.selectedRowKeys.length;
@@ -543,13 +495,15 @@ function fnRecordExport(type: string = 'txt') {
let content = ''; let content = '';
if (type == 'txt') { if (type == 'txt') {
for (const row of rows) { for (const row of rows) {
content += `${row.imsi},${row.ki},${row.algoIndex},${row.amf},${row.opc}\r\n`; const opc = row.opc === '-' ? '' : `,${row.opc}`;
content += `${row.imsi},${row.ki},${row.algoIndex},${row.amf}${opc}\r\n`;
} }
} }
if (type == 'csv') { if (type == 'csv') {
content = `IMSI,ki,Algo Index,AMF,OPC\r\n`; content = `IMSI,ki,Algo Index,AMF,OPC\r\n`;
for (const row of rows) { for (const row of rows) {
content += `${row.imsi},${row.ki},${row.algoIndex},${row.amf},${row.opc}\r\n`; const opc = row.opc === '-' ? '' : `,${row.opc}`;
content += `${row.imsi},${row.ki},${row.algoIndex},${row.amf}${opc}\r\n`;
} }
} }
@@ -557,7 +511,7 @@ function fnRecordExport(type: string = 'txt') {
saveAs(blob, `UDMAuth_${Date.now()}.${type}`); saveAs(blob, `UDMAuth_${Date.now()}.${type}`);
} }
/**列表导出 */ /**列表导出全部数据 */
function fnExportList(type: string) { function fnExportList(type: string) {
const neId = queryParams.neId; const neId = queryParams.neId;
if (!neId) return; if (!neId) return;
@@ -651,6 +605,10 @@ type ModalUploadImportStateType = {
loading: boolean; loading: boolean;
/**上传结果信息 */ /**上传结果信息 */
msg: string; msg: string;
/**导入类型 */
typeOptions: { label: string; value: string }[];
/**表单 */
from: { typeVal: string; typeData: any };
}; };
/**对话框表格信息导入对象信息状态 */ /**对话框表格信息导入对象信息状态 */
@@ -659,11 +617,27 @@ let uploadImportState: ModalUploadImportStateType = reactive({
title: t('components.UploadModal.uploadTitle'), title: t('components.UploadModal.uploadTitle'),
loading: false, loading: false,
msg: '', msg: '',
typeOptions: [
{ label: 'Default', value: 'default' },
{ label: 'K4', value: 'k4' },
],
from: {
typeVal: 'default',
typeData: undefined,
},
}); });
/**对话框表格信息导入类型选择 */
function fnModalUploadImportTypeChange() {
uploadImportState.from.typeData = '';
uploadImportState.msg = '';
}
/**对话框表格信息导入弹出窗口 */ /**对话框表格信息导入弹出窗口 */
function fnModalUploadImportOpen() { function fnModalUploadImportOpen() {
uploadImportState.msg = ''; uploadImportState.msg = '';
uploadImportState.from.typeVal = 'default';
uploadImportState.from.typeData = undefined;
uploadImportState.loading = false; uploadImportState.loading = false;
uploadImportState.visible = true; uploadImportState.visible = true;
} }
@@ -702,6 +676,7 @@ function fnModalUploadImportUpload(file: File) {
return importUDMAuth({ return importUDMAuth({
neId: neId, neId: neId,
uploadPath: filePath, uploadPath: filePath,
...uploadImportState.from,
}); });
}) })
.then(res => { .then(res => {
@@ -770,6 +745,7 @@ onMounted(() => {
<a-input <a-input
v-model:value="queryParams.imsi" v-model:value="queryParams.imsi"
allow-clear allow-clear
:maxlength="15"
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
></a-input> ></a-input>
</a-form-item> </a-form-item>
@@ -807,29 +783,11 @@ onMounted(() => {
{{ t('common.addText') }} {{ t('common.addText') }}
</a-button> </a-button>
<a-button type="primary" @click.prevent="fnModalVisibleByBatch(1)">
<template #icon>
<PlusOutlined />
</template>
{{ t('views.neUser.auth.batchAddText') }}
</a-button>
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.loadDataLoading"
@click.prevent="fnRecordDelete('0')"
>
<template #icon><DeleteOutlined /></template>
{{ t('views.neUser.auth.checkDel') }}
</a-button>
<a-button <a-button
type="primary" type="primary"
danger danger
ghost ghost
@click.prevent="fnModalVisibleByBatch(0)" @click.prevent="fnModalVisibleByBatch()"
> >
<template #icon> <template #icon>
<DeleteOutlined /> <DeleteOutlined />
@@ -876,6 +834,17 @@ onMounted(() => {
</a-button> </a-button>
</a-popconfirm> </a-popconfirm>
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.loadDataLoading"
@click.prevent="fnRecordDelete('0')"
>
<template #icon><DeleteOutlined /></template>
{{ t('views.neUser.auth.checkDel') }}
</a-button>
<a-popconfirm <a-popconfirm
:title="t('views.neUser.auth.checkExportConfirm')" :title="t('views.neUser.auth.checkExportConfirm')"
placement="topRight" placement="topRight"
@@ -1016,6 +985,24 @@ onMounted(() => {
:label-col="{ span: 6 }" :label-col="{ span: 6 }"
:labelWrap="true" :labelWrap="true"
> >
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.neUser.auth.numAdd')"
name="num"
v-bind="modalStateFrom.validateInfos.num"
v-show="!modalState.from.id"
>
<a-input-number
v-model:value="modalState.from.num"
style="width: 100%"
:min="1"
:max="10000"
placeholder="<=10000"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item <a-form-item
@@ -1026,6 +1013,7 @@ onMounted(() => {
<a-input <a-input
v-model:value="modalState.from.imsi" v-model:value="modalState.from.imsi"
allow-clear allow-clear
:maxlength="15"
:disabled="!!modalState.from.id" :disabled="!!modalState.from.id"
> >
<template #prefix> <template #prefix>
@@ -1101,12 +1089,12 @@ onMounted(() => {
</a-col> </a-col>
</a-row> </a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item <a-form-item
label="KI" label="KI"
name="ki" name="ki"
v-bind="modalStateFrom.validateInfos.ki" v-bind="modalStateFrom.validateInfos.ki"
:label-col="{ span: 3 }"
:labelWrap="true"
> >
<a-input <a-input
v-model:value="modalState.from.ki" v-model:value="modalState.from.ki"
@@ -1124,12 +1112,12 @@ onMounted(() => {
</template> </template>
</a-input> </a-input>
</a-form-item> </a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item <a-form-item
label="OPC" label="OPC"
name="opc" name="opc"
v-bind="modalStateFrom.validateInfos.opc" v-bind="modalStateFrom.validateInfos.opc"
:label-col="{ span: 3 }"
:labelWrap="true"
> >
<a-input <a-input
v-model:value="modalState.from.opc" v-model:value="modalState.from.opc"
@@ -1147,175 +1135,6 @@ onMounted(() => {
</template> </template>
</a-input> </a-input>
</a-form-item> </a-form-item>
</a-col>
</a-row>
</a-form>
</ProModal>
<!-- 批量新增框 -->
<ProModal
:drag="true"
:width="800"
:destroyOnClose="true"
:keyboard="false"
:mask-closable="false"
:visible="modalState.visibleByBatch"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnBatchModalOk"
@cancel="fnBatchModalCancel"
>
<a-form
name="modalStateBatchFrom"
layout="horizontal"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.neUser.auth.numAdd')"
name="num"
v-bind="modalStateBatchFrom.validateInfos.num"
>
<a-input-number
v-model:value="modalState.BatchForm.num"
style="width: 100%"
:min="1"
:max="10000"
placeholder="<=10000"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="IMSI"
name="imsi"
v-bind="modalStateBatchFrom.validateInfos.imsi"
>
<a-input v-model:value="modalState.BatchForm.imsi" allow-clear>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.imsiTip') }}<br />
{{ t('views.neUser.auth.imsiTip1') }}<br />
{{ t('views.neUser.auth.imsiTip2') }}<br />
{{ t('views.neUser.auth.imsiTip3') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="Status" name="status">
<a-select value="1">
<a-select-option value="1">Active</a-select-option>
<a-select-option value="0">Inactive</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="AMF"
name="amf"
v-bind="modalStateBatchFrom.validateInfos.amf"
>
<a-input
v-model:value="modalState.BatchForm.amf"
allow-clear
:maxlength="4"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.amfTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="Algo Index"
name="algo"
v-bind="modalStateBatchFrom.validateInfos.algoIndex"
>
<a-input-number
v-model:value="modalState.BatchForm.algoIndex"
style="width: 100%"
:min="0"
:max="15"
placeholder="0 ~ 15"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.algoIndexTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="KI"
name="ki"
v-bind="modalStateBatchFrom.validateInfos.ki"
>
<a-input
v-model:value="modalState.BatchForm.ki"
allow-clear
:maxlength="32"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.kiTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="OPC"
name="opc"
v-bind="modalStateBatchFrom.validateInfos.opc"
>
<a-input
v-model:value="modalState.BatchForm.opc"
allow-clear
:maxlength="32"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.auth.opcTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
</a-form> </a-form>
</ProModal> </ProModal>
@@ -1344,7 +1163,11 @@ onMounted(() => {
name="imsi" name="imsi"
v-bind="modalStateBatchDelFrom.validateInfos.imsi" v-bind="modalStateBatchDelFrom.validateInfos.imsi"
> >
<a-input v-model:value="modalState.BatchDelForm.imsi" allow-clear> <a-input
v-model:value="modalState.BatchDelForm.imsi"
allow-clear
:maxlength="15"
>
<template #prefix> <template #prefix>
<a-tooltip placement="topLeft"> <a-tooltip placement="topLeft">
<template #title> <template #title>
@@ -1389,6 +1212,16 @@ onMounted(() => {
:size="10" :size="10"
> >
<template #default> <template #default>
<a-radio-group
v-model:value="uploadImportState.from.typeVal"
:options="uploadImportState.typeOptions"
@change="fnModalUploadImportTypeChange"
/>
<a-input-password
v-if="uploadImportState.from.typeVal === 'k4'"
v-model:value="uploadImportState.from.typeData"
:placeholder="t('common.inputPlease')"
/>
<a-textarea <a-textarea
:disabled="true" :disabled="true"
:hidden="!uploadImportState.msg" :hidden="!uploadImportState.msg"

View File

@@ -74,6 +74,15 @@ let tableColumns = ref<TableColumnsType>([
align: 'center', align: 'center',
width: 100, width: 100,
}, },
{
title: 'NE Name',
dataIndex: 'neName',
align: 'left',
resizable: true,
width: 200,
minWidth: 150,
maxWidth: 400,
},
{ {
title: 'UE Number', title: 'UE Number',
dataIndex: 'ueNum', dataIndex: 'ueNum',
@@ -89,6 +98,7 @@ let tableColumns = ref<TableColumnsType>([
minWidth: 150, minWidth: 150,
maxWidth: 400, maxWidth: 400,
}, },
{ {
title: 'Radio Address', title: 'Radio Address',
dataIndex: 'address', dataIndex: 'address',
@@ -129,6 +139,7 @@ function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType; tableState.size = key as SizeType;
} }
let promises = ref<any[]>([]);
/**查询列表, pageNum初始页数 */ /**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) { function fnGetList(pageNum?: number) {
if (tableState.loading) return; if (tableState.loading) return;
@@ -136,6 +147,63 @@ function fnGetList(pageNum?: number) {
if (pageNum) { if (pageNum) {
queryParams.pageNum = pageNum; queryParams.pageNum = pageNum;
} }
if (!queryParams.neType) {
tableState.data = [];
promises.value = [];
//同时获取45G基站信息 且在每条信息中添加45G字段(原始数据没有) 已经筛选后的
neCascaderOptions.value.map((item: any) => {
item.children.forEach((child: any) => {
promises.value.push(
listBase5G({
neId: child.neId,
neType: child.neType,
nbId: queryParams.id,
pageNum: queryParams.pageNum,
pageSize: 10000,
}).then(res => {
// 添加 neName 字段到每一项数据
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
res.rows.forEach(row => {
row.neName = `${child.neType}_${child.neId}`;
});
}
return res;
})
);
});
});
Promise.allSettled(promises.value).then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
const allBaseData = result.value;
if (
allBaseData.code === RESULT_CODE_SUCCESS &&
Array.isArray(allBaseData.rows)
) {
// 处理成功结果
tablePagination.total += allBaseData.total;
tableState.data = [...tableState.data, ...allBaseData.rows];
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
} else {
//AMF返回404是代表没找到这个数据 GNB_NOT_FOUND
tablePagination.total = 0;
tableState.data = [];
}
tableState.loading = false;
}
});
});
return;
}
let toBack: Record<string, any> = { let toBack: Record<string, any> = {
neType: queryParams.neType[0], neType: queryParams.neType[0],
@@ -152,6 +220,11 @@ function fnGetList(pageNum?: number) {
} }
tablePagination.total = res.total; tablePagination.total = res.total;
tableState.data = res.rows; tableState.data = res.rows;
res.rows.forEach((item: any) => {
item.neName = queryParams.neType.join('_');
});
if ( if (
tablePagination.total <= tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize && (queryParams.pageNum - 1) * tablePagination.pageSize &&
@@ -160,7 +233,8 @@ function fnGetList(pageNum?: number) {
tableState.loading = false; tableState.loading = false;
fnGetList(queryParams.pageNum - 1); fnGetList(queryParams.pageNum - 1);
} }
} else {//AMF返回404是代表没找到这个数据 GNB_NOT_FOUND } else {
//AMF返回404是代表没找到这个数据 GNB_NOT_FOUND
tablePagination.total = 0; tablePagination.total = 0;
tableState.data = []; tableState.data = [];
} }
@@ -176,11 +250,19 @@ onMounted(() => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) { if (res.data.length > 0) {
// 过滤不可用的网元 // 过滤不可用的网元
neCascaderOptions.value = neInfoStore.getNeCascaderOptions.filter( for (const item of neInfoStore.getNeCascaderOptions) {
(item: any) => { if (!['AMF', 'MME'].includes(item.value)) continue;
return ['AMF', 'MME'].includes(item.value); const v = JSON.parse(JSON.stringify(item));
if (v.label === 'AMF') {
v.label = '5G';
} }
); if (v.label === 'MME') {
v.label = '4G';
}
neCascaderOptions.value.push(v);
}
if (neCascaderOptions.value.length === 0) { if (neCascaderOptions.value.length === 0) {
message.warning({ message.warning({
content: t('common.noData'), content: t('common.noData'),
@@ -188,8 +270,9 @@ onMounted(() => {
}); });
return; return;
} }
// 无查询参数neType时 默认选择AMF // 无查询参数neType时 默认选择AMF
const queryNeType = (route.query.neType as string) || 'AMF'; const queryNeType = (route.query.neType as string) || '5G';
const item = neCascaderOptions.value.find( const item = neCascaderOptions.value.find(
s => s.value === queryNeType s => s.value === queryNeType
); );
@@ -230,7 +313,6 @@ onMounted(() => {
<a-cascader <a-cascader
v-model:value="queryParams.neType" v-model:value="queryParams.neType"
:options="neCascaderOptions" :options="neCascaderOptions"
:allow-clear="false"
:placeholder="t('common.selectPlease')" :placeholder="t('common.selectPlease')"
/> />
</a-form-item> </a-form-item>

View File

@@ -223,7 +223,7 @@ onMounted(() => {
</a-col> </a-col>
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item label="IMSI" name="imsi"> <a-form-item label="IMSI" name="imsi">
<a-input v-model:value="queryParams.imsi" allow-clear></a-input> <a-input v-model:value="queryParams.imsi" allow-clear :maxlength="15"></a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ import { message } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider'; import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'; import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/lib/table'; import { ColumnsType } from 'ant-design-vue/lib/table';
import { listUEInfoBySMF } from '@/api/neUser/smf'; import { listSMFSubscribers } from '@/api/neData/smf';
import useNeInfoStore from '@/store/modules/neinfo'; import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
@@ -51,8 +51,6 @@ type TabeStateType = {
seached: boolean; seached: boolean;
/**记录数据 */ /**记录数据 */
data: object[]; data: object[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
}; };
/**表格状态 */ /**表格状态 */
@@ -61,7 +59,6 @@ let tableState: TabeStateType = reactive({
size: 'middle', size: 'middle',
seached: true, seached: true,
data: [], data: [],
selectedRowKeys: [],
}); });
/**表格字段列 */ /**表格字段列 */
@@ -69,7 +66,7 @@ let tableColumns: ColumnsType = [
{ {
title: 'IMSI', title: 'IMSI',
dataIndex: 'imsi', dataIndex: 'imsi',
align: 'center', align: 'left',
sorter: (a: any, b: any) => Number(a.imsi) - Number(b.imsi), sorter: (a: any, b: any) => Number(a.imsi) - Number(b.imsi),
customRender(opt) { customRender(opt) {
const idx = opt.value.lastIndexOf('-'); const idx = opt.value.lastIndexOf('-');
@@ -83,7 +80,7 @@ let tableColumns: ColumnsType = [
{ {
title: 'MSISDN', title: 'MSISDN',
dataIndex: 'msisdn', dataIndex: 'msisdn',
align: 'center', align: 'left',
sorter: (a: any, b: any) => Number(a.msisdn) - Number(b.msisdn), sorter: (a: any, b: any) => Number(a.msisdn) - Number(b.msisdn),
customRender(opt) { customRender(opt) {
const idx = opt.value.lastIndexOf('-'); const idx = opt.value.lastIndexOf('-');
@@ -97,7 +94,7 @@ let tableColumns: ColumnsType = [
{ {
title: 'RAT Type', title: 'RAT Type',
dataIndex: 'ratType', dataIndex: 'ratType',
align: 'center', align: 'left',
width: 100, width: 100,
}, },
{ {
@@ -116,12 +113,18 @@ let tableColumns: ColumnsType = [
} }
return ''; return '';
}, },
width: 150, width: 200,
}, },
{ {
title: t('common.operate'), title: t('common.operate'),
key: 'imsi', key: 'imsi',
align: 'left', align: 'left',
width: 100,
},
{
title: 'Remark',
dataIndex: 'remark',
align: 'left',
}, },
]; ];
@@ -220,12 +223,8 @@ function fnGetList(pageNum?: number) {
if (pageNum) { if (pageNum) {
queryParams.pageNum = pageNum; queryParams.pageNum = pageNum;
} }
listUEInfoBySMF(toRaw(queryParams)).then(res => { listSMFSubscribers(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选
if (tableState.selectedRowKeys.length > 0) {
tableState.selectedRowKeys = [];
}
tablePagination.total = res.total; tablePagination.total = res.total;
tableState.data = res.rows; tableState.data = res.rows;
if ( if (
@@ -286,7 +285,7 @@ onMounted(() => {
<!-- 表格搜索栏 --> <!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal"> <a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="8" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item :label="t('views.neUser.ue.neType')" name="neId "> <a-form-item :label="t('views.neUser.ue.neType')" name="neId ">
<a-select <a-select
v-model:value="queryParams.neId" v-model:value="queryParams.neId"
@@ -295,18 +294,17 @@ onMounted(() => {
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="8" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item label="IMSI" name="imsi"> <a-form-item label="IMSI" name="imsi">
<a-input v-model:value="queryParams.imsi" allow-clear></a-input> <a-input v-model:value="queryParams.imsi" allow-clear></a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="8" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item label="MSISDN" name="msisdn"> <a-form-item label="MSISDN" name="msisdn">
<a-input v-model:value="queryParams.msisdn" allow-clear></a-input> <a-input v-model:value="queryParams.msisdn" allow-clear></a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-col :lg="8" :md="12" :xs="24">
<a-form-item> <a-form-item>
<a-space :size="8"> <a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)"> <a-button type="primary" @click.prevent="fnGetList(1)">
@@ -382,7 +380,7 @@ onMounted(() => {
:data-source="tableState.data" :data-source="tableState.data"
:size="tableState.size" :size="tableState.size"
:pagination="tablePagination" :pagination="tablePagination"
:scroll="{ x: 1000, y: 400 }" :scroll="{ x: true, y: 400 }"
> >
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'imsi'"> <template v-if="column.key === 'imsi'">

View File

@@ -7,8 +7,8 @@ import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/lib/table'; import { ColumnsType } from 'ant-design-vue/lib/table';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import useDictStore from '@/store/modules/dict';
import useNeInfoStore from '@/store/modules/neinfo'; import useNeInfoStore from '@/store/modules/neinfo';
import { parseObjLineToHump } from '@/utils/parse-utils';
import { import {
addCustom, addCustom,
delCustom, delCustom,
@@ -16,15 +16,17 @@ import {
listCustom, listCustom,
updateCustom, updateCustom,
} from '@/api/perfManage/customTarget'; } from '@/api/perfManage/customTarget';
const { getDict } = useDictStore(); import { getKPITitle } from '@/api/perfManage/goldTarget';
import useDictStore from '@/store/modules/dict';
const { t, currentLocale } = useI18n(); const { t, currentLocale } = useI18n();
const { getDict } = useDictStore();
/**字典数据 */ /**字典数据 */
let dict: { let dict: {
/**原始严重程度 */ /**状态 */
activeAlarmSeverity: DictType[]; sysNormalDisable: DictType[];
} = reactive({ } = reactive({
activeAlarmSeverity: [], sysNormalDisable: [],
}); });
/**查询参数 */ /**查询参数 */
@@ -85,15 +87,22 @@ let tableColumns: ColumnsType = [
align: 'center', align: 'center',
}, },
{ {
title: t('views.perfManage.customTarget.kpiSet'), title: t('views.perfManage.customTarget.title'),
dataIndex: 'kpiSet', dataIndex: 'title',
align: 'center', align: 'center',
}, },
{ {
title: t('views.perfManage.customTarget.period'), title: t('views.perfManage.customTarget.description'),
dataIndex: 'threshold', dataIndex: 'description',
align: 'center', align: 'center',
}, },
{
title: t('views.perfManage.customTarget.status'),
dataIndex: 'status',
key: 'status',
align: 'left',
width: 100,
},
{ {
title: t('common.operate'), title: t('common.operate'),
key: 'id', key: 'id',
@@ -175,13 +184,14 @@ function fnGetList(pageNum?: number) {
queryParams.pageNum = pageNum; queryParams.pageNum = pageNum;
} }
listCustom(toRaw(queryParams)).then(res => { listCustom(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// 取消勾选 // 取消勾选
if (tableState.selectedRowKeys.length > 0) { if (tableState.selectedRowKeys.length > 0) {
tableState.selectedRowKeys = []; tableState.selectedRowKeys = [];
} }
tablePagination.total = res.total; tablePagination.total = res.total;
tableState.data = res.rows; tableState.data = res.data;
if ( if (
tablePagination.total <= tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize && (queryParams.pageNum - 1) * tablePagination.pageSize &&
@@ -207,8 +217,6 @@ type ModalStateType = {
neType: string[]; neType: string[];
/**网元类型性能测量集 */ /**网元类型性能测量集 */
neTypPerformance: Record<string, any>[]; neTypPerformance: Record<string, any>[];
/**网元类型对象类型集 */
objectTypeArr: Record<string, any>[];
/**已选择性能测量项 */ /**已选择性能测量项 */
selectedPre: string[]; selectedPre: string[];
/**表单数据 */ /**表单数据 */
@@ -224,21 +232,32 @@ let modalState: ModalStateType = reactive({
title: '', title: '',
neType: [], neType: [],
neTypPerformance: [], neTypPerformance: [],
objectTypeArr: [],
selectedPre: [], selectedPre: [],
from: { from: {
id: '', id: undefined,
neType: '', neType: 'UDM',
objectType: '',
expression: '',
kpiSet: '',
title: '',
kpiId: '', kpiId: '',
period: 900, title: '',
expression: '',
status: 'Active',
unit: '',
description: '',
}, },
confirmLoading: false, confirmLoading: false,
}); });
/**表单中多选的OPTION */
const modalStateFromOption = reactive({
symbolJson: [
{ label: '(', value: '(' },
{ label: ')', value: ')' },
{ label: '+', value: '+' },
{ label: '-', value: '-' },
{ label: '*', value: '*' },
{ label: '/', value: '/' },
],
});
/**对话框内表单属性和校验规则 */ /**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm( const modalStateFrom = Form.useForm(
modalState.from, modalState.from,
@@ -246,15 +265,7 @@ const modalStateFrom = Form.useForm(
neType: [ neType: [
{ {
required: true, required: true,
message: t('views.traceManage.task.neTypePlease'), message: t('views.ne.common.neTypePlease'),
},
],
objectType: [
{
required: true,
message:
t('views.perfManage.customTarget.objectType') +
t('common.unableNull'),
}, },
], ],
expression: [ expression: [
@@ -279,60 +290,45 @@ const modalStateFrom = Form.useForm(
t('views.perfManage.customTarget.title') + t('common.unableNull'), t('views.perfManage.customTarget.title') + t('common.unableNull'),
}, },
], ],
period: [ unit: [
{ {
required: true, required: true,
message: message:
t('views.perfManage.customTarget.period') + t('common.unableNull'), t('views.perfManage.customTarget.unit') + t('common.unableNull'),
}, },
], ],
}) })
); );
/**性能测量数据集选择初始 */ /**性能测量数据集选择初始 value:neType*/
function fnSelectPerformanceInit(value: any) { function fnSelectPerformanceInit(value: any) {
const performance = useNeInfoStore().perMeasurementList.filter(
i => i.neType === value
);
if (modalState.from.objectType) modalState.from.objectType = '';
if (modalState.selectedPre.length > 0) modalState.selectedPre = [];
modalState.from.expression = ''; modalState.from.expression = '';
const arrSet = new Set<string>(); modalState.neTypPerformance = [
performance.forEach((data: any) => { {
arrSet.add(data.objectType); value: 'granularity',
label: t('views.perfManage.customTarget.granularity'),
},
];
// 当前语言
var language = currentLocale.value.split('_')[0];
if (language === 'zh') language = 'cn';
// 获取表头文字
getKPITitle(value).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
for (const item of res.data) {
const kpiDisplay = item[`${language}Title`];
const kpiValue = item[`kpiId`];
modalState.neTypPerformance.push({
value: kpiValue,
label: kpiDisplay,
});
}
} else {
message.warning({
content: t('common.getInfoFail'),
duration: 2,
}); });
// 组装对象类型options
modalState.objectTypeArr = Array.from(arrSet).map((value: any) => ({
label: value,
value: value,
}));
//进行分组选择
const groupedData = performance.reduce((groups: any, item: any) => {
const { kpiCode, ...rest } = item;
if (!groups[kpiCode]) {
groups[kpiCode] = [];
} }
groups[kpiCode].push(rest);
return groups;
}, {});
//渲染出性能测量集的选择项
modalState.neTypPerformance = Object.keys(groupedData).map(kpiCode => {
return {
label: kpiCode,
options: groupedData[kpiCode].map((item: any) => {
return {
value: item.kpiId,
label:
currentLocale.value === 'zh_CN'
? JSON.parse(item.titleJson).cn
: JSON.parse(item.titleJson).en,
kpiCode: kpiCode,
};
}),
};
}); });
} }
@@ -340,30 +336,17 @@ function fnSelectPerformanceInit(value: any) {
* 对话框弹出显示为 新增或者修改 * 对话框弹出显示为 新增或者修改
* @param noticeId 网元id, 不传为新增 * @param noticeId 网元id, 不传为新增
*/ */
function fnModalVisibleByEdit(id?: string) { function fnModalVisibleByEdit(row?: any, id?: any) {
if (!id) { if (!id) {
modalStateFrom.resetFields(); modalStateFrom.resetFields();
modalState.title = t('views.perfManage.customTarget.addCustom'); modalState.title = t('views.perfManage.customTarget.addCustom');
modalState.visibleByEdit = true; modalState.visibleByEdit = true;
fnSelectPerformanceInit(modalState.from.neType);
} else { } else {
if (modalState.confirmLoading) return; fnSelectPerformanceInit(row.neType);
const hide = message.loading(t('common.loading'), 0); modalState.from = Object.assign(modalState.from, row);
modalState.confirmLoading = true;
getCustom(id).then(res => {
modalState.confirmLoading = false;
hide();
if (res.code === RESULT_CODE_SUCCESS && res.data) {
fnSelectPerformanceInit(res.data.neType);
modalState.selectedPre = res.data.kpiSet
? res.data.kpiSet.split(',')
: [];
modalState.from = Object.assign(modalState.from, res.data);
modalState.title = t('views.perfManage.customTarget.editCustom'); modalState.title = t('views.perfManage.customTarget.editCustom');
modalState.visibleByEdit = true; modalState.visibleByEdit = true;
} else {
message.error(t('views.perfManage.customTarget.errorCustomInfo'), 3);
}
});
} }
} }
@@ -375,13 +358,6 @@ function fnModalOk() {
modalStateFrom modalStateFrom
.validate() .validate()
.then(e => { .then(e => {
// if (modalState.selectedPre.length === 0) {
// message.error({
// content: `${res.msg}`,
// duration: 3,
// });
// }
modalState.from.kpiSet = modalState.selectedPre.join(',');
const from = toRaw(modalState.from); const from = toRaw(modalState.from);
//return false; //return false;
modalState.confirmLoading = true; modalState.confirmLoading = true;
@@ -424,43 +400,54 @@ function fnModalCancel() {
modalStateFrom.resetFields(); modalStateFrom.resetFields();
modalState.neType = []; modalState.neType = [];
modalState.neTypPerformance = []; modalState.neTypPerformance = [];
modalState.selectedPre = [];
} }
/** /**
* 选择性能指标,填充进当前计算公式的值 * 选择性能指标,填充进当前计算公式的值
*/ */
function fnSelectPer(s: any, option: any) { function fnSelectPer(s: any, option: any) {
modalState.from.expression += `'${s}'`;
}
function fnSelectSymbol(s: any) {
modalState.from.expression += s; modalState.from.expression += s;
} }
/**网元参数 */
/** let neCascaderOptions = ref<Record<string, any>[]>([]);
* 多选框取消性能指标 表达式的变化
*/
function fnDelPer(s: any, option: any) {
modalState.from.expression = modalState.from.expression.replace(s, '');
}
// function checkText(e:any){
// console.log(e);
// const reg = /^[*+-/]*$/;
// }
onMounted(() => { onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('active_alarm_severity')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.activeAlarmSeverity = resArr[0].value;
}
});
Promise.allSettled([ Promise.allSettled([
// 获取网元网元列表 // 获取网元网元列表
getDict('sys_normal_disable'),
useNeInfoStore().fnNelist(), useNeInfoStore().fnNelist(),
// 获取性能测量集列表 ])
useNeInfoStore().fnNeTaskPerformance(), .then(resArr => {
]).finally(() => { if (resArr[0].status === 'fulfilled') {
dict.sysNormalDisable = resArr[0].value;
}
if (
resArr[1].status === 'fulfilled' &&
Array.isArray(resArr[1].value.data)
) {
if (resArr[1].value.data.length > 0) {
// 过滤不可用的网元
neCascaderOptions.value =
useNeInfoStore().getNeCascaderOptions.filter((item: any) => {
return !['OMC', 'NSSF', 'NEF', 'NRF', 'LMF', 'N3IWF'].includes(
item.value
);
});
if (neCascaderOptions.value.length === 0) {
message.warning({
content: t('common.noData'),
duration: 2,
});
return;
}
}
}
})
.finally(() => {
// 获取列表数据 // 获取列表数据
fnGetList(); fnGetList();
}); });
@@ -479,14 +466,14 @@ onMounted(() => {
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.traceManage.task.neType')" :label="t('views.ne.common.neType')"
name="neType " name="neType "
> >
<a-auto-complete <a-auto-complete
v-model:value="queryParams.neType" v-model:value="queryParams.neType"
:options="useNeInfoStore().getNeSelectOtions" :options="neCascaderOptions"
allow-clear allow-clear
:placeholder="t('views.traceManage.task.neTypePlease')" :placeholder="t('views.ne.common.neTypePlease')"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
@@ -580,7 +567,7 @@ onMounted(() => {
<template #title>{{ t('common.editText') }}</template> <template #title>{{ t('common.editText') }}</template>
<a-button <a-button
type="link" type="link"
@click.prevent="fnModalVisibleByEdit(record.id)" @click.prevent="fnModalVisibleByEdit(record, record.id)"
> >
<template #icon><FormOutlined /></template> <template #icon><FormOutlined /></template>
</a-button> </a-button>
@@ -593,6 +580,24 @@ onMounted(() => {
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</template> </template>
<template v-if="column.key === 'status'">
<DictTag
:options="[
{
label: t('views.perfManage.customTarget.active'),
value: 'Active',
tagType: 'success',
},
{
label: t('views.perfManage.customTarget.inactive'),
value: 'Inactive',
tagType: 'error',
},
]"
:value="record.status"
/>
</template>
</template> </template>
</a-table> </a-table>
</a-card> </a-card>
@@ -610,35 +615,25 @@ onMounted(() => {
@ok="fnModalOk" @ok="fnModalOk"
@cancel="fnModalCancel" @cancel="fnModalCancel"
> >
<a-form name="modalStateFrom" layout="horizontal"> <a-form
name="modalStateFrom"
layout="horizontal"
:label-col="{ span: 6 }"
:label-wrap="true"
>
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.traceManage.task.neType')" :label="t('views.ne.common.neType')"
name="neType" name="neType"
v-bind="modalStateFrom.validateInfos.neType" v-bind="modalStateFrom.validateInfos.neType"
> >
<a-select <a-select
v-model:value="modalState.from.neType" v-model:value="modalState.from.neType"
:options="useNeInfoStore().getNeSelectOtions" :options="neCascaderOptions"
@change="fnSelectPerformanceInit" @change="fnSelectPerformanceInit"
:allow-clear="false" :allow-clear="false"
:placeholder="t('views.traceManage.task.neTypePlease')" :placeholder="t('views.ne.common.neTypePlease')"
>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.perfManage.customTarget.objectType')"
name="kpiSobjectTypeet"
v-show="modalState.from.neType"
v-bind="modalStateFrom.validateInfos.objectType"
>
<a-select
placeholder="Please select"
v-model:value="modalState.from.objectType"
:options="modalState.objectTypeArr"
> >
</a-select> </a-select>
</a-form-item> </a-form-item>
@@ -646,6 +641,16 @@ onMounted(() => {
</a-row> </a-row>
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.perfManage.customTarget.title')"
name="title"
v-bind="modalStateFrom.validateInfos.title"
>
<a-input v-model:value="modalState.from.title" allow-clear>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.perfManage.customTarget.kpiId')" :label="t('views.perfManage.customTarget.kpiId')"
@@ -656,64 +661,96 @@ onMounted(() => {
</a-input> </a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.perfManage.customTarget.period')"
name="period"
v-bind="modalStateFrom.validateInfos.period"
>
<a-select
v-model:value="modalState.from.period"
:placeholder="t('common.selectPlease')"
:options="[
{ label: '5S', value: 5 },
{ label: '1M', value: 60 },
{ label: '5M', value: 300 },
{ label: '15M', value: 900 },
{ label: '30M', value: 1800 },
{ label: '60M', value: 3600 },
]"
/>
</a-form-item>
</a-col>
</a-row> </a-row>
<a-form-item
:label="t('views.perfManage.customTarget.title')"
name="title"
v-bind="modalStateFrom.validateInfos.title"
>
<a-input v-model:value="modalState.from.title" allow-clear> </a-input>
</a-form-item>
<a-form-item <a-form-item
:label="t('views.perfManage.customTarget.expression')" :label="t('views.perfManage.customTarget.expression')"
name="expression" name="expression"
:label-col="{ span: 3 }"
v-bind="modalStateFrom.validateInfos.expression" v-bind="modalStateFrom.validateInfos.expression"
> >
<a-input v-model:value="modalState.from.expression" allow-clear> <a-input v-model:value="modalState.from.expression" allow-clear>
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="21" :md="21" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item name="perSelect"> <a-form-item
name="perSelect"
:label="t('views.perfManage.customTarget.symbol')"
>
<a-select
placeholder="Please select"
:options="modalStateFromOption.symbolJson"
@select="fnSelectSymbol"
></a-select>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
name="perSelect"
:label="t('views.perfManage.customTarget.element')"
>
<a-select <a-select
mode="multiple"
style="margin-left: 80px"
placeholder="Please select" placeholder="Please select"
allow-clear
v-model:value="modalState.selectedPre"
:options="modalState.neTypPerformance" :options="modalState.neTypPerformance"
@select="fnSelectPer" @select="fnSelectPer"
@deselect="fnDelPer"
></a-select> ></a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.perfManage.customTarget.unit')"
name="expression"
v-bind="modalStateFrom.validateInfos.unit"
>
<a-auto-complete
v-model:value="modalState.from.unit"
:options="[
{
label: 'Mbps',
value: 'Mbps',
},
{
label: '%',
value: '%',
},
]"
></a-auto-complete>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.perfManage.customTarget.status')"
name="status"
>
<a-select
v-model:value="modalState.from.status"
default-value="0"
:options="[
{
label: t('views.perfManage.customTarget.active'),
value: 'Active',
},
{
label: t('views.perfManage.customTarget.inactive'),
value: 'Inactive',
},
]"
:placeholder="t('common.selectPlease')"
>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item <a-form-item
:label="t('views.perfManage.customTarget.description')" :label="t('views.perfManage.customTarget.description')"
name="description" name="description"
:label-col="{ span: 3 }"
> >
<a-textarea <a-textarea
v-model:value="modalState.from.description" v-model:value="modalState.from.description"

View File

@@ -254,15 +254,7 @@ function fnGetListTitle() {
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
tableColumns.value = []; tableColumns.value = [];
const columns: ColumnsType = [ const columns: ColumnsType = [];
{
title: t('views.perfManage.perfData.neName'),
dataIndex: 'neName',
key: 'neName',
align: 'left',
width: 100,
},
];
for (const item of res.data) { for (const item of res.data) {
const kpiDisplay = item[`${language}Title`]; const kpiDisplay = item[`${language}Title`];
const kpiValue = item[`kpiId`]; const kpiValue = item[`kpiId`];
@@ -277,6 +269,13 @@ function fnGetListTitle() {
maxWidth: 300, maxWidth: 300,
}); });
} }
columns.push({
title: t('views.perfManage.perfData.neName'),
dataIndex: 'neName',
key: 'neName',
align: 'left',
width: 100,
});
columns.push({ columns.push({
title: t('views.perfManage.goldTarget.time'), title: t('views.perfManage.goldTarget.time'),
dataIndex: 'timeGroup', dataIndex: 'timeGroup',
@@ -458,7 +457,7 @@ function fnRanderChartData() {
} }
for (const item of orgData) { for (const item of orgData) {
chartDataXAxisData.push(item['timeGroup']); chartDataXAxisData.push(parseDateToStr(+item['timeGroup']));
const keys = Object.keys(item); const keys = Object.keys(item);
for (const y of chartDataYSeriesData) { for (const y of chartDataYSeriesData) {
for (const key of keys) { for (const key of keys) {
@@ -505,21 +504,23 @@ function fnLegendSelected(bool: any) {
/**图表实时统计 */ /**图表实时统计 */
function fnRealTimeSwitch(bool: any) { function fnRealTimeSwitch(bool: any) {
if (bool) { if (bool) {
tableState.seached = false;
// 建立链接 // 建立链接
const options: OptionsType = { const options: OptionsType = {
url: '/ws', url: '/ws',
params: { params: {
/**订阅通道组 /**订阅通道组
* *
* 指标(GroupID:10) * 指标(GroupID:10_neType_neId)
*/ */
subGroupID: '10', subGroupID: `10_${queryParams.neType}_${queryParams.neId}`,
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: wsError, onerror: wsError,
}; };
ws.connect(options); ws.connect(options);
} else { } else {
tableState.seached = true;
ws.close(); ws.close();
} }
} }
@@ -543,8 +544,10 @@ function wsMessage(res: Record<string, any>) {
return; return;
} }
// kpiEvent 黄金指标指标事件 // kpiEvent 黄金指标指标事件
if (data.groupId === '10') {
const kpiEvent = data.data; const kpiEvent = data.data;
tableState.data.unshift(kpiEvent);
tablePagination.total++;
// 非对应网元类型 // 非对应网元类型
if (kpiEvent.neType !== queryParams.neType) return; if (kpiEvent.neType !== queryParams.neType) return;
@@ -553,14 +556,14 @@ function wsMessage(res: Record<string, any>) {
// x轴 // x轴
if (key === 'timeGroup') { if (key === 'timeGroup') {
// chartDataXAxisData.shift(); // chartDataXAxisData.shift();
chartDataXAxisData.push(v); chartDataXAxisData.push(parseDateToStr(+v));
continue; continue;
} }
// y轴 // y轴
const yItem = chartDataYSeriesData.find(item => item.key === key); const yItem = chartDataYSeriesData.find(item => item.key === key);
if (yItem) { if (yItem) {
// yItem.data.shift(); // yItem.data.shift();
yItem.data.push(v); yItem.data.push(+v);
} }
} }
@@ -572,10 +575,9 @@ function wsMessage(res: Record<string, any>) {
series: chartDataYSeriesData, series: chartDataYSeriesData,
}); });
} }
}
onMounted(() => { onMounted(() => {
// 目前支持的 AMF AUSF MME MOCNGW NSSF SMF UDM UPF // 目前支持的 AMF AUSF MME MOCNGW NSSF SMF UDM UPF PCF
// 获取网元网元列表 // 获取网元网元列表
neInfoStore.fnNelist().then(res => { neInfoStore.fnNelist().then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
@@ -583,15 +585,9 @@ onMounted(() => {
// 过滤不可用的网元 // 过滤不可用的网元
neCascaderOptions.value = neInfoStore.getNeCascaderOptions.filter( neCascaderOptions.value = neInfoStore.getNeCascaderOptions.filter(
(item: any) => { (item: any) => {
return ![ return !['OMC', 'NSSF', 'NEF', 'NRF', 'LMF', 'N3IWF'].includes(
'OMC', item.value
'PCF', );
'NSSF',
'NEF',
'NRF',
'LMF',
'N3IWF',
].includes(item.value);
} }
); );
if (neCascaderOptions.value.length === 0) { if (neCascaderOptions.value.length === 0) {
@@ -619,9 +615,9 @@ onMounted(() => {
// 查询当前小时 // 查询当前小时
const nowDate: Date = new Date(); const nowDate: Date = new Date();
nowDate.setMinutes(0, 0, 0); nowDate.setMinutes(0, 0, 0);
queryRangePicker.value[0] = parseDateToStr(nowDate); queryRangePicker.value[0] = `${nowDate.getTime()}`;
nowDate.setMinutes(59, 59, 59); nowDate.setMinutes(59, 59, 59);
queryRangePicker.value[1] = parseDateToStr(nowDate); queryRangePicker.value[1] = `${nowDate.getTime()}`;
fnGetListTitle(); fnGetListTitle();
// 绘图 // 绘图
fnRanderChart(); fnRanderChart();
@@ -653,7 +649,7 @@ onBeforeUnmount(() => {
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item <a-form-item
name="neType" name="neType"
:label="t('views.traceManage.task.neType')" :label="t('views.ne.common.neType')"
> >
<a-cascader <a-cascader
v-model:value="state.neType" v-model:value="state.neType"
@@ -666,15 +662,17 @@ onBeforeUnmount(() => {
<a-col :lg="10" :md="12" :xs="24"> <a-col :lg="10" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.perfManage.goldTarget.timeFrame')" :label="t('views.perfManage.goldTarget.timeFrame')"
name="eventTime" name="timeFrame"
> >
<a-range-picker <a-range-picker
v-model:value="queryRangePicker" v-model:value="queryRangePicker"
value-format="YYYY-MM-DD HH:mm:ss" bordered
format="YYYY-MM-DD HH:mm:ss"
:allow-clear="false" :allow-clear="false"
show-time :show-time="{ format: 'HH:mm:ss' }"
/> format="YYYY-MM-DD HH:mm:ss"
value-format="x"
style="width: 100%"
></a-range-picker>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="4" :md="12" :xs="24"> <a-col :lg="4" :md="12" :xs="24">
@@ -747,15 +745,6 @@ onBeforeUnmount(() => {
<!-- 插槽-卡片右侧 --> <!-- 插槽-卡片右侧 -->
<template #extra> <template #extra>
<a-space :size="8" align="center" v-show="tableState.showTable"> <a-space :size="8" align="center" v-show="tableState.showTable">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip>
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.reloadText') }}</template> <template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()"> <a-button type="text" @click.prevent="fnGetList()">
@@ -833,11 +822,16 @@ onBeforeUnmount(() => {
:data-source="tableState.data" :data-source="tableState.data"
:size="tableState.size" :size="tableState.size"
:pagination="tablePagination" :pagination="tablePagination"
:scroll="{ x: tableColumnsDnd.length * 200, y: 450 }" :scroll="{ x: tableColumnsDnd.length * 200, y: 'calc(100vh - 480px)' }"
@resizeColumn="(w:number, col:any) => (col.width = w)" @resizeColumn="(w:number, col:any) => (col.width = w)"
:show-expand-column="false" :show-expand-column="false"
@change="fnTableChange" @change="fnTableChange"
> >
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'timeGroup'">
{{ parseDateToStr(+record.timeGroup) }}
</template>
</template>
</a-table> </a-table>
<!-- 图表 --> <!-- 图表 -->

View File

@@ -0,0 +1,834 @@
<script setup lang="ts">
import * as echarts from 'echarts/core';
import {
TooltipComponent,
TooltipComponentOption,
GridComponent,
GridComponentOption,
LegendComponent,
LegendComponentOption,
DataZoomComponent,
DataZoomComponentOption,
} from 'echarts/components';
import { LineChart, LineSeriesOption } from 'echarts/charts';
import { UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import {
reactive,
ref,
onMounted,
toRaw,
markRaw,
nextTick,
onBeforeUnmount,
} from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue/lib';
import { ColumnsType } from 'ant-design-vue/lib/table';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import TableColumnsDnd from '@/components/TableColumnsDnd/index.vue';
import {
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n';
import { listCustom } from '@/api/perfManage/customTarget';
import { listCustomData } from '@/api/perfManage/customData';
import { parseDateToStr } from '@/utils/date-utils';
import { writeSheet } from '@/utils/execl-utils';
import saveAs from 'file-saver';
import { generateColorRGBA } from '@/utils/generate-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import { useRoute } from 'vue-router';
const neInfoStore = useNeInfoStore();
const route = useRoute();
const { t, currentLocale } = useI18n();
const ws = new WS();
echarts.use([
TooltipComponent,
GridComponent,
LegendComponent,
DataZoomComponent,
LineChart,
CanvasRenderer,
UniversalTransition,
]);
type EChartsOption = echarts.ComposeOption<
| TooltipComponentOption
| GridComponentOption
| LegendComponentOption
| DataZoomComponentOption
| LineSeriesOption
>;
/**图DOM节点实例对象 */
const kpiChartDom = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
const kpiChart = ref<any>(null);
/**网元参数 */
let neCascaderOptions = ref<Record<string, any>[]>([]);
/**记录开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']);
/**表格字段列 */
let tableColumns = ref<ColumnsType>([]);
/**表格字段列排序 */
let tableColumnsDnd = ref<ColumnsType>([]);
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
},
});
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: Record<string, any>[];
/**显示表格 */
showTable: boolean;
};
/**表格状态 */
let tableState: TabeStateType = reactive({
tableColumns: [],
loading: false,
size: 'middle',
seached: true,
data: [],
showTable: false,
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**查询参数 */
let queryParams: any = reactive({
/**网元类型 */
neType: '',
/**网元标识 */
neId: '',
/**开始时间 */
startTime: '',
/**结束时间 */
endTime: '',
/**排序字段 */
sortField: 'created_at',
/**排序方式 */
sortOrder: 'desc',
});
/**表格分页、排序、筛选变化时触发操作, 排序方式,取值为 ascend descend */
function fnTableChange(pagination: any, filters: any, sorter: any, extra: any) {
const { columnKey, order } = sorter;
if (!order) return;
if (order.startsWith(queryParams.sortOrder)) return;
if (order) {
queryParams.sortField = columnKey;
queryParams.sortOrder = order.replace('end', '');
} else {
queryParams.sortOrder = 'asc';
}
fnGetList();
}
/**对象信息状态类型 */
type StateType = {
/**网元类型 */
neType: string[];
/**图表实时统计 */
chartRealTime: boolean;
/**图表标签选择 */
chartLegendSelectedFlag: boolean;
};
/**对象信息状态 */
let state: StateType = reactive({
neType: [],
chartRealTime: false,
chartLegendSelectedFlag: false,
});
/**
* 数据列表导出
*/
function fnRecordExport() {
Modal.confirm({
title: 'Tip',
content: t('views.perfManage.goldTarget.exportSure'),
onOk() {
const key = 'exportKPI';
message.loading({ content: t('common.loading'), key });
if (tableState.data.length <= 0) {
message.error({
content: t('views.perfManage.goldTarget.exportEmpty'),
key,
duration: 2,
});
return;
}
const tableColumnsTitleArr: string[] = [];
const tableColumnsKeyArr: string[] = [];
for (const columns of tableColumnsDnd.value) {
tableColumnsTitleArr.push(`${columns.title}`);
tableColumnsKeyArr.push(`${columns.key}`);
}
const kpiDataArr = [];
for (const item of tableState.data) {
const kpiData: Record<string, any> = {};
const keys = Object.keys(item);
for (let i = 0; i <= tableColumnsKeyArr.length; i++) {
for (const key of keys) {
if (tableColumnsKeyArr[i] === key) {
const title = tableColumnsTitleArr[i];
kpiData[title] = item[key];
}
}
}
kpiDataArr.push(kpiData);
}
writeSheet(kpiDataArr, 'KPI', { header: tableColumnsTitleArr })
.then(fileBlob => saveAs(fileBlob, `kpi_data_${Date.now()}.xlsx`))
.finally(() => {
message.success({
content: t('common.msgSuccess', { msg: t('common.export') }),
key,
duration: 2,
});
});
},
});
}
/**查询数据列表表头 */
function fnGetListTitle() {
// 当前语言
var language = currentLocale.value.split('_')[0];
if (language === 'zh') language = 'cn';
// 获取表头文字
listCustom({ neType: state.neType[0], status: 'Active' })
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length === 0) {
message.error({
content: t('views.perfManage.customTarget.kpiIdTip'),
duration: 2,
});
tableState.data = [];
tableColumns.value = [];
tableColumnsDnd.value = [];
fnRanderChartData();
return false;
}
tableColumns.value = [];
const columns: ColumnsType = [];
for (const item of res.data) {
const kpiDisplay = item[`unit`]? item[`title`]+ `(${item['unit']})`:item[`title`];
const kpiValue = item[`kpiId`];
columns.push({
title: kpiDisplay,
dataIndex: kpiValue,
align: 'left',
key: kpiValue,
resizable: true,
width: 100,
minWidth: 150,
maxWidth: 300,
});
}
columns.push({
title: t('views.perfManage.perfData.neName'),
dataIndex: 'neName',
key: 'neName',
align: 'left',
width: 100,
});
columns.push({
title: t('views.perfManage.goldTarget.time'),
dataIndex: 'timeGroup',
align: 'left',
fixed: 'right',
key: 'timeGroup',
sorter: true,
width: 100,
});
nextTick(() => {
tableColumns.value = columns;
});
return true;
} else {
message.warning({
content: t('common.getInfoFail'),
duration: 2,
});
return false;
}
})
.then(result => {
result && fnGetList();
});
}
/**查询数据列表 */
function fnGetList() {
if (tableState.loading) return;
tableState.loading = true;
queryParams.neType = state.neType[0];
queryParams.neId = state.neType[1];
queryParams.startTime = queryRangePicker.value[0];
queryParams.endTime = queryRangePicker.value[1];
listCustomData(toRaw(queryParams))
.then(res => {
tableState.loading = false;
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
tablePagination.total = res.data.length;
tableState.data = res.data;
return true;
}
return false;
})
.then(result => {
if (result) {
fnRanderChartData();
}
});
}
/**切换显示类型 图或表格 */
function fnChangShowType() {
tableState.showTable = !tableState.showTable;
}
/**绘制图表 */
function fnRanderChart() {
const container: HTMLElement | undefined = kpiChartDom.value;
if (!container) return;
kpiChart.value = markRaw(echarts.init(container, 'light'));
const option: EChartsOption = {
tooltip: {
trigger: 'axis',
position: function (pt: any) {
return [pt[0], '10%'];
},
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [], // 数据x轴
},
yAxis: {
type: 'value',
boundaryGap: [0, '100%'],
},
legend: {
type: 'scroll',
orient: 'vertical',
top: 40,
right: 20,
itemWidth: 20,
itemGap: 25,
textStyle: {
color: '#646A73',
},
icon: 'circle',
selected: {},
},
grid: {
left: '10%',
right: '30%',
bottom: '20%',
},
dataZoom: [
{
type: 'inside',
start: 90,
end: 100,
},
{
start: 90,
end: 100,
},
],
series: [], // 数据y轴
};
kpiChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (kpiChart.value) {
kpiChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
/**图表标签选择 */
let chartLegendSelected: Record<string, boolean> = {};
/**图表配置数据x轴 */
let chartDataXAxisData: string[] = [];
/**图表配置数据y轴 */
let chartDataYSeriesData: Record<string, any>[] = [];
/**图表数据渲染 */
function fnRanderChartData() {
if (kpiChart.value == null && tableState.data.length <= 0) {
return;
}
// 重置
chartLegendSelected = {};
chartDataXAxisData = [];
chartDataYSeriesData = [];
for (var columns of tableColumns.value) {
if (
columns.key === 'neName' ||
columns.key === 'startIndex' ||
columns.key === 'timeGroup'
) {
continue;
}
const color = generateColorRGBA();
chartDataYSeriesData.push({
name: `${columns.title}`,
key: `${columns.key}`,
type: 'line',
symbol: 'none',
sampling: 'lttb',
itemStyle: {
color: color,
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: color.replace(')', ',0.8)'),
},
{
offset: 1,
color: color.replace(')', ',0.3)'),
},
]),
},
data: [],
});
chartLegendSelected[`${columns.title}`] = state.chartLegendSelectedFlag;
}
// 用降序就反转
let orgData = tableState.data;
if (queryParams.sortOrder === 'desc') {
orgData = orgData.toReversed();
}
for (const item of orgData) {
chartDataXAxisData.push(item['timeGroup']);
const keys = Object.keys(item);
for (const y of chartDataYSeriesData) {
for (const key of keys) {
if (y.key === key) {
y.data.push(+item[key]);
}
}
}
}
// console.log(queryParams.sortOrder, chartLegendSelected);
// console.log(chartDataXAxisData, chartDataYSeriesData);
// 绘制图数据
kpiChart.value.setOption(
{
legend: {
selected: chartLegendSelected,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: chartDataXAxisData,
},
series: chartDataYSeriesData,
},
{
replaceMerge: ['xAxis', 'series'],
}
);
}
/**图表折线显示全部 */
function fnLegendSelected(bool: any) {
for (const key of Object.keys(chartLegendSelected)) {
chartLegendSelected[key] = bool;
}
kpiChart.value.setOption({
legend: {
selected: chartLegendSelected,
},
});
}
/**图表实时统计 */
function fnRealTimeSwitch(bool: any) {
if (bool) {
tableState.seached = false;
// 建立链接
const options: OptionsType = {
url: '/ws',
params: {
/**订阅通道组
*
* 指标(GroupID:10_neType_neId)
*/
subGroupID: `20_${queryParams.neType}_${queryParams.neId}`,
},
onmessage: wsMessage,
onerror: wsError,
};
ws.connect(options);
} else {
tableState.seached = true;
ws.close();
}
}
/**接收数据后回调 */
function wsError(ev: any) {
// 接收数据后回调
console.error(ev);
}
/**接收数据后回调 */
function wsMessage(res: Record<string, any>) {
const { code, requestId, data } = res;
if (code === RESULT_CODE_ERROR) {
console.warn(res.msg);
return;
}
// 订阅组信息
if (!data?.groupId) {
return;
}
// kpiEvent 黄金指标指标事件
const kpiEvent = data.data;
tableState.data.unshift(kpiEvent);
tablePagination.total++;
// 非对应网元类型
if (kpiEvent.neType !== queryParams.neType) return;
for (const key of Object.keys(data.data)) {
const v = kpiEvent[key];
// x轴
if (key === 'timeGroup') {
// chartDataXAxisData.shift();
chartDataXAxisData.push(v);
continue;
}
// y轴
const yItem = chartDataYSeriesData.find(item => item.key === key);
if (yItem) {
// yItem.data.shift();
yItem.data.push(+v);
}
}
// 绘制图数据
kpiChart.value.setOption({
xAxis: {
data: chartDataXAxisData,
},
series: chartDataYSeriesData,
});
}
onMounted(() => {
// 目前支持的 AMF AUSF MME MOCNGW NSSF SMF UDM UPF PCF
// 获取网元网元列表
neInfoStore.fnNelist().then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
// 过滤不可用的网元
neCascaderOptions.value = neInfoStore.getNeCascaderOptions.filter(
(item: any) => {
return !['OMC', 'NSSF', 'NEF', 'NRF', 'LMF', 'N3IWF'].includes(
item.value
);
}
);
if (neCascaderOptions.value.length === 0) {
message.warning({
content: t('common.noData'),
duration: 2,
});
return;
}
// 无查询参数neType时 默认选择UPF
const queryNeType = (route.query.neType as string) || 'UPF';
const item = neCascaderOptions.value.find(s => s.value === queryNeType);
if (item && item.children) {
const info = item.children[0];
state.neType = [info.neType, info.neId];
queryParams.neType = info.neType;
queryParams.neId = info.neId;
} else {
const info = neCascaderOptions.value[0].children[0];
state.neType = [info.neType, info.neId];
queryParams.neType = info.neType;
queryParams.neId = info.neId;
}
// 查询当前小时
const now = new Date();
now.setMinutes(0, 0, 0);
queryRangePicker.value[0] = parseDateToStr(now.getTime());
now.setMinutes(59, 59, 59);
queryRangePicker.value[1] = parseDateToStr(now.getTime());
fnGetListTitle();
// 绘图
fnRanderChart();
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
});
});
onBeforeUnmount(() => {
ws.close();
});
</script>
<template>
<PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParamsFrom" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
name="neType"
:label="t('views.ne.common.neType')"
>
<a-cascader
v-model:value="state.neType"
:options="neCascaderOptions"
:allow-clear="false"
:placeholder="t('common.selectPlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="10" :md="12" :xs="24">
<a-form-item
:label="t('views.perfManage.goldTarget.timeFrame')"
name="timeFrame"
>
<a-range-picker
v-model:value="queryRangePicker"
bordered
:allow-clear="false"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 100%"
></a-range-picker>
</a-form-item>
</a-col>
<a-col :lg="2" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button
type="primary"
:loading="tableState.loading"
@click.prevent="fnGetListTitle()"
>
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
<a-button
type="primary"
:loading="tableState.loading"
@click.prevent="fnChangShowType()"
>
<template #icon> <AreaChartOutlined /> </template>
{{
tableState.showTable
? t('views.perfManage.goldTarget.kpiChartTitle')
: t('views.perfManage.goldTarget.kpiTableTitle')
}}
</a-button>
<a-button
type="dashed"
:loading="tableState.loading"
@click.prevent="fnRecordExport()"
v-show="tableState.showTable"
>
<template #icon>
<ExportOutlined />
</template>
{{ t('common.export') }}
</a-button>
</a-space>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center" v-show="tableState.showTable">
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<TableColumnsDnd
v-if="tableColumns.length > 0"
:cache-id="`kpiTarget_${state.neType[0]}`"
:columns="tableColumns"
v-model:columns-dnd="tableColumnsDnd"
></TableColumnsDnd>
<a-tooltip>
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click" placement="bottomRight">
<a-button type="text">
<template #icon><ColumnHeightOutlined /></template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
<a-menu-item key="middle">
{{ t('common.size.middle') }}
</a-menu-item>
<a-menu-item key="small">
{{ t('common.size.small') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
<a-form layout="inline" v-show="!tableState.showTable">
<a-form-item
:label="t('views.perfManage.goldTarget.showChartSelected')"
name="chartLegendSelectedFlag"
>
<a-switch
:disabled="tableState.loading"
v-model:checked="state.chartLegendSelectedFlag"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
@change="fnLegendSelected"
size="small"
/>
</a-form-item>
<a-form-item
:label="t('views.perfManage.goldTarget.realTimeData')"
name="chartRealTime"
>
<a-switch
:disabled="tableState.loading"
v-model:checked="state.chartRealTime"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
@change="fnRealTimeSwitch"
size="small"
/>
</a-form-item>
</a-form>
</template>
<!-- 表格列表 -->
<a-table
v-show="tableState.showTable"
class="table"
row-key="id"
:columns="tableColumnsDnd"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumnsDnd.length * 200, y: 'calc(100vh - 480px)' }"
@resizeColumn="(w:number, col:any) => (col.width = w)"
:show-expand-column="false"
@change="fnTableChange"
>
</a-table>
<!-- 图表 -->
<div style="padding: 24px" v-show="!tableState.showTable">
<div ref="kpiChartDom" style="height: 450px; width: 100%"></div>
</div>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped></style>

View File

@@ -1,543 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted, toRaw } from 'vue'; import { onMounted } from 'vue';
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import { Modal } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/lib/table';
import { parseDateToStr } from '@/utils/date-utils';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { saveAs } from 'file-saver';
import useI18n from '@/hooks/useI18n';
import { getTraceRawInfo, listTraceData } from '@/api/traceManage/analysis';
const { t } = useI18n();
/**查询参数 */ onMounted(() => {});
let queryParams = reactive({
/**移动号 */
imsi: '',
/**移动号 */
msisdn: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
imsi: '',
pageNum: 1,
pageSize: 20,
});
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: object[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: true,
data: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('views.traceManage.analysis.trackTaskId'),
dataIndex: 'taskId',
align: 'center',
},
{
title: t('views.traceManage.analysis.imsi'),
dataIndex: 'imsi',
align: 'center',
},
{
title: t('views.traceManage.analysis.msisdn'),
dataIndex: 'msisdn',
align: 'center',
},
{
title: t('views.traceManage.analysis.srcIp'),
dataIndex: 'srcAddr',
align: 'center',
},
{
title: t('views.traceManage.analysis.dstIp'),
dataIndex: 'dstAddr',
align: 'center',
},
{
title: t('views.traceManage.analysis.signalType'),
dataIndex: 'ifType',
align: 'center',
},
{
title: t('views.traceManage.analysis.msgType'),
dataIndex: 'msgType',
align: 'center',
},
{
title: t('views.traceManage.analysis.msgDirect'),
dataIndex: 'msgDirect',
align: 'center',
},
{
title: t('views.traceManage.analysis.rowTime'),
dataIndex: 'timestamp',
align: 'center',
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value);
},
},
{
title: t('common.operate'),
key: 'id',
align: 'center',
},
];
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**查询备份信息列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
listTraceData(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
tablePagination.total = res.total;
tableState.data = res.rows;
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
}
tableState.loading = false;
});
}
/**抽屉对象信息状态类型 */
type ModalStateType = {
/**抽屉框是否显示 */
visible: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
};
/**抽屉对象信息状态 */
let modalState: ModalStateType = reactive({
visible: false,
title: '',
from: {
rawData: '',
rawDataHTML: '',
downBtn: false,
},
});
/**
* 对话框弹出显示
* @param row 记录信息
*/
function fnModalVisible(row: Record<string, any>) {
// 进制转数据
const hexString = parseBase64Data(row.rawMsg);
const rawData = convertToReadableFormat(hexString);
modalState.from.rawData = rawData;
// RAW解析HTML
getTraceRawInfo(row.id).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
const htmlString = rawDataHTMLScript(res.msg);
modalState.from.rawDataHTML = htmlString;
modalState.from.downBtn = true;
} else {
modalState.from.rawDataHTML = t('views.traceManage.analysis.noData');
}
});
modalState.title = t('views.traceManage.analysis.taskTitle', {
num: row.imsi,
});
modalState.visible = true;
}
/**
* 对话框弹出关闭
*/
function fnModalVisibleClose() {
modalState.visible = false;
modalState.from.downBtn = false;
modalState.from.rawDataHTML = '';
modalState.from.rawData = '';
}
// 将Base64编码解码为字节数组
function parseBase64Data(hexData: string) {
// 将Base64编码解码为字节数组
const byteString = atob(hexData);
const byteArray = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++) {
byteArray[i] = byteString.charCodeAt(i);
}
// 将每一个字节转换为2位16进制数表示并拼接起来
let hexString = '';
for (let i = 0; i < byteArray.length; i++) {
const hex = byteArray[i].toString(16);
hexString += hex.length === 1 ? '0' + hex : hex;
}
return hexString;
}
// 转换十六进制字节流为可读格式和ASCII码表示
function convertToReadableFormat(hexString: string) {
let result = '';
let asciiResult = '';
let arr = [];
let row = 100;
for (let i = 0; i < hexString.length; i += 2) {
const hexChars = hexString.substring(i, i + 2);
const decimal = parseInt(hexChars, 16);
const asciiChar =
decimal >= 32 && decimal <= 126 ? String.fromCharCode(decimal) : '.';
result += hexChars + ' ';
asciiResult += asciiChar;
if ((i + 2) % 32 === 0) {
arr.push({
row: row,
code: result,
asciiText: asciiResult,
});
result = '';
asciiResult = '';
row += 10;
}
if (2 + i == hexString.length) {
arr.push({
row: row,
code: result,
asciiText: asciiResult,
});
result = '';
asciiResult = '';
row += 10;
}
}
return arr;
}
// 信息详情HTMl内容处理
function rawDataHTMLScript(htmlString: string) {
// 删除所有 <a> 标签
// const withoutATags = htmlString.replace(/<a\b[^>]*>(.*?)<\/a>/gi, '');
// 删除所有 <script> 标签
let withoutScriptTags = htmlString.replace(
/<script\b[^>]*>([\s\S]*?)<\/script>/gi,
''
);
// 默认全展开
// const withoutHiddenElements = withoutScriptTags.replace(
// /style="display:none"/gi,
// 'style="background:#ffffff"'
// );
function set_node(node: any, str: string) {
if (!node) return;
node.style.display = str;
node.style.background = '#ffffff';
}
Reflect.set(window, 'set_node', set_node);
function toggle_node(node: any) {
node = document.getElementById(node);
if (!node) return;
set_node(node, node.style.display != 'none' ? 'none' : 'block');
}
Reflect.set(window, 'toggle_node', toggle_node);
function hide_node(node: any) {
node = document.getElementById(node);
if (!node) return;
set_node(node, 'none');
}
Reflect.set(window, 'hide_node', hide_node);
// 展开第一个
withoutScriptTags = withoutScriptTags.replace(
'id="f1c" style="display:none"',
'id="f1c" style="display:block"'
);
return withoutScriptTags;
}
/**信息文件下载 */
function fnDownloadFile() {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.traceManage.analysis.taskDownTip'),
onOk() {
const blob = new Blob([modalState.from.rawDataHTML], {
type: 'text/plain',
});
saveAs(blob, `${modalState.title}_${Date.now()}.html`);
},
});
}
onMounted(() => {
// 获取列表数据
fnGetList();
});
</script> </script>
<template> <template>
<PageContainer> <PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.traceManage.analysis.imsi')"
name="imsi"
>
<a-input
v-model:value="queryParams.imsi"
:allow-clear="true"
:placeholder="t('views.traceManage.analysis.imsiPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.traceManage.analysis.msisdn')"
name="imsi"
>
<a-input
v-model:value="queryParams.msisdn"
:allow-clear="true"
:placeholder="t('views.traceManage.analysis.msisdnPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }"> <a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 --> <h1>Perf Report</h1>
<template #title> </template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click">
<a-button type="text">
<template #icon><ColumnHeightOutlined /></template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">{{
t('common.size.default')
}}</a-menu-item>
<a-menu-item key="middle">{{
t('common.size.middle')
}}</a-menu-item>
<a-menu-item key="small">{{
t('common.size.small')
}}</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: true }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>查看详情</template>
<a-button type="link" @click.prevent="fnModalVisible(record)">
<template #icon><ProfileOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
</a-table>
</a-card> </a-card>
<!-- 详情框 -->
<ProModal
:drag="true"
:width="800"
:title="modalState.title"
:visible="modalState.visible"
@cancel="fnModalVisibleClose"
>
<div class="raw-title">
{{ t('views.traceManage.analysis.signalData') }}
</div>
<a-row
class="raw"
:gutter="16"
v-for="v in modalState.from.rawData"
:key="v.row"
>
<a-col class="num" :span="2">{{ v.row }}</a-col>
<a-col class="code" :span="12">{{ v.code }}</a-col>
<a-col class="txt" :span="10">{{ v.asciiText }}</a-col>
</a-row>
<a-divider />
<div class="raw-title">
{{ t('views.traceManage.analysis.signalDetail') }}
<a-button
type="dashed"
size="small"
@click.prevent="fnDownloadFile"
v-if="modalState.from.downBtn"
>
<template #icon>
<DownloadOutlined />
</template>
{{ t('views.traceManage.analysis.taskDownText') }}
</a-button>
</div>
<div class="raw-html" v-html="modalState.from.rawDataHTML"></div>
</ProModal>
</PageContainer> </PageContainer>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped></style>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
.raw {
&-title {
color: #000000d9;
font-size: 24px;
line-height: 1.8;
}
.num {
background-color: #e5e5e5;
}
.code {
background-color: #e7e6ff;
}
.txt {
background-color: #ffe3e5;
}
&-html {
max-height: 300px;
overflow-y: auto;
}
}
</style>

View File

@@ -1,543 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, onMounted, toRaw } from 'vue'; import { onMounted } from 'vue';
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import { Modal } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/lib/table';
import { parseDateToStr } from '@/utils/date-utils';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { saveAs } from 'file-saver';
import useI18n from '@/hooks/useI18n';
import { getTraceRawInfo, listTraceData } from '@/api/traceManage/analysis';
const { t } = useI18n();
/**查询参数 */ onMounted(() => {});
let queryParams = reactive({
/**移动号 */
imsi: '',
/**移动号 */
msisdn: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
imsi: '',
pageNum: 1,
pageSize: 20,
});
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: object[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: true,
data: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('views.traceManage.analysis.trackTaskId'),
dataIndex: 'taskId',
align: 'center',
},
{
title: t('views.traceManage.analysis.imsi'),
dataIndex: 'imsi',
align: 'center',
},
{
title: t('views.traceManage.analysis.msisdn'),
dataIndex: 'msisdn',
align: 'center',
},
{
title: t('views.traceManage.analysis.srcIp'),
dataIndex: 'srcAddr',
align: 'center',
},
{
title: t('views.traceManage.analysis.dstIp'),
dataIndex: 'dstAddr',
align: 'center',
},
{
title: t('views.traceManage.analysis.signalType'),
dataIndex: 'ifType',
align: 'center',
},
{
title: t('views.traceManage.analysis.msgType'),
dataIndex: 'msgType',
align: 'center',
},
{
title: t('views.traceManage.analysis.msgDirect'),
dataIndex: 'msgDirect',
align: 'center',
},
{
title: t('views.traceManage.analysis.rowTime'),
dataIndex: 'timestamp',
align: 'center',
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value);
},
},
{
title: t('common.operate'),
key: 'id',
align: 'center',
},
];
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**查询备份信息列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
listTraceData(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
tablePagination.total = res.total;
tableState.data = res.rows;
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
}
tableState.loading = false;
});
}
/**抽屉对象信息状态类型 */
type ModalStateType = {
/**抽屉框是否显示 */
visible: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
};
/**抽屉对象信息状态 */
let modalState: ModalStateType = reactive({
visible: false,
title: '',
from: {
rawData: '',
rawDataHTML: '',
downBtn: false,
},
});
/**
* 对话框弹出显示
* @param row 记录信息
*/
function fnModalVisible(row: Record<string, any>) {
// 进制转数据
const hexString = parseBase64Data(row.rawMsg);
const rawData = convertToReadableFormat(hexString);
modalState.from.rawData = rawData;
// RAW解析HTML
getTraceRawInfo(row.id).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
const htmlString = rawDataHTMLScript(res.msg);
modalState.from.rawDataHTML = htmlString;
modalState.from.downBtn = true;
} else {
modalState.from.rawDataHTML = t('views.traceManage.analysis.noData');
}
});
modalState.title = t('views.traceManage.analysis.taskTitle', {
num: row.imsi,
});
modalState.visible = true;
}
/**
* 对话框弹出关闭
*/
function fnModalVisibleClose() {
modalState.visible = false;
modalState.from.downBtn = false;
modalState.from.rawDataHTML = '';
modalState.from.rawData = '';
}
// 将Base64编码解码为字节数组
function parseBase64Data(hexData: string) {
// 将Base64编码解码为字节数组
const byteString = atob(hexData);
const byteArray = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++) {
byteArray[i] = byteString.charCodeAt(i);
}
// 将每一个字节转换为2位16进制数表示并拼接起来
let hexString = '';
for (let i = 0; i < byteArray.length; i++) {
const hex = byteArray[i].toString(16);
hexString += hex.length === 1 ? '0' + hex : hex;
}
return hexString;
}
// 转换十六进制字节流为可读格式和ASCII码表示
function convertToReadableFormat(hexString: string) {
let result = '';
let asciiResult = '';
let arr = [];
let row = 100;
for (let i = 0; i < hexString.length; i += 2) {
const hexChars = hexString.substring(i, i + 2);
const decimal = parseInt(hexChars, 16);
const asciiChar =
decimal >= 32 && decimal <= 126 ? String.fromCharCode(decimal) : '.';
result += hexChars + ' ';
asciiResult += asciiChar;
if ((i + 2) % 32 === 0) {
arr.push({
row: row,
code: result,
asciiText: asciiResult,
});
result = '';
asciiResult = '';
row += 10;
}
if (2 + i == hexString.length) {
arr.push({
row: row,
code: result,
asciiText: asciiResult,
});
result = '';
asciiResult = '';
row += 10;
}
}
return arr;
}
// 信息详情HTMl内容处理
function rawDataHTMLScript(htmlString: string) {
// 删除所有 <a> 标签
// const withoutATags = htmlString.replace(/<a\b[^>]*>(.*?)<\/a>/gi, '');
// 删除所有 <script> 标签
let withoutScriptTags = htmlString.replace(
/<script\b[^>]*>([\s\S]*?)<\/script>/gi,
''
);
// 默认全展开
// const withoutHiddenElements = withoutScriptTags.replace(
// /style="display:none"/gi,
// 'style="background:#ffffff"'
// );
function set_node(node: any, str: string) {
if (!node) return;
node.style.display = str;
node.style.background = '#ffffff';
}
Reflect.set(window, 'set_node', set_node);
function toggle_node(node: any) {
node = document.getElementById(node);
if (!node) return;
set_node(node, node.style.display != 'none' ? 'none' : 'block');
}
Reflect.set(window, 'toggle_node', toggle_node);
function hide_node(node: any) {
node = document.getElementById(node);
if (!node) return;
set_node(node, 'none');
}
Reflect.set(window, 'hide_node', hide_node);
// 展开第一个
withoutScriptTags = withoutScriptTags.replace(
'id="f1c" style="display:none"',
'id="f1c" style="display:block"'
);
return withoutScriptTags;
}
/**信息文件下载 */
function fnDownloadFile() {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.traceManage.analysis.taskDownTip'),
onOk() {
const blob = new Blob([modalState.from.rawDataHTML], {
type: 'text/plain',
});
saveAs(blob, `${modalState.title}_${Date.now()}.html`);
},
});
}
onMounted(() => {
// 获取列表数据
fnGetList();
});
</script> </script>
<template> <template>
<PageContainer> <PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.traceManage.analysis.imsi')"
name="imsi"
>
<a-input
v-model:value="queryParams.imsi"
:allow-clear="true"
:placeholder="t('views.traceManage.analysis.imsiPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.traceManage.analysis.msisdn')"
name="imsi"
>
<a-input
v-model:value="queryParams.msisdn"
:allow-clear="true"
:placeholder="t('views.traceManage.analysis.msisdnPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }"> <a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 --> <h1>Perf Set</h1>
<template #title> </template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click">
<a-button type="text">
<template #icon><ColumnHeightOutlined /></template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">{{
t('common.size.default')
}}</a-menu-item>
<a-menu-item key="middle">{{
t('common.size.middle')
}}</a-menu-item>
<a-menu-item key="small">{{
t('common.size.small')
}}</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: true }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>查看详情</template>
<a-button type="link" @click.prevent="fnModalVisible(record)">
<template #icon><ProfileOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
</a-table>
</a-card> </a-card>
<!-- 详情框 -->
<ProModal
:drag="true"
:width="800"
:title="modalState.title"
:visible="modalState.visible"
@cancel="fnModalVisibleClose"
>
<div class="raw-title">
{{ t('views.traceManage.analysis.signalData') }}
</div>
<a-row
class="raw"
:gutter="16"
v-for="v in modalState.from.rawData"
:key="v.row"
>
<a-col class="num" :span="2">{{ v.row }}</a-col>
<a-col class="code" :span="12">{{ v.code }}</a-col>
<a-col class="txt" :span="10">{{ v.asciiText }}</a-col>
</a-row>
<a-divider />
<div class="raw-title">
{{ t('views.traceManage.analysis.signalDetail') }}
<a-button
type="dashed"
size="small"
@click.prevent="fnDownloadFile"
v-if="modalState.from.downBtn"
>
<template #icon>
<DownloadOutlined />
</template>
{{ t('views.traceManage.analysis.taskDownText') }}
</a-button>
</div>
<div class="raw-html" v-html="modalState.from.rawDataHTML"></div>
</ProModal>
</PageContainer> </PageContainer>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped></style>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
.raw {
&-title {
color: #000000d9;
font-size: 24px;
line-height: 1.8;
}
.num {
background-color: #e5e5e5;
}
.code {
background-color: #e7e6ff;
}
.txt {
background-color: #ffe3e5;
}
&-html {
max-height: 300px;
overflow-y: auto;
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More