457 Commits

Author SHA1 Message Date
zhongzm
ae1dad1fc3 feat:语音仪表盘 2025-07-16 11:09:51 +08:00
TsMask
4c6e90c5c4 chore: 更新版本号 2.250509 2025-05-09 19:40:09 +08:00
TsMask
e98ee3ee1e fix: 锁屏密码dom错误 2025-04-22 14:35:20 +08:00
TsMask
e4628040c9 chore: 依赖版本更新 2025-04-22 14:22:45 +08:00
TsMask
343b1612aa style: 补充多语言翻译 2025-04-22 14:22:31 +08:00
TsMask
73eb70b7d8 fix: 网元OAM下发开关控制重启 2025-04-22 14:22:17 +08:00
TsMask
ceea517613 fix: 锁屏密码base处理,无密码进入 2025-04-22 14:22:06 +08:00
TsMask
6ab4e80b38 fix: 时间改用R3399格式 2025-04-22 14:21:46 +08:00
TsMask
e2cf4b6500 feat: ws心跳消息 2025-04-22 14:21:36 +08:00
TsMask
3896b61b13 feat: 网元信令跟踪功能 2025-04-22 14:21:20 +08:00
lai
ffced06df8 del 2025-04-15 16:54:29 +08:00
lai
4e63395383 改造成mf 2025-04-15 16:24:44 +08:00
lai
956cbfc3a3 title 2025-04-15 16:05:32 +08:00
lai
f5b843d9a8 title 2025-04-15 16:05:13 +08:00
lai
1246308a3d psap demp 2025-04-15 16:02:31 +08:00
TsMask
0cb7158f57 chore: 更新版本号 2.250412 2025-04-12 10:09:46 +08:00
TsMask
63c7ae2538 fix: 看板用户数初始neId传入失败,禁止选择当前项 2025-04-12 09:57:36 +08:00
zhongzm
48ddafaec9 feat:UE的Export界面 2025-04-08 15:58:21 +08:00
lai
eeeae3dd12 修复udm数据量被双层叠加 2025-04-03 10:15:21 +08:00
TsMask
f0a5da681c chore: 更新版本号 2.250331 2025-03-31 20:03:35 +08:00
lai
35c7b86865 UDM用户看板 2025-03-26 20:38:51 +08:00
lai
cef90a49f9 KPI更新 2025-03-26 20:18:25 +08:00
TsMask
c9a0fd7818 fix: 看板用户数切换展示 2025-03-25 10:52:11 +08:00
TsMask
7e35dca9d8 fix: KPI总览无数据时展示title 2025-03-25 10:52:00 +08:00
TsMask
860e06e7b0 chore: 更新版本号 2.250321 2025-03-21 18:00:18 +08:00
TsMask
b352533523 fix: 网元配置改回原先单网元配置 2025-03-21 17:59:34 +08:00
TsMask
a8a5c0a31e fix: 禁止admin修改菜单分配 2025-03-21 17:06:51 +08:00
TsMask
26686f88db feat: SMF数据单位转换MB显示 2025-03-21 16:39:58 +08:00
TsMask
fb3f1daecf feat: PCAP文件目录下载目录为ZIP文件功能 2025-03-21 16:30:03 +08:00
TsMask
3680da64c1 feat: 看板UPF流量总计7or30天 2025-03-21 16:23:57 +08:00
TsMask
f87fcb73b9 fix: 增加文件下载超时时间至600秒 2025-03-21 16:23:43 +08:00
TsMask
c11227d747 style: 用户列表时间列宽200px 2025-03-21 16:18:17 +08:00
TsMask
8a612e0760 fix: 告警事件导出异常/告警ID列移除 2025-03-21 16:17:35 +08:00
TsMask
29f5e41976 fix: 告警时间转换导致查询修改错误 2025-03-21 16:10:27 +08:00
TsMask
3ab0b47095 fix: pcap分析Protocol列换行问题 2025-03-21 16:02:41 +08:00
TsMask
aa04abdbb4 fix: 自定义指标只有UPF显示sum列其他网元隐藏 2025-03-21 16:02:04 +08:00
TsMask
db95099934 fix: 禁止admin修改菜单分配 2025-03-21 16:01:21 +08:00
TsMask
71ef748af8 chore: 更新版本号 2.250314 2025-03-14 11:03:32 +08:00
TsMask
91c9829d77 style: 优化PacketTable组件的滚动条样式和布局 2025-03-11 16:03:36 +08:00
TsMask
5304b298f6 fix: 网元许可调整表格列宽和图标样式 2025-03-11 16:01:33 +08:00
TsMask
e09369aa5a style: 多语言翻译安装-Install 2025-03-11 15:59:05 +08:00
TsMask
3363e36669 fix: 总览数值累加/基站跳转页面修改 2025-03-11 15:50:09 +08:00
TsMask
ea2ce56e52 feat: 更新数据获取逻辑并添加数据使用情况展示 2025-03-11 15:49:45 +08:00
TsMask
dee60e0699 feat: 添加带时区的时间格式支持 2025-03-11 15:41:59 +08:00
TsMask
e62fc0c039 fix: 系统菜单按钮权限状态可修改 2025-03-08 14:23:54 +08:00
TsMask
de16b96971 chore: 更新版本号 2.250308 2025-03-08 11:02:23 +08:00
TsMask
f0e34726ec feat: 添加软件包文件未发现提示信息 2025-03-05 11:11:58 +08:00
TsMask
2cbd2e0aa7 fix: 看板UPF切换问题 2025-03-04 16:59:16 +08:00
TsMask
16913aa721 feat: 看板权限控制部分显示 2025-03-04 16:05:30 +08:00
TsMask
ba426d7737 chore: 更新版本号 2.250228 2025-02-28 19:54:51 +08:00
TsMask
8df5e278c8 feat: 添加时间范围选择器,支持快速选择当前小时、今天和昨天 2025-02-25 14:53:18 +08:00
TsMask
ea8fb7cad2 chore: 更新版本号 2.250221 2025-02-21 21:07:35 +08:00
TsMask
b7b8b11860 fix: 兼容3G的SGWC字段 2025-02-21 15:46:55 +08:00
TsMask
bae61108be chore: 编译类型错误 2025-02-20 19:24:06 +08:00
TsMask
f60e530636 sytle: 多语言EN更新 2025-02-20 10:21:28 +08:00
TsMask
b3e9761305 fix: SGWC-CDR字段变更SGSN/GGSN 2025-02-20 10:20:44 +08:00
TsMask
fbd2867ad2 chore: 更新版本号 2.250214 2025-02-14 19:42:30 +08:00
TsMask
2ca23ad99a fix: 网元SMF配置选择UPF下拉框数据获取失败 2025-02-14 19:33:09 +08:00
lai
4b032d74be 优化KPI展示 2025-02-13 19:32:04 +08:00
TsMask
8bfa73a67a fix: 导出备份配置调整 2025-02-11 18:33:47 +08:00
TsMask
f188e193f3 style: 调整UDM用户数据新增表单排版 2025-02-11 17:52:02 +08:00
TsMask
b362855a60 fix: 信令pcap解析列表颜色转换问题 2025-02-11 11:28:33 +08:00
TsMask
4c28d6b98c style: 调整UDM鉴权新增表单排版 2025-02-10 10:02:13 +08:00
TsMask
2276f2281a chore: 更新版本号 2.250208 2025-02-08 19:40:36 +08:00
TsMask
7ccb580e91 feat: 基站状态导入功能 2025-02-08 19:39:47 +08:00
TsMask
0346dfd584 chore: 更新依赖版 2025-02-08 18:08:05 +08:00
TsMask
5a64afe209 feat: 基站状态记录上报和导出功能 2025-02-08 18:07:46 +08:00
TsMask
fd82d710b6 feat: UDM用户数据导入输出失败记录,UDM2.2502.58 2025-02-07 16:00:52 +08:00
TsMask
fbc1535015 chore: 更新版本号 2.250124 2025-01-24 20:29:06 +08:00
TsMask
36de89570f fix: 网元状态切换保留List页面状态 2025-01-24 20:28:26 +08:00
TsMask
22e595131c feat: 基站状态添加MME4G状态 2025-01-24 09:38:55 +08:00
TsMask
208d14d65a fix: SMF-CDR时间可选查询范围,无数据loading关闭 2025-01-20 20:23:40 +08:00
TsMask
80b9cd83fb style: 修改kpi表头提示,关闭排序 2025-01-20 20:22:02 +08:00
TsMask
721ec4a5da feat: 日志备份FTP服务配置项 2025-01-20 17:24:05 +08:00
TsMask
34f558199a chore: 更新版本号 2.250117 2025-01-17 18:24:09 +08:00
TsMask
07eab9378a fix: 接口加密参数控制开关 2025-01-17 15:54:01 +08:00
TsMask
806cbbd9ed style: KPI数据表格头提示信息 2025-01-17 15:30:22 +08:00
TsMask
8adf2a3dd0 fix: 基站状态条件查询时只显示当前状态数量 2025-01-17 15:29:44 +08:00
TsMask
2164ffc9b2 fix: SMF-CDR查询IMSI数据结果图优化,支持DNN条件 2025-01-17 15:28:33 +08:00
TsMask
7091f1ffa6 fix: KPI指标表格头提示信息 2025-01-16 20:56:09 +08:00
TsMask
fa44f6abe0 fix: 基站状态列宽拖动,状态结果统计 2025-01-16 20:50:50 +08:00
TsMask
8586d7f1ce fix: 基站状态页面编辑判断错误 2025-01-16 14:27:51 +08:00
TsMask
b2d818fc30 style: 依赖库无类型声明定义 2025-01-15 21:08:22 +08:00
TsMask
a20d5ee99f fix: 网元信息修改局部更新状态判断 2025-01-15 21:07:44 +08:00
TsMask
74b55423d5 style: 移除l部分无用的log输出 2025-01-15 21:07:17 +08:00
TsMask
96acbc0919 fix: 网元状态概览页面定时刷新异常错误 2025-01-15 18:42:42 +08:00
TsMask
322b5f18ed fix: 基站状态显示设备名和在线用户数 2025-01-15 17:55:19 +08:00
TsMask
e36dac9b81 fix: KPI指标表格头提示信息 2025-01-15 17:13:38 +08:00
TsMask
8214175890 fix: 基站状态页面翻译和部分优化 2025-01-15 17:12:51 +08:00
TsMask
9e55768312 chore: 更新版本号 2.250110 2025-01-10 19:38:58 +08:00
TsMask
98ed8adfe3 feat: 网元状态3待机显示 2025-01-10 19:33:06 +08:00
TsMask
f5938110f4 fix: 去除系统用户密码重置账号校验 2025-01-10 18:45:16 +08:00
TsMask
acd8a33b4a chore: 更新版本号 2.250103 2025-01-03 21:52:54 +08:00
TsMask
7ab2b3b546 chore: 更新版本号 2.240103 2025-01-03 21:13:17 +08:00
TsMask
b490e4f5b9 chore: 更新依赖版 2025-01-03 21:09:51 +08:00
TsMask
fe82336937 feat: 基站状态页面及拓扑展示页面功能实现 2025-01-03 21:09:16 +08:00
TsMask
35a7ed5b35 chore: 更新版本号 2.241228 2024-12-28 10:55:43 +08:00
TsMask
56def56b58 Merge remote-tracking branch 'origin/lichang' 2024-12-28 10:55:04 +08:00
TsMask
6074078f5d fix: 网元数据接口查询超时时间改为60s 2024-12-28 10:11:24 +08:00
TsMask
428adb5186 fix: 自定义指标查询超时60s 2024-12-27 19:09:28 +08:00
TsMask
1cbce9ad03 feat: UE数据列表统一格式 2024-12-27 19:08:58 +08:00
TsMask
2138896d43 fix: 网元类型选择框警告状态修复 2024-12-27 19:07:02 +08:00
TsMask
c40ee9c8cc fix: 看板重复获取UE修复和AMF-UE数据结构变更调整 2024-12-27 19:06:12 +08:00
TsMask
d33183ca5e style: SMF-CDR流量报表页面 2024-12-26 20:11:34 +08:00
TsMask
9ff9529402 Merge remote-tracking branch 'origin/lichang' 2024-12-26 18:56:13 +08:00
TsMask
51a8d6d3a0 feat: SMF-CDR用户流量使用情况图表展示 2024-12-26 18:40:36 +08:00
TsMask
c22663505c fix: SMF-CDR去除RatingGroup区分 2024-12-26 18:39:09 +08:00
TsMask
9b589a0e69 Merge remote-tracking branch 'origin/lichang' 2024-12-25 17:50:10 +08:00
TsMask
b0b9c69ad2 style: 网元信息服务区域默认Area 2024-12-24 20:37:09 +08:00
TsMask
5a3fa2a6ba fix: 网元配置类型切换清空下拉树 2024-12-24 19:49:39 +08:00
lai
5c0909e356 修改切换条件 2024-12-24 18:38:47 +08:00
TsMask
b7da976819 fix: 看板UDM签约数据获取x2问题 2024-12-23 20:05:13 +08:00
TsMask
41e37766b7 feat: SMSC短信内容显示,权限控制显示操作删除cdr:ne:remove 2024-12-23 19:39:06 +08:00
TsMask
44612081bc chore: 更新版本号 2.241220 2024-12-20 18:36:52 +08:00
TsMask
4929ed30bc Merge remote-tracking branch 'origin/lichang' 2024-12-20 18:31:28 +08:00
TsMask
eea4e0069d feat: CDR页面切换网元重置查询 2024-12-20 16:36:33 +08:00
lai
2b69b8d72b 增加仪表盘累加及UPF下拉框 2024-12-20 15:47:17 +08:00
TsMask
1f130098ee style: 加载缺省空字符 2024-12-20 15:45:07 +08:00
TsMask
5a4ab62e97 fix: CDR数据检查格式,SGWC调试 2024-12-20 15:43:44 +08:00
TsMask
139a14fd3d feat: 网元总览点击状态显示页面调整 2024-12-19 20:24:46 +08:00
TsMask
6e7402fd63 feat: 网元配置多网元同时配置HA功能优化选择 2024-12-19 20:24:29 +08:00
TsMask
302ea84cde style: 看板UDM-签约数量参数调整 2024-12-19 11:17:06 +08:00
TsMask
7d470fd681 style: 网元列表加载同时清除缓存 2024-12-19 11:14:55 +08:00
TsMask
b64c4c66ab style: 网元类型列表静态可选排序 2024-12-19 11:11:54 +08:00
TsMask
15ac549532 feat: 新增SGWC-CDR页面 2024-12-19 11:11:16 +08:00
TsMask
9bff669769 fix: 多语言删除无用定义 2024-12-18 15:37:21 +08:00
TsMask
8a53ac8b9f feat: 网元配置多网元同时配置HA功能 2024-12-18 15:33:52 +08:00
TsMask
d3a18f95db style: CDR/UE展开详情布局调整 2024-12-18 15:32:36 +08:00
TsMask
11649c3fb1 fix: SMF-在线订阅用户列表信息 2024-12-18 15:31:27 +08:00
TsMask
09fd8bc4dc style: smsc时间显示列宽度200px 2024-12-16 11:25:30 +08:00
TsMask
5a704146a5 fix: 角色分配菜单勾选父子级联转出子节点关联根节点数组 2024-12-16 11:07:50 +08:00
TsMask
e25cd91df1 Merge remote-tracking branch 'origin/main' into lichang 2024-12-16 10:45:41 +08:00
TsMask
71f2e596fe chore: 编译依赖拆包manualChunks行为 2024-12-16 10:25:19 +08:00
TsMask
7e60f0dd05 chore: 编译依赖拆包manualChunks行为 2024-12-16 10:23:22 +08:00
TsMask
a94f9414a4 chore: 更新版本号 2.241213 2024-12-13 21:19:28 +08:00
TsMask
7f69bc69bc chore: 编译依赖拆包manualChunks行为 2024-12-13 18:25:52 +08:00
lai
4b1058cff3 完善表单赋值机制 2024-12-13 16:04:21 +08:00
lai
a6bab3fa0b 修复拆解smData异常问题 2024-12-13 15:28:15 +08:00
TsMask
c44fae8d13 style: UDM签约cag参数默认为空字符 2024-12-11 15:46:03 +08:00
TsMask
bcb214448c style: 在线基站列表网元类型切换时刷新 2024-12-11 15:45:07 +08:00
TsMask
65db17a319 fix: SMF-Data隐藏RatingGroup显示 2024-12-10 10:26:19 +08:00
TsMask
6969669027 fix: 拓扑网元状态15s一个周期 2024-12-10 10:25:39 +08:00
TsMask
38a698f07b fix: redis终端改为命令输入框,禁止窗口输入 2024-12-10 10:25:00 +08:00
TsMask
9f121505d1 fix: telnet终端输入回车值无法正确发出 2024-12-10 10:24:14 +08:00
TsMask
6add41254d chore: 更新版本号 2.241209 2024-12-09 10:52:54 +08:00
TsMask
85bc4aea53 fix: 页面解析错误 2024-12-09 10:51:54 +08:00
simonzhangsz
9167da1bc5 tsc check 2024-12-06 19:13:49 +08:00
lai
69dfc2a1f5 event Type异常 2024-12-06 18:10:06 +08:00
TsMask
b1a699252b fix: 免登录认证参数默认值 2024-12-06 17:36:36 +08:00
TsMask
a0886abd38 chore: 更新版本号 2.241130 2024-11-30 17:10:59 +08:00
lai
9383c17484 优化指标界面 2024-11-29 17:17:54 +08:00
zhongzm
512bd6d8eb fix:tooltip计算修复 2024-11-29 14:50:38 +08:00
zhongzm
b8b66fe610 fix:table排序 2024-11-28 20:09:19 +08:00
zhongzm
154569304c feat:添加多选 2024-11-28 18:57:35 +08:00
zhongzm
cc3432ca06 Merge remote-tracking branch 'origin/main' 2024-11-28 18:45:20 +08:00
zhongzm
68b9c5fa5e feat:添加累加值计算 2024-11-28 18:45:11 +08:00
TsMask
6620ac7279 fix: Para5G参数UPF新增网卡名 2024-11-28 15:49:23 +08:00
TsMask
a9a094d04a revet: 去除系统免登录加密参数data 2024-11-25 20:02:16 +08:00
TsMask
5d4a04ecf2 feat: 去除系统免登录加密参数data 2024-11-25 18:55:16 +08:00
TsMask
1b28260680 feat: 系统免登录参数路由跳转 2024-11-25 11:59:46 +08:00
zhongzm
83cb3f8728 test 2024-11-25 10:48:53 +08:00
zhongzm
7d69d3c21d fix:样式修复、明暗主题适应 2024-11-23 17:01:41 +08:00
TsMask
ebde50f58b chore: 更新版本号 2.241123 2024-11-23 16:40:10 +08:00
lai
b8924d161f 修正自定义指标 2024-11-22 18:05:17 +08:00
lai
ccb52ea94f 过滤无自定义指标的网元 2024-11-22 17:27:27 +08:00
lai
72abbe1c53 Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-11-22 17:07:55 +08:00
zhongzm
f318f61b4a fix:明暗主题随机颜色方法修复 2024-11-22 16:15:25 +08:00
zhongzm
45d8314e29 fix:暗色模式样式适应 2024-11-22 15:43:00 +08:00
zhongzm
886a1c8667 fix:修改取色范围适应暗色模式 2024-11-22 15:42:38 +08:00
lai
378729720d Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-11-22 10:41:11 +08:00
zhongzm
c2a3d4b8a8 fix:修改时间粒度,修改默认时间 2024-11-22 10:14:45 +08:00
zhongzm
8444de8e98 Merge remote-tracking branch 'origin/main' 2024-11-22 09:57:04 +08:00
zhongzm
874e01996a fix:样式调整,增加栅格线,曲线平滑,平均值计算 2024-11-22 09:56:51 +08:00
TsMask
fb855fd74e fix: 获取网元状态定时轮询修复 2024-11-21 18:19:07 +08:00
TsMask
78f963fbea fix: CDR-IMS去掉MOSM MTSM 2024-11-21 12:06:32 +08:00
TsMask
cf0116b5c6 fix: CDR时间的处理 2024-11-21 10:06:07 +08:00
lai
80b07c462f 避免x轴时间数组重复 2024-11-20 18:32:14 +08:00
zhongzm
5a8ab1343f fix:日期选择器属性弃用修复 2024-11-20 17:00:22 +08:00
zhongzm
67349e24d8 fix:日期选择器属性弃用修复 2024-11-20 16:59:36 +08:00
lai
f59697a2f2 Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-11-20 16:31:40 +08:00
lai
8c197bee04 回复 2024-11-20 16:31:28 +08:00
TsMask
f866fbf153 style: 网元总览显示用户容量 2024-11-20 12:01:12 +08:00
TsMask
d7b4fd3f71 fix: SMSC-CDR时间字段判断是否时间戳 2024-11-20 11:12:55 +08:00
zhongzm
29449cc597 Merge remote-tracking branch 'origin/main' 2024-11-19 17:21:03 +08:00
zhongzm
7615bccf04 fix:日期选择器添加快捷选项,tooltip显示修复 2024-11-19 17:20:49 +08:00
TsMask
f5f27d78f1 Merge remote-tracking branch 'origin/lichang' 2024-11-19 16:58:41 +08:00
TsMask
ebc46ff7d4 fix: 工具ipfer操作客户端host输入判断 2024-11-19 16:58:07 +08:00
lai
2513baf48e 修改中英文提示 2024-11-19 14:38:06 +08:00
lai
ac2483d690 Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-11-19 14:24:16 +08:00
lai
a85f87f3fc 居中显示tooltip 2024-11-19 14:23:16 +08:00
lai
68e002776c 完善数据处理 2024-11-19 14:09:37 +08:00
TsMask
9322f52c9a Merge remote-tracking branch 'origin/lichang' 2024-11-19 11:52:50 +08:00
lai
2c9807f9b8 默认全选 2024-11-19 11:36:03 +08:00
lai
f8f4dc0f2e Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-11-19 11:30:13 +08:00
lai
49bd59c639 回填 2024-11-19 11:28:49 +08:00
TsMask
6bd7a28458 fix: 看板初始用户活动各20条进行排序显示 2024-11-19 10:24:32 +08:00
TsMask
053517324d fix: 修复网元概览数据饼图状态异常 2024-11-19 10:23:43 +08:00
zhongzm
256802e698 fix:修复加载显示 2024-11-18 18:15:23 +08:00
zhongzm
851e8a461e fix:修复缩放异常 2024-11-18 10:39:55 +08:00
zhongzm
d8d49f23c4 fix:样式报错修复 2024-11-18 10:02:20 +08:00
TsMask
1511792e3b fix: 快速开站网元排序进行逐个安装 2024-11-15 19:53:45 +08:00
lai
045a25f3ae 删除漫游引导 2024-11-15 19:23:29 +08:00
TsMask
b6d1ba6766 chore: 更新版本号 2.241115 2024-11-15 18:18:42 +08:00
zhongzm
e8e0e07189 Merge remote-tracking branch 'origin/main' 2024-11-15 16:58:38 +08:00
zhongzm
1672c6c6ba feat:自定义指标漫游引导 2024-11-15 16:58:25 +08:00
TsMask
ac251c2c00 Merge remote-tracking branch 'origin/lichang' 2024-11-15 16:51:22 +08:00
lai
9b9c0b39fd 优化界面显示 2024-11-15 15:48:54 +08:00
zhongzm
31bca2b98f fix:修复图表tooltip显示被遮盖的问题 2024-11-15 14:32:53 +08:00
zhongzm
bf94591035 fix:图表生成时序调整 2024-11-15 14:31:58 +08:00
TsMask
d523b60311 fix: 登录页去除头尾栏,暗色背景修改 2024-11-15 10:30:00 +08:00
zhongzm
e0990a40df fix:实时数据文本显示 2024-11-14 20:39:23 +08:00
TsMask
20c1c455c4 fix: 修复TelInput组件号码无法解析问题 2024-11-14 20:08:40 +08:00
TsMask
dab76add73 style: 网元license操作图标调整 2024-11-14 14:18:52 +08:00
TsMask
101cb70893 style: 页面样式调整 2024-11-14 11:33:49 +08:00
TsMask
348b11e201 fix: 网元切换时命令操作信息过滤 2024-11-14 11:33:17 +08:00
zhongzm
a9fdda3f5e fix:清楚旧功能的多余代码 2024-11-13 17:12:12 +08:00
zhongzm
89d22e55c7 feat:关键指标图表界面重构(grafana标准) 2024-11-13 16:56:15 +08:00
zhongzm
40f2a78717 feat:添加表格排序功能 2024-11-13 10:26:59 +08:00
zhongzm
80ee1c05ff feat:关键指标概览界面重构(grafana标准) 2024-11-12 19:01:28 +08:00
TsMask
021f9f28f6 fix; SMSC-CDR时间格式调整 2024-11-12 10:12:32 +08:00
zhongzm
d7990a6ee5 feat:添加漫游式引导 2024-11-08 18:25:11 +08:00
TsMask
3561a5dc39 Merge remote-tracking branch 'origin/main' into lichang 2024-11-08 17:40:18 +08:00
TsMask
247a009eef chore: 更新版本号 2.241108 2024-11-08 17:39:06 +08:00
TsMask
fcd4db8217 feat: 快速安装UPF配置网卡名和驱动类型 2024-11-08 16:17:44 +08:00
TsMask
3e03d47520 style: 缓存管理边距样式 2024-11-08 16:05:21 +08:00
TsMask
414afea783 style: 调整MML执行日志表格样式 2024-11-08 16:04:51 +08:00
TsMask
df7c455881 fix: 多语言切换隐藏,导致无法切换主题色 2024-11-08 16:03:33 +08:00
lai
1644765ce2 补充 2024-11-08 15:24:07 +08:00
lai
db16cdb79b 回填信息 2024-11-08 14:55:19 +08:00
lai
800547d1ef 自定义指标优化 2024-11-08 12:00:20 +08:00
TsMask
5614be7877 fix: UDM用户数据加载速度优化并提示时间 2024-11-08 10:47:39 +08:00
TsMask
0644e49161 feat: 工具>主机终端操作服务器命令支持redis 2024-11-07 19:29:59 +08:00
lai
15b81eef97 首页菜单选择框为树状选择框 2024-11-07 14:10:00 +08:00
lai
5ddf83d1fd 改为时间戳且加上快捷时间选择 2024-11-07 14:09:26 +08:00
zhongzm
6326f46bf2 style:样式修改,暗黑色适应 2024-11-06 19:44:52 +08:00
zhongzm
f75719ca37 fix:修改日期选择器默认时间 2024-11-06 15:25:23 +08:00
TsMask
21cf86baff style: 去除console 2024-11-05 17:42:17 +08:00
TsMask
6583bc9972 style: 日志导出文件管理选择控件样式调整 2024-11-05 17:41:51 +08:00
TsMask
886ea37702 fix: iperf支持v2和v3的命令操作 2024-11-05 17:33:57 +08:00
TsMask
23116db988 fix: 终端命令显示查看组件输出命令到首行 2024-11-05 17:33:23 +08:00
zhongzm
8283523327 fix:修复实时数据显示问题 2024-11-05 17:24:42 +08:00
zhongzm
33159befc3 fix:增加并发请求,优化性能和逻辑 2024-11-05 17:11:38 +08:00
zhongzm
c567b19fb2 fix:修复template中不影响运行的报错 2024-11-04 16:48:16 +08:00
zhongzm
d8487d7cd7 fix:修复其他指标列表无法打开,文本错乱的问题 2024-11-04 16:04:11 +08:00
TsMask
347c9f1d3b Merge remote-tracking branch 'origin/lichang' 2024-11-04 11:19:59 +08:00
TsMask
a731a6408b fix: 调整默认布局mix,修复底部宽度100% 2024-11-04 11:19:25 +08:00
TsMask
c0ac1f6ed5 Merge remote-tracking branch 'origin/lichang' 2024-11-02 17:27:21 +08:00
TsMask
544c3697bd chore: 更新版本号 2.241102 2024-11-02 15:48:04 +08:00
TsMask
33a8ce97d3 chore: 更新版本号 2.241102 2024-11-02 15:47:18 +08:00
TsMask
6ee9d464fb feat: PCF导出有取消操作 2024-11-02 15:46:26 +08:00
TsMask
df5072bae7 fix: CDR-IMS显示呼叫-挂断时间 2024-11-02 15:46:05 +08:00
TsMask
ae94e3bf2a fix: 编译错误 2024-11-01 11:42:07 +08:00
TsMask
2276445ff6 feat:网元指标添加其他指标选项 优化样式 2024-10-31 19:23:46 +08:00
zhongzm
e12dce1f0f feat:网元指标添加其他指标选项 优化样式 2024-10-31 18:35:26 +08:00
TsMask
d0457fc285 fix: UDM鉴权签约用户勾选导出 2024-10-31 16:32:14 +08:00
TsMask
e04fd4077e feat:自定义网元指标概览 2024-10-31 10:36:26 +08:00
zhongzm
63d32f0a39 feat:自定义网元指标概览 2024-10-31 10:30:16 +08:00
TsMask
99565dd652 fix: CDR-IMS显示呼叫-挂断时间 2024-10-31 10:02:38 +08:00
TsMask
7e03437ab6 fix: UDM鉴权签约用户勾选导出 2024-10-29 11:03:36 +08:00
TsMask
5f9d19ac65 fix: 静态资源文件路径解析 2024-10-28 17:21:52 +08:00
TsMask
f23d4117d7 fix: UDM签约数据参数类型转换字符串参数 2024-10-28 16:53:43 +08:00
TsMask
e4a56d68e0 fix: 网元总览接口变更 2024-10-28 16:53:34 +08:00
TsMask
cf5d08aaab chore: 更新版本号 2.241028 2024-10-28 16:53:21 +08:00
TsMask
7ad566d74f fix: 网元总览接口变更 2024-10-28 16:52:41 +08:00
TsMask
c312186d91 fix: UDM签约数据参数类型转换字符串参数 2024-10-28 16:52:05 +08:00
TsMask
e3f7b08c69 feat: 登录页面切换主题和语言类型 2024-10-28 14:52:22 +08:00
TsMask
6e3ef7e56a fix: 静态资源文件路径解析 2024-10-28 14:31:04 +08:00
TsMask
0a96fee6c3 chore: 更新版本号 2.241028 2024-10-28 11:04:04 +08:00
TsMask
dc7d24e2bf docs: 更新说明 2024-10-28 11:03:29 +08:00
TsMask
089ae12dd1 feat: 页面调整组件属性升级 2024-10-28 11:02:51 +08:00
TsMask
da0d49d306 feat: 页面调整组件属性升级 2024-10-28 11:02:39 +08:00
TsMask
670225a655 style: 关键指标概览页面占位 2024-10-28 11:01:02 +08:00
TsMask
fa35bfc340 fix: Event Type的label修改导致引用对象数据变更 2024-10-28 10:58:03 +08:00
TsMask
f4a5d28a29 feat: 展开详情显示Network Function IPv4 2024-10-28 10:55:36 +08:00
TsMask
c735aeba6d fix: 导出带取消操作按钮 2024-10-28 10:53:48 +08:00
TsMask
3c058ec107 feat: PCF补充增加online和offline字段,导出有取消操作 2024-10-28 10:53:03 +08:00
TsMask
327e82e057 fix: 布局组件升级调整 2024-10-28 10:51:11 +08:00
TsMask
012fc44f08 chore: 更新升级依赖库 2024-10-28 10:48:17 +08:00
lai
72fd372fe0 增加关闭按钮 2024-10-24 10:40:54 +08:00
lai
acdadcbb6f 增加Network Function IPv4地址 2024-10-24 10:40:20 +08:00
lai
7a49de71ea 修复 修改Event Type的label 2024-10-24 10:38:28 +08:00
zhongzm
56e4419e77 Revert "Revert "fix:中英提示修复""
This reverts commit 3abb4dd4bd.
2024-10-24 10:12:51 +08:00
zhongzm
3abb4dd4bd Revert "fix:中英提示修复"
This reverts commit a45243390b.
2024-10-24 10:12:09 +08:00
zhongzm
726a284ab5 Merge remote-tracking branch 'origin/main' 2024-10-24 10:11:28 +08:00
zhongzm
a45243390b fix:中英提示修复 2024-10-24 10:11:08 +08:00
TsMask
1faed9bc3d style: 关键指标概览页面占位 2024-10-23 18:48:29 +08:00
TsMask
9bd700eeb7 feat: PCF补充增加online和offline字段 2024-10-23 10:44:15 +08:00
zhongzm
5cc3b9c8cf fix:css样式报错修复 2024-10-22 18:54:35 +08:00
zhongzm
208895c7d5 feat:快速布局功能以及ws连接修复 2024-10-22 16:01:53 +08:00
TsMask
46578ce97b feat: 快速开站SMSC的IP赋值 2024-10-18 20:16:06 +08:00
TsMask
0ff5bd5e20 style: UDM用户数据根据网元类型变更刷新列表 2024-10-18 11:42:21 +08:00
TsMask
f08e637e69 fix: 版权信息文本长度128 2024-10-18 11:34:37 +08:00
TsMask
a600e056b8 chore: 更新版本号 2.241018 2024-10-18 10:36:06 +08:00
zhongzm
671c80972e fix:多选改checkbox 2024-10-18 10:32:55 +08:00
TsMask
d07230b582 Merge remote-tracking branch 'origin/main' into lichang 2024-10-18 10:20:54 +08:00
TsMask
35c24407ac Merge remote-tracking branch 'origin/lichang' 2024-10-18 10:18:48 +08:00
TsMask
cf33756548 Merge remote-tracking branch 'origin/main' into lichang 2024-10-18 10:17:53 +08:00
TsMask
1ef98298bc style: 移除port/dbinfo/capability属性信息 2024-10-17 19:55:09 +08:00
zhongzm
b1c2a95ec4 Merge remote-tracking branch 'origin/lichang' into lichang 2024-10-17 19:54:55 +08:00
zhongzm
147b2fad8d fix:网元响应式数组添加防抖 2024-10-17 19:54:40 +08:00
TsMask
b629088406 Merge remote-tracking branch 'origin/lichang' 2024-10-17 19:51:43 +08:00
TsMask
430a067280 Merge remote-tracking branch 'origin/main' into lichang 2024-10-17 18:27:09 +08:00
TsMask
8a71e8f773 Merge remote-tracking branch 'origin/lichang' 2024-10-17 18:26:49 +08:00
lai
ff556ce1ec 添加首页加载状态 2024-10-17 18:18:37 +08:00
zhongzm
9ed7aed4b4 feat:自定义布局保存,WS数据追加 2024-10-17 18:07:39 +08:00
TsMask
9e14297488 Merge remote-tracking branch 'origin/main' into lichang 2024-10-17 15:55:15 +08:00
TsMask
8e70706ed5 fix: 网元日志实时查看组件参数调整 2024-10-17 15:07:04 +08:00
TsMask
3e0529cf87 fix: 终端SSH视图组件调整参数配置外部地址 2024-10-17 15:06:34 +08:00
TsMask
91af2bed92 feat: 工具iperf/ping功能页面 2024-10-17 15:05:21 +08:00
lai
1ecefb91dc 增加导出时携带完整搜索条件 2024-10-17 14:51:55 +08:00
TsMask
72d9895902 fix: UDM用户数据按查询条件导出 2024-10-17 11:39:17 +08:00
lai
41fa214137 完善自定义首页设置 2024-10-17 10:30:21 +08:00
TsMask
1565f25a03 del: 移除debugger标记 2024-10-17 10:29:39 +08:00
lai
c5c2926d99 修改告警导出异常 2024-10-17 10:29:22 +08:00
lai
55456f9220 自定义主页 2024-10-16 19:28:05 +08:00
TsMask
cf1686c348 Merge branch 'lichang' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into lichang 2024-10-16 16:47:01 +08:00
TsMask
f7833bcd9f fix: UDM数据load失败无法重试 2024-10-16 16:46:57 +08:00
TsMask
5a621053a4 feat: 网元连接配置UDM支持Redis 2024-10-16 16:46:14 +08:00
lai
2a6451ef2a 更改首页代码文件路径 2024-10-16 15:11:26 +08:00
lai
f1b440c8dd 补充 2024-10-16 14:50:47 +08:00
lai
a67e54ca6e 补充 2024-10-16 14:23:32 +08:00
lai
53d9e63c36 新增自定义首页 2024-10-16 14:12:31 +08:00
zhongzm
b4623d19e5 fix:关键指标界面拖拽块设为图标,优化样式 2024-10-15 18:26:02 +08:00
zhongzm
700bff6e38 fix:关键指标界面自定义布局功能 2024-10-15 17:48:35 +08:00
TsMask
d77c4e43d4 feat: 信令跟踪保活续期 2024-10-15 15:14:38 +08:00
TsMask
6e11d2b16a feat: 网元日志文件获取查看,抓包单独查看 2024-10-15 14:55:41 +08:00
TsMask
405842bc0b fix: 看板用户事件AMF订阅编号无neId 2024-10-15 14:38:29 +08:00
zhongzm
bf8d7f2124 fix:代码优化-方法封装-拖拽保存-大小自适应 2024-10-14 18:52:47 +08:00
TsMask
ba98b37306 feat: 优化PCF参数可选请求数据处理 2024-10-12 19:10:52 +08:00
TsMask
aa8ed65fd8 del: 删除旧License页面相关接口请求 2024-10-12 19:10:21 +08:00
TsMask
936a4410b3 del: 删除旧参数配置页面相关接口请求 2024-10-12 19:10:04 +08:00
TsMask
58ec76f9e5 Merge remote-tracking branch 'origin/main' into lichang 2024-10-12 15:43:09 +08:00
TsMask
c1a77c8e48 chore: 更新版本号 241012 2024-10-12 15:42:29 +08:00
TsMask
477e8e4631 feat: 工具iperf/ping页面占位 2024-10-12 15:39:53 +08:00
TsMask
4f9d65a3a7 feat: UDM签约支持MICO和RAT修改 2024-10-12 15:07:06 +08:00
TsMask
b1799d8ccb style: 首页-网元详细信息-删除数据库以及端口字段显示 2024-10-12 09:50:57 +08:00
TsMask
86833e7d6b feat: 关键指标报表页面 2024-10-11 18:48:46 +08:00
TsMask
59cf57898b Merge branch 'lichang' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into lichang 2024-10-11 18:14:31 +08:00
zhongzm
fb9382e3a0 Merge remote-tracking branch 'origin/lichang' into lichang 2024-10-11 18:05:32 +08:00
zhongzm
dae4697cd2 feat:多图表网元指标界面实时数据连接修复和拖拽功能实现 2024-10-11 17:57:51 +08:00
TsMask
1b2e892f74 fix: 看板总流量24小时切换类型声明 2024-10-11 16:50:46 +08:00
TsMask
c66c640f75 fix: 看板总流量24小时实时累加 2024-10-11 15:47:26 +08:00
TsMask
30849416b6 fix: 工具ps/net页面定时器清除 2024-10-11 14:12:57 +08:00
TsMask
5edcee8da5 style: 隐藏跳转主机添加页面 2024-10-11 14:12:08 +08:00
TsMask
311beed2a7 fix: UPF总量数据格式化单位问题 2024-10-11 14:11:18 +08:00
TsMask
eb5fdfb635 fix: 修复工具ps/net资源列表 2024-10-11 09:53:13 +08:00
TsMask
78bcde9ef2 style: 信令跟踪根据状态隐藏操作 2024-10-10 21:06:56 +08:00
lai
630e2a16ad 限制自定义指标输入长度 2024-10-10 19:45:45 +08:00
TsMask
e1fe031f25 Merge branch 'lichang' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into lichang 2024-10-09 18:56:28 +08:00
zhongzm
855ba7dc9e feat:net界面修复:F12后的报错消除 2024-10-09 12:04:16 +08:00
TsMask
3a72e73d5d style: 拓扑图smsc图标 2024-10-09 10:53:47 +08:00
TsMask
4cb13a1419 feat: 网元快速安装添加SMSC的ip填充 2024-10-09 09:50:31 +08:00
TsMask
8dd84a5255 fix: 网元抓包loading状态禁止重复开始任务 2024-10-08 16:49:14 +08:00
TsMask
c0e62f48b7 fix: wiregasm去除gz压缩文件 2024-10-01 14:10:22 +08:00
TsMask
b992225e28 fix: 网元快速安装多语言识别 2024-10-01 13:02:06 +08:00
TsMask
2f04562a34 feat: 信令跟踪功能页面 2024-09-30 21:02:01 +08:00
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
176 changed files with 11881 additions and 27831 deletions

View File

@@ -1,5 +1,5 @@
# 历史路径-哈希带井号标识 # 历史路径-哈希带井号标识
VITE_HISTORY_HASH = false VITE_HISTORY_HASH = true
# 历史路径-前缀URL如/h5 # 历史路径-前缀URL如/h5
VITE_HISTORY_BASE_URL = "/" VITE_HISTORY_BASE_URL = "/"
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
VITE_APP_CODE = "OMC" VITE_APP_CODE = "OMC"
# 应用版本 # 应用版本
VITE_APP_VERSION = "2.241123-fix" VITE_APP_VERSION = "2.250509"
# 接口基础URL地址-不带/后缀 # 接口基础URL地址-不带/后缀
VITE_API_BASE_URL = "/omc-api" VITE_API_BASE_URL = "/omc-api"

View File

@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
VITE_APP_CODE = "OMC" VITE_APP_CODE = "OMC"
# 应用版本 # 应用版本
VITE_APP_VERSION = "2.241123-fix" VITE_APP_VERSION = "2.250509"
# 接口基础URL地址-不带/后缀 # 接口基础URL地址-不带/后缀
VITE_API_BASE_URL = "/omc-api" VITE_API_BASE_URL = "/omc-api"

View File

@@ -12,50 +12,50 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@ant-design/icons-vue": "^7.0.1", "@ant-design/icons-vue": "7.0.1",
"@antv/g6": "~4.8.24", "@antv/g6": "4.8.24",
"@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-javascript": "6.2.3",
"@codemirror/lang-yaml": "^6.1.1", "@codemirror/lang-yaml": "6.1.2",
"@codemirror/merge": "^6.7.2", "@codemirror/merge": "6.10.0",
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "6.1.2",
"@tato30/vue-pdf": "^1.11.2", "@tato30/vue-pdf": "1.11.3",
"@vueuse/core": "11.2.0", "@vueuse/core": "13.0.0",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "0.10.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "5.5.0",
"ant-design-vue": "^4.2.5", "ant-design-vue": "4.2.6",
"antdv-pro-layout": "^4.1.9", "antdv-pro-layout": "4.2.0",
"antdv-pro-modal": "^4.0.5", "antdv-pro-modal": "4.0.6",
"codemirror": "^6.0.1", "codemirror": "6.0.1",
"crypto-js": "^4.2.0", "crypto-js": "4.2.0",
"dayjs": "^1.11.11", "dayjs": "1.11.13",
"echarts": "~5.5.0", "echarts": "5.6.0",
"file-saver": "^2.0.5", "file-saver": "2.0.5",
"grid-layout-plus": "^1.0.5", "grid-layout-plus": "1.0.6",
"intl-tel-input": "^24.6.0", "intl-tel-input": "25.2.0",
"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",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"p-queue": "~8.0.1", "p-queue": "8.0.1",
"pinia": "2.2.6", "pinia": "2.3.0",
"vue": "^3.5.12", "vue": "3.5.13",
"vue-i18n": "^10.0.4", "vue-i18n": "11.1.2",
"vue-router": "^4.4.5", "vue-router": "4.5.0",
"vue3-smooth-dnd": "^0.0.6", "vue3-smooth-dnd": "0.0.6",
"xlsx": "~0.18.5" "xlsx": "0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@types/crypto-js": "^4.2.2", "@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": "^22.7.7", "@types/node": "^18.0.0",
"@types/nprogress": "^0.2.3", "@types/nprogress": "0.2.3",
"@vitejs/plugin-vue": "^5.1.4", "@vitejs/plugin-vue": "5.2.3",
"less": "^4.2.0", "less": "4.2.2",
"typescript": "^5.6.3", "typescript": "5.8.2",
"unplugin-vue-components": "^0.27.4", "unplugin-vue-components": "0.28.0",
"vite": "5.4.10", "vite": "6.2.2",
"vite-plugin-compression": "~0.5.1", "vite-plugin-compression": "0.5.1",
"vue-tsc": "^2.1.8" "vue-tsc": "2.2.8"
} }
} }

View File

@@ -1,27 +0,0 @@
# 实训教学模块
网元固定一套ne_id 默认使用`001`
## 静态资源
将目录下文件放置到对应目录 替换约18个文件
- i18n 对应覆盖 src\i18n
- views 对应覆盖 src\views
## 涉及文件
- src\i18n\locales\en-US.ts
- src\i18n\locales\zh-CN.ts
- src\views\monitor\topologyArchitecture\index.vue
- src\views\configManage\configParamTreeTable
- src\views\configManage\configParamApply
- src\views\ne\neInfo\index.vue
- src\plugins\auth-user.ts
- src\views\dashboard\amfUE\index.vue
- src\views\dashboard\mmeUE\index.vue
- src\views\dashboard\imsCDR\index.vue
- src\views\dashboard\smfCDR\index.vue
- src\views\dashboard\smscCDR\index.vue
- src\store\modules\user.ts

View File

@@ -1,120 +0,0 @@
import { request } from '@/plugins/http-fetch';
/**
* 保存为示例配置 (仅管理员操作)
* @param query 查询参数
* @returns object
*/
export function ptSaveAsDefault(neType: string, neid: string) {
return request({
url: `/pt/neConfigData/saveAsDefault`,
method: 'post',
data: { neType, neid },
});
}
/**
* 重置为示例配置 (仅学生/教师操作)
* @param query 查询参数
* @returns object
*/
export function ptResetAsDefault(neType: string) {
return request({
url: `/pt/neConfigData/resetAsDefault`,
method: 'post',
data: { neType },
});
}
/**
* 数据比较示例
* @param params 查询参数
* @returns object
*/
export function ptContrastAsDefault(params: Record<string, any>) {
return request({
url: `/pt/neConfigData/contrast`,
params,
method: 'get',
});
}
/**
* 配置数据导出Excel
* @param student 仅教师 student
* @returns object
*/
export function ptExport(student: string | undefined) {
return request({
url: `/pt/neConfigData/export`,
method: 'get',
params: { student },
responseType: 'blob',
timeout: 180_000,
});
}
/**
* 配置数据导出Excel (仅教师全量)
* @returns object
*/
export function ptExportAll() {
return request({
url: `/pt/neConfigData/export-all`,
method: 'get',
responseType: 'blob',
timeout: 180_000,
});
}
/**
* 网元参数配置信息
* @param params 数据 {neType,paramName}
* @returns object
*/
export function getPtNeConfigData(params: Record<string, any>) {
return request({
url: `/pt/neConfigData`,
params,
method: 'get',
});
}
/**
* 网元参数配置数据更新
* @param data 数据 {neType,paramName:"参数名",paramData:{参数},loc:"层级index仅array"}
* @returns object
*/
export function editPtNeConfigData(data: Record<string, any>) {
return request({
url: `/pt/neConfigData`,
method: 'put',
data: data,
});
}
/**
* 网元参数配置新增array
* @param data 数据 {neType,paramName:"参数名",paramData:{参数},loc:"层级index"}
* @returns object
*/
export function addPtNeConfigData(data: Record<string, any>) {
return request({
url: `/pt/neConfigData`,
method: 'post',
data: data,
});
}
/**
* 网元参数配置删除array
* @param params 数据 {neType,paramName:"参数名",loc:"层级index"}
* @returns object
*/
export function delPtNeConfigData(params: Record<string, any>) {
return request({
url: `/pt/neConfigData`,
method: 'delete',
params,
});
}

View File

@@ -1,53 +0,0 @@
import { request } from '@/plugins/http-fetch';
/**
* 班级学生列表 (仅教师操作)
* @param params 数据 {userName}
* @returns object
*/
export function getPtClassStudents(params?: Record<string, any>) {
return request({
url: `/pt/neConfigApply/students`,
params,
method: 'get',
});
}
/**
* 网元参数配置应用申请列表
* @param params 数据 {neType,paramName}
* @returns object
*/
export function getPtNeConfigApplyList(params: Record<string, any>) {
return request({
url: `/pt/neConfigApply/list`,
params,
method: 'get',
});
}
/**
* 网元参数配置应用申请提交(仅学生操作)
* @param data 数据 { "neType": "MME", "status": "1" }
* @returns object
*/
export function stuPtNeConfigApply(data: Record<string, any>) {
return request({
url: `/pt/neConfigApply`,
method: 'post',
data: data,
});
}
/**
* 网元参数配置应用申请状态变更(仅管理员/教师操作)
* @param data 数据 { "applyId": "1", "neType": "MME", "status": "3", "backInfo": "sgw参数错误" }
* @returns object
*/
export function updatePtNeConfigApply(data: Record<string, any>) {
return request({
url: `/pt/neConfigApply`,
method: 'put',
data: data,
});
}

View File

@@ -1,27 +0,0 @@
import { request } from '@/plugins/http-fetch';
/**
* 网元参数配置数据变更日志信息
* @param params 数据 {neType,paramName}
* @returns object
*/
export function getPtNeConfigDataLogList(params: Record<string, any>) {
return request({
url: `/pt/neConfigDataLog`,
params,
method: 'get',
});
}
/**
* 网元参数配置数据变更日志还原到数据
* @param data 数据 { "id": "1", "value": "old" }
* @returns object
*/
export function restorePtNeConfigDataLog(data: Record<string, any>) {
return request({
url: `/pt/neConfigDataLog/restore`,
method: 'put',
data: data,
});
}

View File

@@ -1,42 +0,0 @@
import { request } from '@/plugins/http-fetch';
/**
* 导入用户模板数据
* @param data 表单数据对象
* @returns object
*/
export function importData(data: FormData) {
return request({
url: '/pt/system/user/importData',
method: 'post',
data,
dataType: 'form-data',
timeout: 180_000,
});
}
/**
* 导入用户模板下载
* @returns bolb
*/
export function importTemplate() {
return request({
url: '/pt/system/user/importTemplate',
method: 'get',
responseType: 'blob',
});
}
/**
* 用户列表导出
* @param query 查询参数
* @returns bolb
*/
export function exportUser(query: Record<string, any>) {
return request({
url: '/pt/system/user/export',
method: 'post',
data: query,
responseType: 'blob',
});
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,66 +0,0 @@
import { ADMIN_PERMISSION, ADMIN_ROLE_KEY } from '@/constants/admin-constants';
import useUserStore from '@/store/modules/user';
/**
* 是否系统管理员
* @returns true | false
*/
export function isSystemAdmin(): boolean {
const userPermissions = useUserStore().permissions;
if (userPermissions.includes(ADMIN_PERMISSION)) return true;
const userRoles = useUserStore().roles;
if (userRoles.includes(ADMIN_ROLE_KEY)) return true;
return false;
}
/**
* 只需含有其中权限
* @param role 权限字符数组
* @returns true | false
*/
export function hasPermissions(permissions: string[]): boolean {
if (!permissions || permissions.length === 0) return false;
const userPermissions = useUserStore().permissions;
if (!userPermissions || userPermissions.length === 0) return false;
if (userPermissions.includes(ADMIN_PERMISSION)) return true;
return permissions.some(p => userPermissions.some(up => up === p));
}
/**
* 同时匹配其中权限
* @param role 权限字符数组
* @returns true | false
*/
export function matchPermissions(permissions: string[]): boolean {
if (!permissions || permissions.length === 0) return false;
const userPermissions = useUserStore().permissions;
if (!userPermissions || userPermissions.length === 0) return false;
if (userPermissions.includes(ADMIN_PERMISSION)) return true;
return permissions.every(p => userPermissions.some(up => up === p));
}
/**
* 只需含有其中角色
* @param role 角色字符数组
* @returns true | false
*/
export function hasRoles(roles: string[]): boolean {
if (!roles || roles.length === 0) return false;
const userRoles = useUserStore().roles;
if (!userRoles || userRoles.length === 0) return false;
if (userRoles.includes(ADMIN_ROLE_KEY)) return true;
return roles.some(r => userRoles.some(ur => ur === r));
}
/**
* 同时匹配其中角色
* @param role 角色字符数组
* @returns true | false
*/
export function matchRoles(roles: string[]): boolean {
if (!roles || roles.length === 0) return false;
const userRoles = useUserStore().roles;
if (!userRoles || userRoles.length === 0) return false;
if (userRoles.includes(ADMIN_ROLE_KEY)) return true;
return roles.every(r => userRoles.some(ur => ur === r));
}

View File

@@ -1,171 +0,0 @@
import defaultAvatar from '@/assets/images/default_avatar.png';
import useLayoutStore from './layout';
import { login, logout, getInfo } from '@/api/login';
import { setToken, removeToken } from '@/plugins/auth-token';
import { defineStore } from 'pinia';
import { TOKEN_RESPONSE_FIELD } from '@/constants/token-constants';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { parseUrlPath } from '@/plugins/file-static-url';
/**用户信息类型 */
type UserInfo = {
/**用户ID */
userId: string;
/**登录账号 */
userName: string;
/**用户角色 字符串数组 */
roles: string[];
/**用户权限 字符串数组 */
permissions: string[];
/**用户头像 */
avatar: string;
/**用户昵称 */
nickName: string;
/**用户手机号 */
phonenumber: string;
/**用户邮箱 */
email: string;
/**用户性别 */
sex: string | undefined;
/**其他信息 */
profile: Record<string, any>;
};
const useUserStore = defineStore('user', {
state: (): UserInfo => ({
userId: '',
userName: '',
roles: [],
permissions: [],
avatar: '',
nickName: '',
phonenumber: '',
email: '',
sex: undefined,
profile: {},
}),
getters: {
/**
* 获取正确头像地址
* @param state 内部属性不用传入
* @returns 头像地址url
*/
getAvatar(state) {
if (!state.avatar) {
return defaultAvatar;
}
return parseUrlPath(state.avatar);
},
/**
* 获取基础信息属性
* @param state 内部属性不用传入
* @returns 基础信息
*/
getBaseInfo(state) {
return {
nickName: state.nickName,
phonenumber: state.phonenumber,
email: state.email,
sex: state.sex,
};
},
},
actions: {
/**
* 更新基础信息属性
* @param data 变更信息
*/
setBaseInfo(data: Record<string, any>) {
this.nickName = data.nickName;
this.phonenumber = data.phonenumber;
this.email = data.email;
this.sex = data.sex;
},
/**
* 更新头像
* @param avatar 上传后的地址
*/
setAvatar(avatar: string) {
this.avatar = avatar;
},
/**
* 获取正确头像地址
* @param avatar
*/
fnAvatar(avatar: string) {
if (!avatar) {
return defaultAvatar;
}
return parseUrlPath(avatar);
},
// 登录
async fnLogin(loginBody: Record<string, string>) {
const res = await login(loginBody);
if (res.code === RESULT_CODE_SUCCESS && res.data) {
const token = res.data[TOKEN_RESPONSE_FIELD];
setToken(token);
}
return res;
},
// 获取用户信息
async fnGetInfo() {
const res = await getInfo();
if (res.code === RESULT_CODE_SUCCESS && res.data) {
const { user, roles, permissions } = res.data;
this.userId = user.userId;
// 登录账号
this.userName = user.userName;
// 用户头像
this.avatar = user.avatar;
// 基础信息
this.nickName = user.nickName;
this.phonenumber = user.phonenumber;
this.email = user.email;
this.sex = user.sex;
// 验证返回的roles是否是一个非空数组
if (Array.isArray(roles) && roles.length > 0) {
this.roles = roles;
this.permissions = permissions;
} else {
this.roles = ['ROLE_DEFAULT'];
this.permissions = [];
}
// 水印文字信息=用户昵称 手机号
let waterMarkContent = this.userName;
if (this.phonenumber) {
waterMarkContent = `${this.userName} ${this.phonenumber}`;
}
// useLayoutStore().changeWaterMark(waterMarkContent);
useLayoutStore().changeWaterMark('');
// 学生布局用不一样的
if (this.roles.includes('student')) {
useLayoutStore().changeConf('layout', 'side');
useLayoutStore().changeConf('menuTheme', 'dark');
useLayoutStore().changeConf('tabRender', false);
}
}
// 网络错误时退出登录状态
if (res.code === 0) {
removeToken();
window.location.reload();
}
return res;
},
// 退出系统
async fnLogOut() {
try {
await logout();
} catch (error) {
throw error;
} finally {
this.roles = [];
this.permissions = [];
removeToken();
}
},
},
});
export default useUserStore;

View File

@@ -1,714 +0,0 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, computed } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { ProModal } from 'antdv-pro-modal';
import { Form, message, Modal } from 'ant-design-vue/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/es/table';
import { parseDateToStr } from '@/utils/date-utils';
import useDictStore from '@/store/modules/dict';
import useNeInfoStore from '@/store/modules/neinfo';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useI18n from '@/hooks/useI18n';
import {
getPtNeConfigApplyList,
stuPtNeConfigApply,
updatePtNeConfigApply,
} from '@/api/pt/neConfigApply';
import { hasRoles } from '@/plugins/auth-user';
const { t } = useI18n();
const { getDict } = useDictStore();
const neInfoStore = useNeInfoStore();
/**字典数据 */
let dict: {
/**配置申请应用状态 */
ptConfigApplyStatus: DictType[];
} = reactive({
ptConfigApplyStatus: [],
});
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: '',
/**申请人 */
createBy: '',
/**状态 */
status: undefined,
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
neType: '',
createBy: '',
status: undefined,
pageNum: 1,
pageSize: 20,
});
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
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: 'center',
width: 100,
},
{
title: t('views.ne.common.neType'),
dataIndex: 'neType',
align: 'left',
width: 100,
},
{
title: '申请人',
dataIndex: 'createBy',
align: 'left',
width: 120,
},
{
title: '申请时间',
dataIndex: 'createTime',
align: 'left',
width: 150,
customRender(opt) {
if (+opt.value <= 0) return '';
return parseDateToStr(+opt.value);
},
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
align: 'left',
width: 100,
},
{
title: '处理人',
dataIndex: 'updateBy',
align: 'left',
width: 120,
},
{
title: '处理时间',
dataIndex: 'updateTime',
align: 'left',
width: 150,
customRender(opt) {
if (+opt.value <= 0) return '';
return parseDateToStr(+opt.value);
},
},
{
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: 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)[], infos: any) {
const arr = [];
for (const item of infos) {
if (item.status === '0') {
arr.push(item.id);
}
}
tableState.selectedRowKeys = arr;
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
getPtNeConfigApplyList(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 取消勾选
if (tableState.selectedRowKeys.length > 0) {
tableState.selectedRowKeys = [];
}
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 = {
/**详情框是否显示 */
openByView: boolean;
/**新增框或修改框是否显示 */
openByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
/**cron生成框是否显示 */
openByCron: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
openByView: false,
openByEdit: false,
title: '任务',
from: {
id: undefined,
createBy: '',
createTime: 0,
updateBy: '',
updateTime: 0,
neType: 'MME',
status: '0',
backInfo: '',
},
confirmLoading: false,
openByCron: false,
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
status: [
{
required: true,
message: t('common.selectPlease'),
},
],
})
);
/**
* 对话框弹出显示为 查看
* @param jobId 任务id
*/
function fnModalVisibleByVive(row: Record<string, any>) {
modalState.from = Object.assign(modalState.from, row);
modalState.title = '查看';
modalState.openByView = true;
}
/**
* 对话框弹出显示为 编辑
* @param jobId 任务id
*/
function fnModalVisibleByEdit(row: Record<string, any>) {
Object.assign(modalState.from, row);
modalState.from.status = '3';
modalState.title = '编辑状态';
modalState.openByEdit = true;
}
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
modalStateFrom
.validate()
.then(() => {
modalState.confirmLoading = true;
const from = toRaw(modalState.from);
updatePtNeConfigApply({
applyId: from.id,
neType: from.neType,
status: from.status,
backInfo: from.backInfo,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
modalState.openByEdit = false;
modalStateFrom.resetFields();
fnGetList();
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
modalState.confirmLoading = false;
});
})
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
});
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.openByEdit = false;
modalState.openByView = false;
modalState.from = {};
}
/**批量退回 */
function fnRecordBack(row?: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: row
? '确认要撤回配置应用申请吗?'
: '确认要批量退回学生的配置应用申请吗?',
onOk() {
let result: any;
if (row) {
result = stuPtNeConfigApply({ neType: row.neType, status: '1' });
} else {
result = updatePtNeConfigApply({
status: '3',
backId: tableState.selectedRowKeys.join(','),
backInfo: '请重新检查配置',
});
}
result.then((res: any) => {
fnGetList();
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
});
},
});
}
/**应用状态 */
const applyStatus = computed(() => {
if (hasRoles(['student'])) {
return dict.ptConfigApplyStatus.filter(s => ['0', '1'].includes(s.value));
}
let data = dict.ptConfigApplyStatus;
if (modalState.openByEdit && modalState.from.id) {
data = dict.ptConfigApplyStatus.filter(s => ['2', '3'].includes(s.value));
}
return data;
});
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('pt_config_apply_status')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.ptConfigApplyStatus = resArr[0].value;
}
});
// 获取网元列表
neInfoStore.fnNelist().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.configManage.license.neType')"
name="neType "
>
<a-auto-complete
v-model:value="queryParams.neType"
:options="neInfoStore.getNeSelectOtions"
allow-clear
:placeholder="t('views.configManage.license.neTypePlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="状态" name="status">
<a-select
v-model:value="queryParams.status"
allow-clear
:placeholder="t('common.selectPlease')"
:options="applyStatus"
>
</a-select>
</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>
<div class="button-container">
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
@click.prevent="fnRecordBack()"
v-roles:has="['admin', 'teacher']"
>
<template #icon><DeleteOutlined /></template>
批量退回
</a-button>
</div>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<div class="button-container">
<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>
</div>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:scroll="{ x: tableColumns.length * 120 }"
:pagination="tablePagination"
:row-selection="{
type: 'checkbox',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<DictTag
:options="dict.ptConfigApplyStatus"
:value="record.status"
/>
</template>
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.viewText') }}</template>
<a-button
type="link"
@click.prevent="fnModalVisibleByVive(record)"
>
<template #icon><ProfileOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip v-if="record.status === '0' && hasRoles(['student'])">
<template #title>撤回</template>
<a-button type="link" @click.prevent="fnRecordBack(record)">
<template #icon><RollbackOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip
v-if="record.status === '0' && hasRoles(['admin', 'teacher'])"
>
<template #title>{{ t('common.editText') }}</template>
<a-button
type="link"
@click.prevent="fnModalVisibleByEdit(record)"
>
<template #icon><FormOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
</a-table>
</a-card>
<!-- 详情框 -->
<ProModal
:drag="true"
:width="800"
:open="modalState.openByView"
:title="modalState.title"
@cancel="fnModalCancel"
>
<a-form layout="horizontal" :label-col="{ span: 6 }" :label-wrap="true">
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neType')" name="neType">
{{ modalState.from.neType }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="状态" name="status">
<DictTag
:options="dict.ptConfigApplyStatus"
:value="modalState.from.status"
/>
</a-form-item>
</a-col>
</a-row>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="申请人" name="createBy">
{{ modalState.from.createBy }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="申请时间" name="createTime">
{{ parseDateToStr(+modalState.from.createTime) }}
</a-form-item>
</a-col>
</a-row>
<a-row v-if="modalState.from.status !== '0'">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="处理人" name="updateBy">
{{ modalState.from.updateBy }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="处理时间" name="updateTime">
{{ parseDateToStr(+modalState.from.updateTime) }}
</a-form-item>
</a-col>
</a-row>
<a-form-item
v-if="modalState.from.status === '3'"
label="退回说明"
name="backInfo"
:label-col="{ span: 3 }"
:label-wrap="true"
>
{{ modalState.from.backInfo }}
</a-form-item>
</a-form>
<template #footer>
<a-button key="cancel" @click="fnModalCancel">
{{ t('common.close') }}
</a-button>
</template>
</ProModal>
<!-- 新增框或修改框 -->
<ProModal
:drag="true"
:width="800"
:open="modalState.openByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
:destroyOnClose="true"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="modalStateFrom"
layout="horizontal"
:label-col="{ span: 6 }"
:label-wrap="true"
>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neType')" name="neType">
{{ modalState.from.neType }}
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="申请人" name="createBy">
{{ modalState.from.createBy }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="申请时间" name="createTime">
{{ parseDateToStr(+modalState.from.createTime) }}
</a-form-item>
</a-col>
</a-row>
<a-form-item
label="状态"
name="status"
:label-col="{ span: 3 }"
:label-wrap="true"
v-bind="modalStateFrom.validateInfos.status"
>
<a-select
v-model:value="modalState.from.status"
:placeholder="t('common.selectPlease')"
:options="applyStatus"
>
</a-select>
</a-form-item>
<a-form-item
v-if="modalState.from.status === '3'"
label="退回说明"
name="backInfo"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-textarea
v-model:value="modalState.from.backInfo"
:auto-size="{ minRows: 2, maxRows: 6 }"
:maxlength="400"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
</a-form>
</ProModal>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -1,327 +0,0 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import CodemirrorEditeDiff from '@/components/CodemirrorEditeDiff/index.vue';
import { parseDateToStr } from '@/utils/date-utils';
import {
getPtNeConfigDataLogList,
restorePtNeConfigDataLog,
} from '@/api/pt/neConfigDataLog';
import useDictStore from '@/store/modules/dict';
import { message } from 'ant-design-vue';
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:open']);
const props = defineProps({
open: {
type: Boolean,
default: false,
},
/**网元类型 */
neType: {
type: String,
default: '',
},
/**参数名 */
paramName: {
type: String,
default: '',
},
/**学生用户账号 */
student: {
type: String,
default: '',
},
});
const { getDict } = useDictStore();
/**字典数据 */
let dict: {
/**业务类型 */
sysBusinessType: DictType[];
} = reactive({
sysBusinessType: [],
});
/**对话框对象信息状态类型 */
type StateType = {
/**新增框或修改框是否显示 */
openByList: boolean;
/**差异比较框是否显示 */
openByDiff: boolean;
/**标题 */
title: string;
/**加载状态 */
loading: boolean;
/**数据 */
data: Record<string, any>[];
/**差异数据 */
dataDiff: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let state: StateType = reactive({
openByList: false,
openByDiff: false,
title: '操作参数名称-学生账号',
loading: false,
data: [],
dataDiff: {},
confirmLoading: false,
});
function onClose() {
state.loading = false;
state.openByList = false;
state.openByDiff = false;
state.data = [];
state.dataDiff = {};
emit('cancel');
emit('update:open', false);
queryParams = {
neType: '',
paramName: '',
student: '',
pageNum: 1,
pageSize: 10,
};
}
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: '',
/**可用属性值 */
paramName: '',
/**学生账号 */
student: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 10,
});
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (state.loading) return;
state.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
if (pageNum === 1) state.data = [];
}
getPtNeConfigDataLogList(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// tablePagination.total = res.total;
state.data = state.data.concat(res.rows);
// 去首个做标题
if (queryParams.pageNum === 1 && state.data.length > 0) {
const item = state.data[0];
state.title = `${item.paramDisplay} - ${item.createBy}`;
}
if (state.data.length <= res.total && res.rows.length > 0) {
queryParams.pageNum++;
}
}
state.loading = false;
});
}
/**差异比较框打开 */
function fnMergeCellOpen(row: Record<string, any>) {
state.dataDiff = row;
state.dataDiff.paramJsonOld = JSON.stringify(
JSON.parse(state.dataDiff.paramJsonOld),
null,
2
);
state.dataDiff.paramJsonNew = JSON.stringify(
JSON.parse(state.dataDiff.paramJsonNew),
null,
2
);
state.openByDiff = true;
}
/**差异比较框关闭 */
function fnMergeCellClose() {
state.openByDiff = false;
state.dataDiff = {};
}
/**差异比较还原 */
function fnMergeCellRestore(value: 'old' | 'new') {
if (state.confirmLoading) return;
const id = state.dataDiff.id;
restorePtNeConfigDataLog({ id, value })
.then((res: any) => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
fnMergeCellClose();
fnGetList(1);
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
state.confirmLoading = false;
});
}
/**监听是否显示,初始数据 */
watch(
() => props.open,
val => {
if (val) {
if (props.neType && props.paramName) {
state.title = '';
state.openByList = true;
// 根据条件查询数据
queryParams.neType = props.neType;
queryParams.paramName = props.paramName;
if (props.student) {
queryParams.student = props.student;
}
fnGetList();
}
}
}
);
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('sys_oper_type')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.sysBusinessType = resArr[0].value;
}
});
});
</script>
<template>
<div>
<a-drawer
:width="500"
:title="state.title"
placement="right"
:open="state.openByList"
@close="onClose"
>
<a-list
class="demo-loadmore-list"
item-layout="horizontal"
:data-source="state.data"
>
<template #loadMore>
<div
:style="{
textAlign: 'center',
marginTop: '12px',
height: '32px',
lineHeight: '32px',
}"
>
<a-button @click="fnGetList()" :loading="state.loading">
{{ t('views.configManage.configParamForm.ptDiffLoad') }}
</a-button>
</div>
</template>
<template #renderItem="{ item }">
<a-list-item>
<template #actions>
<a-tooltip>
<template #title>
{{ t('views.configManage.configParamForm.ptDiffMerge') }}
</template>
<a-button type="primary" @click.prevent="fnMergeCellOpen(item)">
<template #icon><MergeCellsOutlined /></template>
</a-button>
</a-tooltip>
</template>
<a-list-item-meta>
<template #title>
<DictTag
:options="dict.sysBusinessType"
:value="item.operaType"
/>
</template>
<template #description>
{{ parseDateToStr(item.createTime) }}
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-drawer>
<a-modal
:width="800"
:destroyOnClose="true"
:mask-closable="false"
v-model:open="state.openByDiff"
:footer="null"
:body-style="{ padding: 0, maxHeight: '650px', 'overflow-y': 'auto' }"
@ok="fnMergeCellClose()"
@cancel="fnMergeCellClose()"
>
<template #title>
<DictTag
:options="dict.sysBusinessType"
:value="state.dataDiff.operaType"
/>
{{ parseDateToStr(state.dataDiff.createTime) }}
</template>
<div class="diffBack">
<div>
<a-button
type="text"
:loading="state.confirmLoading"
@click.prevent="fnMergeCellRestore('old')"
>
<template #icon><MergeCellsOutlined /></template>
{{ t('views.configManage.configParamForm.ptDiffRest') }}
</a-button>
</div>
<div>
<a-button
type="text"
:loading="state.confirmLoading"
@click.prevent="fnMergeCellRestore('new')"
>
<template #icon><MergeCellsOutlined /></template>
{{ t('views.configManage.configParamForm.ptDiffRest') }}
</a-button>
</div>
</div>
<CodemirrorEditeDiff
:old-area="state.dataDiff.paramJsonOld"
:new-area="state.dataDiff.paramJsonNew"
></CodemirrorEditeDiff>
</a-modal>
</div>
</template>
<style lang="less" scoped>
.diffBack {
display: flex;
flex-direction: row;
justify-content: space-between;
& > div:first-child {
flex: 1;
background: #fa9;
}
& > div:last-child {
flex: 1;
background: #8f8;
}
}
</style>

View File

@@ -1,407 +0,0 @@
import {
addPtNeConfigData,
delPtNeConfigData,
editPtNeConfigData,
} from '@/api/pt/neConfig';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { Modal, message } from 'ant-design-vue/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { reactive, watch } from 'vue';
/**
* 参数配置array类型
* @param param 父级传入 { t, treeState, fnActiveConfigNode, ruleVerification, modalState, fnModalCancel}
* @returns
*/
export default function useConfigArray({
t,
treeState,
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 (treeState.neType === '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.open = true;
// 关闭嵌套
arrayState.arrayChildExpandKeys = [];
}
/**多列表编辑关闭 */
function arrayEditClose() {
arrayState.arrayChildExpandKeys = [];
fnModalCancel();
}
/**多列表编辑确认 */
function arrayEditOk(from: Record<string, any>) {
const loc = `${from['index']['value']}`;
// 特殊SMF-upfid选择
if (treeState.neType === '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);
editPtNeConfigData({
neType: treeState.neType,
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() {
delPtNeConfigData({
neType: treeState.neType,
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 (treeState.neType === '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.open = true;
}
/**多列表新增单行确认 */
function arrayAddOk(from: Record<string, any>) {
// 特殊SMF-upfid选择
if (treeState.neType === '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);
addPtNeConfigData({
neType: treeState.neType,
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 (treeState.neType === '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

@@ -1,348 +0,0 @@
import {
addPtNeConfigData,
delPtNeConfigData,
editPtNeConfigData,
} from '@/api/pt/neConfig';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { Modal, message } from 'ant-design-vue/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { nextTick, reactive } from 'vue';
/**
* 参数配置array类型的嵌套array
* @param param 父级传入 { t, treeState, fnActiveConfigNode, ruleVerification, modalState, arrayState, arrayInitEdit, arrayInitAdd, arrayEditClose}
* @returns
*/
export default function useConfigArrayChild({
t,
treeState,
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.open = 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);
editPtNeConfigData({
neType: treeState.neType,
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() {
delPtNeConfigData({
neType: treeState.neType,
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.open = 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);
addPtNeConfigData({
neType: treeState.neType,
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

@@ -1,243 +0,0 @@
import { ptSaveAsDefault, ptResetAsDefault } from '@/api/pt/neConfig';
import {
getPtClassStudents,
stuPtNeConfigApply,
updatePtNeConfigApply,
} from '@/api/pt/neConfigApply';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { Modal } from 'ant-design-vue/es';
import { message } from 'ant-design-vue/lib';
import { computed, reactive } from 'vue';
/**
* 实训教学函数
* @param param 父级传入 {t,fnActiveConfigNode}
* @returns
*/
export default function usePtOptions({ t, fnActiveConfigNode }: any) {
const ptConfigState = reactive({
saveLoading: false,
restLoading: false,
applyLoading: false,
});
/**(管理员)保存网元下所有配置为示例配置 */
function ptConfigSave(neType: string) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.configManage.configParamForm.ptLoadTip'),
onOk() {
ptConfigState.saveLoading = true;
ptSaveAsDefault(neType, '001')
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
ptConfigState.saveLoading = false;
fnActiveConfigNode('#');
});
},
});
}
/**重置网元下所有配置 */
function ptConfigReset(neType: string) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.configManage.configParamForm.ptResetTip'),
onOk() {
ptConfigState.restLoading = true;
ptResetAsDefault(neType)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
ptConfigState.restLoading = false;
fnActiveConfigNode('#');
});
},
});
}
/**配置下方应用(学生)申请撤回和(管理/教师)应用退回 */
function ptConfigApply(
neType: string,
status: '0' | '1' | '2' | '3',
student?: string
) {
let result: any;
if (status === '2' || status === '3') {
let from: {
neType: string;
status: string;
student?: string;
backInfo?: string;
} = {
neType,
status: '2',
};
if (student) {
if (status === '2') {
from = { neType, status, student };
}
if (status === '3') {
from = { neType, status, student, backInfo: '请重新检查配置' };
}
}
result = updatePtNeConfigApply(from);
}
if (status === '0' || status === '1') {
result = stuPtNeConfigApply({ neType, status });
}
if (!result) return;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.configManage.configParamForm.ptApplyStuTip', {
ne: neType,
}),
onOk() {
ptConfigState.applyLoading = true;
result
.then((res: any) => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
// 教师修改学生时改变状态
if (student) {
const item = classState.studentOptionsDef.find(
s => s.value === classState.student
);
if (item) {
item.applyStatus = status;
}
}
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
ptConfigState.applyLoading = false;
});
},
});
}
const classState = reactive<{
/**学生账号 */
student: string | undefined;
/**学生可选择列表 */
studentOptions: {
value: string;
label: string;
applyId: string;
applyStatus: string;
}[];
studentOptionsDef: {
value: string;
label: string;
applyId: string;
applyStatus: string;
}[];
}>({
student: undefined,
studentOptions: [],
studentOptionsDef: [],
});
/**学生选择搜索 */
function studentChange(v: any) {
if (!v) {
Object.assign(classState.studentOptions, classState.studentOptionsDef);
}
fnActiveConfigNode('#');
}
let timeout: any;
/**学生选择搜索 */
function studentSearch(neType: string, val: string) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
if (!val) {
Object.assign(classState.studentOptions, classState.studentOptionsDef);
return;
}
timeout = setTimeout(() => classStudents(neType, val), 500);
}
/**班级学生列表 */
function classStudents(neType: string, val?: string) {
getPtClassStudents({ neType, userName: val }).then(res => {
classState.studentOptions = [];
if (!Array.isArray(res.data) || res.data.length <= 0) {
return;
}
for (const v of res.data) {
classState.studentOptions.push({
value: v.userName,
label: v.userName,
applyId: v.applyId,
applyStatus: v.applyStatus,
});
// 设为最新状态
const item = classState.studentOptionsDef.find(
s => s.value === v.userName
);
if (item) {
item.applyStatus = v.applyStatus;
}
}
if (!val) {
Object.assign(classState.studentOptionsDef, classState.studentOptions);
}
});
}
// 学生状态
const studentStatus = computed(() => {
const item = classState.studentOptionsDef.find(
s => s.value === classState.student
);
if (item) return item.applyStatus;
return '';
});
return {
ptConfigState,
ptConfigSave,
ptConfigReset,
ptConfigApply,
classState,
classStudents,
studentStatus,
studentSearch,
studentChange,
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,442 +0,0 @@
<script setup lang="ts">
import { PageContainer } from 'antdv-pro-layout';
import { ColumnsType } from 'ant-design-vue/es/table';
import { message } from 'ant-design-vue/es';
import { reactive, ref, onMounted, onBeforeUnmount, markRaw } from 'vue';
import useI18n from '@/hooks/useI18n';
import { TooltipComponent } from 'echarts/components';
import { GaugeChart } from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import * as echarts from 'echarts/core';
import { TitleComponent, LegendComponent } from 'echarts/components';
import { PieChart } from 'echarts/charts';
import { LabelLayout } from 'echarts/features';
import { useRoute } from 'vue-router';
import useAppStore from '@/store/modules/app';
import useDictStore from '@/store/modules/dict';
import { listAllNeInfo } from '@/api/ne/neInfo';
import { parseDateToStr } from '@/utils/date-utils';
const { getDict } = useDictStore();
const appStore = useAppStore();
const route = useRoute();
const { t } = useI18n();
echarts.use([
TooltipComponent,
GaugeChart,
TitleComponent,
LegendComponent,
PieChart,
CanvasRenderer,
LabelLayout,
]);
/**图DOM节点实例对象 */
const statusBar = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
const statusBarChart = ref<any>(null);
/**网元状态字典数据 */
let indexColor = ref<DictType[]>([
{ label: 'Normal', value: 'normal', tagType: '', tagClass: '#91cc75' },
{
label: 'Abnormal',
value: 'abnormal',
tagType: '',
tagClass: '#ee6666',
},
]);
/**表格字段列 */
//customRender(){} ----单元格处理
let tableColumns: ColumnsType = [
{
title: t('views.index.object'),
dataIndex: 'neName',
align: 'left',
},
{
title: t('views.index.realNeStatus'),
dataIndex: 'serverState',
align: 'left',
key: 'status',
},
{
title: t('views.index.reloadTime'),
dataIndex: 'serverState',
align: 'left',
customRender(opt) {
if (opt.value?.refreshTime) return parseDateToStr(opt.value?.refreshTime);
return '-';
},
},
{
title: t('views.index.version'),
dataIndex: 'serverState',
align: 'left',
customRender(opt) {
return opt.value?.version || '-';
},
},
{
title: t('views.index.serialNum'),
dataIndex: 'serverState',
align: 'left',
customRender(opt) {
return opt.value?.sn || '-';
},
},
{
title: t('views.index.expiryDate'),
dataIndex: 'serverState',
align: 'left',
customRender(opt) {
return opt.value?.expire || '-';
},
},
{
title: t('views.index.ipAddress'),
dataIndex: 'serverState',
align: 'left',
customRender(opt) {
return opt.value?.neIP || '-';
},
},
];
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**记录数据 */
data: object[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
data: [],
selectedRowKeys: [],
});
/**表格状态 */
let nfInfo: any = reactive({
obj: 'OMC',
version: appStore.version,
status: t('views.index.normal'),
outTimeDate: '',
serialNum: appStore.serialNum,
});
/**表格状态类型 */
type nfStateType = {
/**主机名 */
hostName: string;
/**操作系统信息 */
osInfo: string;
/**IP地址 */
ipAddress: string;
/**版本 */
version: string;
/**CPU利用率 */
cpuUse: string;
/**内存使用 */
memoryUse: string;
/**用户容量 */
capability: number;
/**序列号 */
serialNum: string;
/**许可证到期日期 */
/* selectedRowKeys: (string | number)[];*/
expiryDate: string;
};
/**网元详细信息 */
let pronInfo: nfStateType = reactive({
hostName: '5gc',
osInfo: 'Linux 5gc 4.15.0-112-generic 2020 x86_64 GNU/Linux',
ipAddress: '-',
version: '-',
cpuUse: '-',
memoryUse: '-',
capability: 0,
serialNum: '-',
expiryDate: '-',
});
/**查询网元状态列表 */
function fnGetList(one: boolean) {
if (tableState.loading) return;
one && (tableState.loading = true);
listAllNeInfo({ bandStatus: true }).then(res => {
tableState.data = res.data;
tableState.loading = false;
var rightNum = 0;
var errorNum = 0;
res.data.forEach((item: any) => {
if (item.serverState.online) {
rightNum++;
} else {
errorNum++;
}
});
const optionData: any = {
title: {
text: '',
subtext: '',
left: 'center',
},
tooltip: {
trigger: 'item',
},
legend: {
orient: 'vertical',
left: 'left',
},
color: indexColor.value.map(item => item.tagClass),
series: [
{
name: t('views.index.realNeStatus'),
type: 'pie',
radius: '70%',
center: ['50%', '50%'],
data: [
{ value: rightNum, name: t('views.index.normal') },
{ value: errorNum, name: t('views.index.abnormal') },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
label: {},
},
],
};
fnDesign(statusBar.value, optionData);
});
}
function fnDesign(container: HTMLElement | undefined, option: any) {
if (!container) return;
if (!statusBarChart.value) {
statusBarChart.value = markRaw(echarts.init(container, 'light'));
}
option && statusBarChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (statusBarChart.value) {
statusBarChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
/**抽屉 网元详细信息 */
const open = ref(false);
const closeDrawer = () => {
open.value = false;
};
/**抽屉 网元详细信息 */
/**监听表格行事件*/
function rowClick(record: any, index: any) {
return {
onClick: (event: any) => {
let pronData = JSON.parse(JSON.stringify(record.serverState));
if (!pronData.online) {
message.error(t('views.index.neStatus'), 2);
return false;
} else {
const totalMemInKB = pronData.mem?.totalMem;
const nfUsedMemInKB = pronData.mem?.nfUsedMem;
const sysMemUsageInKB = pronData.mem?.sysMemUsage;
// 将KB转换为MB
const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100;
const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
const sysMemUsageInMB =
Math.round((sysMemUsageInKB / 1024) * 100) / 100;
//渲染详细信息
pronInfo = {
hostName: pronData.hostname,
osInfo: pronData.os,
ipAddress: pronData.neIP,
version: pronData.version,
cpuUse:
pronData.neName +
':' +
pronData.cpu?.nfCpuUsage / 100 +
'%; ' +
'SYS:' +
pronData.cpu?.sysCpuUsage / 100 +
'%',
memoryUse:
'Total:' +
totalMemInMB +
'MB; ' +
pronData.name +
':' +
nfUsedMemInMB +
'MB; SYS:' +
sysMemUsageInMB +
'MB',
capability: pronData.capability,
serialNum: pronData.sn,
expiryDate: pronData.expire,
};
}
open.value = true;
},
};
}
let timer: any;
/**
* 国际化翻译转换
*/
function fnLocale() {
let title = route.meta.title as string;
if (title.indexOf('router.') !== -1) {
title = t(title);
}
appStore.setTitle(title);
}
onMounted(() => {
getDict('index_status')
.then(res => {
if (res.length > 0) {
indexColor.value = res;
}
})
.finally(() => {
fnLocale();
fnGetList(true);
timer = setInterval(() => fnGetList(false), 10000); // 每隔10秒执行一次
});
});
// 在组件卸载之前清除定时器
onBeforeUnmount(() => {
clearInterval(timer);
});
</script>
<template>
<PageContainer :breadcrumb="{}">
<div>
<a-drawer :open="open" @close="closeDrawer" :width="700">
<a-descriptions bordered :column="1" :label-style="{ width: '160px' }">
<a-descriptions-item :label="t('views.index.hostName')">{{
pronInfo.hostName
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.osInfo')">{{
pronInfo.osInfo
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.ipAddress')">{{
pronInfo.ipAddress
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.version')">{{
pronInfo.version
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.capability')">{{
pronInfo.capability
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.cpuUse')">{{
pronInfo.cpuUse
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.memoryUse')">{{
pronInfo.memoryUse
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.serialNum')">{{
pronInfo.serialNum
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.expiryDate')">{{
pronInfo.expiryDate
}}</a-descriptions-item>
</a-descriptions>
</a-drawer>
</div>
<a-row :gutter="16">
<a-col :lg="14" :md="16" :xs="24">
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
size="small"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:pagination="false"
:scroll="{ x: true }"
:customRow="rowClick"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<div v-if="record.serverState.online">
<a-tag color="blue">{{ t('views.index.normal') }}</a-tag>
</div>
<div v-else>
<a-tag color="pink">{{ t('views.index.abnormal') }}</a-tag>
</div>
</template>
</template>
</a-table>
</a-col>
<a-col :lg="10" :md="8" :xs="24">
<a-card
:title="t('views.index.runStatus')"
style="margin-bottom: 16px"
size="small"
>
<div style="width: 100%; min-height: 200px" ref="statusBar"></div>
</a-card>
<a-card
:title="t('views.index.mark')"
style="margin-top: 16px"
size="small"
>
<a-descriptions
bordered
:column="1"
:label-style="{ width: '160px' }"
>
<a-descriptions-item :label="t('views.index.object')">{{
nfInfo.obj
}}</a-descriptions-item>
<template v-if="nfInfo.obj === 'OMC'">
<a-descriptions-item :label="t('views.index.versionNum')">{{
nfInfo.version
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.systemStatus')">{{
nfInfo.status
}}</a-descriptions-item>
</template>
<template v-else>
<a-descriptions-item :label="t('views.index.serialNum')">{{
nfInfo.serialNum
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.expiryDate')">{{
nfInfo.outTimeDate
}}</a-descriptions-item>
</template>
</a-descriptions>
</a-card>
</a-col>
</a-row>
</PageContainer>
</template>
<style lang="less" scoped></style>

View File

@@ -1,728 +0,0 @@
<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/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/es/table';
import useI18n from '@/hooks/useI18n';
import {
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import useDictStore from '@/store/modules/dict';
import { listAMFDataUE, delAMFDataUE, exportAMFDataUE } from '@/api/neData/amf';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import saveAs from 'file-saver';
import PQueue from 'p-queue';
import { hasRoles } from '@/plugins/auth-user';
const { t } = useI18n();
const { getDict } = useDictStore();
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
/**字典数据 */
let dict: {
/**UE 事件认证代码类型 */
ueAauthCode: DictType[];
/**UE 事件类型 */
ueEventType: DictType[];
/**UE 事件CM状态 */
ueEventCmState: DictType[];
} = reactive({
ueAauthCode: [],
ueEventType: [],
ueEventCmState: [],
});
/**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']);
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: 'AMF',
neId: '001',
eventType: '',
imsi: '',
sortField: 'timestamp',
sortOrder: 'desc',
/**开始时间 */
startTime: '',
/**结束时间 */
endTime: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
eventTypes.value = [];
queryParams = Object.assign(queryParams, {
eventType: '',
imsi: '',
startTime: '',
endTime: '',
pageNum: 1,
pageSize: 20,
});
queryRangePicker.value = ['', ''];
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**记录类型 */
const eventTypes = ref<string[]>([]);
/**查询记录类型变更 */
function fnQueryEventTypeChange(value: any) {
if (Array.isArray(value)) {
queryParams.eventType = 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: 'IMSI',
dataIndex: 'eventJSON',
align: 'left',
width: 150,
customRender(opt) {
const eventJSON = opt.value;
return eventJSON.imsi;
},
},
{
title: t('views.dashboard.ue.eventType'),
dataIndex: 'eventType',
key: 'eventType',
align: 'left',
width: 150,
},
{
title: t('views.dashboard.ue.result'),
dataIndex: 'eventJSON',
key: 'result',
align: 'left',
width: 150,
},
{
title: t('views.dashboard.ue.time'),
dataIndex: 'eventJSON',
key: 'time',
align: 'left',
width: 150,
},
{
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.ue.delTip', { msg }),
onOk() {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
delAMFDataUE(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];
listAMFDataUE(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 eventJSON = item.eventJSON;
if (!eventJSON) {
Reflect.set(item, 'eventJSON', {});
}
try {
eventJSON = JSON.parse(eventJSON);
Reflect.set(item, 'eventJSON', eventJSON);
} catch (error) {
console.error(error);
Reflect.set(item, 'eventJSON', {});
}
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.ue.exportTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
const querys = toRaw(queryParams);
querys.pageSize = 10000;
exportAMFDataUE(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
saveAs(res.data, `amf_ue_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) {
// 建立链接
const options: OptionsType = {
url: '/ws',
params: {
/**订阅通道组
*
* AMF_UE会话事件(GroupID:1010)
*/
subGroupID: '1010',
},
onmessage: wsMessage,
onerror: wsError,
};
ws.connect(options);
} else {
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;
}
// ueEvent AMF_UE会话事件
if (data.groupId === '1010') {
const ueEvent = data.data;
queue.add(async () => {
modalState.maxId += 1;
tableState.data.unshift({
id: modalState.maxId,
neType: ueEvent.neType,
neName: ueEvent.neName, // 空
rmUID: ueEvent.rmUID, // 空
timestamp: ueEvent.timestamp,
eventType: ueEvent.eventType,
eventJSON: ueEvent.eventJSON,
});
tablePagination.total += 1;
if (tableState.data.length > 100) {
tableState.data.pop();
}
await new Promise(resolve => setTimeout(resolve, 800));
});
}
}
onMounted(() => {
// 初始字典数据
Promise.allSettled([
getDict('ue_auth_code'),
getDict('ue_event_type'),
getDict('ue_event_cm_state'),
])
.then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.ueAauthCode = resArr[0].value;
}
if (resArr[1].status === 'fulfilled') {
dict.ueEventType = resArr[1].value;
}
if (resArr[2].status === 'fulfilled') {
dict.ueEventCmState = resArr[2].value;
}
})
.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="t('views.dashboard.ue.eventType')"
name="eventType "
>
<a-select
v-model:value="eventTypes"
mode="multiple"
:options="dict.ueEventType"
:placeholder="t('common.selectPlease')"
@change="fnQueryEventTypeChange"
></a-select>
</a-form-item>
</a-col>
<a-col :lg="4" :md="12" :xs="24">
<a-form-item label="IMSI" name="imsi ">
<a-input
v-model:value="queryParams.imsi"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</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-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-popconfirm
placement="bottomLeft"
:title="
!realTimeData
? t('views.dashboard.ue.realTimeDataStart')
: t('views.dashboard.ue.realTimeDataStop')
"
ok-text="Yes"
cancel-text="No"
@confirm="fnRealTime()"
>
<a-button type="primary" :danger="realTimeData">
<template #icon><FundOutlined /> </template>
{{
!realTimeData
? t('views.dashboard.ue.realTimeDataStart')
: t('views.dashboard.ue.realTimeDataStop')
}}
</a-button>
</a-popconfirm>
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading"
@click.prevent="fnRecordDelete('0')"
v-if="!hasRoles(['student'])"
>
<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"
/>
</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="hasRoles(['student']) ? tableColumns.filter((s:any)=>s.key !== 'id'): tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'eventType'">
<DictTag :options="dict.ueEventType" :value="record.eventType" />
</template>
<template v-if="column.key === 'result'">
<span v-if="record.eventType === 'auth-result'">
<DictTag
:options="dict.ueAauthCode"
:value="record.eventJSON.authCode"
/>
</span>
<span v-if="record.eventType === 'detach'">
<span>{{ t('views.dashboard.ue.resultOk') }}</span>
</span>
<span v-if="record.eventType === 'cm-state'">
<DictTag
:options="dict.ueEventCmState"
:value="record.eventJSON.status"
/>
</span>
</template>
<template v-if="column.key === 'time'">
<span
v-if="record.eventType === 'auth-result'"
:title="record.eventJSON.authTime"
>
{{ record.eventJSON.authTime }}
</span>
<span
v-if="record.eventType === 'detach'"
:title="record.eventJSON.detachTime"
>
{{ record.eventJSON.detachTime }}
</span>
<span
v-if="record.eventType === 'cm-state'"
:title="record.eventJSON.changeTime"
>
{{ record.eventJSON.changeTime }}
</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.ue.ueInfo') }}
</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>
<a-divider orientation="left">
{{ t('views.dashboard.ue.rowInfo') }}
</a-divider>
<div>
<span>{{ t('views.dashboard.ue.time') }}: </span>
<span
v-if="record.eventType === 'auth-result'"
:title="record.eventJSON.authTime"
>
{{ record.eventJSON.authTime }}
</span>
<span
v-if="record.eventType === 'detach'"
:title="record.eventJSON.detachTime"
>
{{ record.eventJSON.detachTime }}
</span>
<span
v-if="record.eventType === 'cm-state'"
:title="record.eventJSON.changeTime"
>
{{ record.eventJSON.changeTime }}
</span>
</div>
<div>
<span>{{ t('views.dashboard.ue.eventType') }}: </span>
<DictTag :options="dict.ueEventType" :value="record.eventType" />
</div>
<div>
<span>{{ t('views.dashboard.ue.result') }}: </span>
<span v-if="record.eventType === 'auth-result'">
<DictTag
:options="dict.ueAauthCode"
:value="record.eventJSON.authCode"
/>
</span>
<span v-if="record.eventType === 'detach'">
{{ t('views.dashboard.ue.resultOk') }}
</span>
<span v-if="record.eventType === 'cm-state'">
<DictTag
:options="dict.ueEventCmState"
:value="record.eventJSON.status"
/>
</span>
</div>
</div>
</template>
</a-table>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -1,839 +0,0 @@
<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/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/es/table';
import useI18n from '@/hooks/useI18n';
import {
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import useDictStore from '@/store/modules/dict';
import useNeInfoStore from '@/store/modules/neinfo';
import {
delIMSDataCDR,
exportIMSDataCDR,
listIMSDataCDR,
} from '@/api/neData/ims';
import { parseDateToStr, parseDuration } from '@/utils/date-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import saveAs from 'file-saver';
import PQueue from 'p-queue';
import { hasRoles } from '@/plugins/auth-user';
const { t } = useI18n();
const { getDict } = useDictStore();
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
/**字典数据 */
let dict: {
/**CDR SIP响应代码类别类型 */
cdrSipCode: DictType[];
/**CDR 呼叫类型 */
cdrCallType: DictType[];
} = reactive({
cdrSipCode: [],
cdrCallType: [],
});
/**网元可选 */
let neOtions = ref<Record<string, any>[]>([]);
/**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']);
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: 'IMS',
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',
key: 'callType',
align: 'left',
width: 100,
},
{
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: 150,
},
{
title: t('views.dashboard.cdr.duration'),
dataIndex: 'cdrJSON',
key: 'callDuration',
align: 'left',
width: 100,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.callType === 'sms'
? '-'
: parseDuration(cdrJSON.callDuration);
},
},
{
title: t('views.dashboard.cdr.seizureTime'),
dataIndex: 'cdrJSON',
align: 'left',
width: 200,
customRender(opt) {
const cdrJSON = opt.value;
if (typeof cdrJSON.seizureTime === 'number') {
return parseDateToStr(+cdrJSON.seizureTime * 1000);
}
return cdrJSON.seizureTime;
},
},
{
title: t('views.dashboard.cdr.releaseTime'),
dataIndex: 'cdrJSON',
align: 'left',
width: 200,
customRender(opt) {
const cdrJSON = opt.value;
if (typeof cdrJSON.releaseTime === 'number') {
return parseDateToStr(+cdrJSON.releaseTime * 1000);
}
return cdrJSON.releaseTime;
},
},
{
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);
delIMSDataCDR(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];
listIMSDataCDR(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;
exportIMSDataCDR(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
saveAs(res.data, `ims_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: {
/**订阅通道组
*
* IMS_CDR会话事件(GroupID:1005)
*/
subGroupID: `1005_${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 === `1005_${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_sip_code'), getDict('cdr_call_type')]).then(
resArr => {
if (resArr[0].status === 'fulfilled') {
dict.cdrSipCode = resArr[0].value;
}
if (resArr[1].status === 'fulfilled') {
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(() => {
// 获取列表数据
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="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-form-item
:label="t('views.dashboard.cdr.recordType')"
name="recordType"
>
<a-select
v-model:value="recordTypes"
mode="multiple"
:options="['MOC', 'MTC'].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')"
v-if="!hasRoles(['student'])"
>
<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="hasRoles(['student']) ? tableColumns.filter((s:any)=>s.key !== 'id'): 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 === 'callType'">
<DictTag
:options="dict.cdrCallType"
:value="record.cdrJSON.callType"
/>
</template>
<template v-if="column.key === 'cause'">
<span v-if="record.cdrJSON.callType !== 'sms'">
<DictTag
:options="dict.cdrSipCode"
: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 }">
<a-row :gutter="16">
<a-col :lg="5" :md="12" :xs="24">
<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>
{{
typeof record.cdrJSON.releaseTime === 'number'
? parseDateToStr(+record.cdrJSON.releaseTime * 1000)
: record.cdrJSON.releaseTime
}}
</span>
</div>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-divider orientation="left">
{{ t('views.dashboard.cdr.rowInfo') }}
</a-divider>
<div>
<span>{{ t('views.dashboard.cdr.type') }}: </span>
<DictTag
:options="dict.cdrCallType"
:value="record.cdrJSON.callType"
/>
</div>
<div>
<span>{{ t('views.dashboard.cdr.duration') }}: </span>
<span v-if="record.cdrJSON.callType !== 'sms'">
{{ parseDuration(record.cdrJSON.callDuration) }}
</span>
<span v-else> - </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.callType !== 'sms'">
<DictTag
:options="dict.cdrSipCode"
:value="record.cdrJSON.cause"
value-default="0"
/>
</span>
<span v-else>
{{ t('views.dashboard.cdr.resultOk') }}
</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.seizureTime') }}: </span>
<span>
{{
typeof record.cdrJSON.seizureTime === 'number'
? parseDateToStr(+record.cdrJSON.seizureTime * 1000)
: record.cdrJSON.seizureTime
}}
</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.releaseTime') }}: </span>
<span>
{{
typeof record.cdrJSON.releaseTime === 'number'
? parseDateToStr(+record.cdrJSON.releaseTime * 1000)
: record.cdrJSON.releaseTime
}}
</span>
</div>
</a-col>
</a-row>
</template>
</a-table>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -1,742 +0,0 @@
<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/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/es/table';
import useI18n from '@/hooks/useI18n';
import {
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import useDictStore from '@/store/modules/dict';
import useNeInfoStore from '@/store/modules/neinfo';
import { listMMEDataUE, delMMEDataUE, exportMMEDataUE } from '@/api/neData/mme';
import { parseDateToStr } from '@/utils/date-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import saveAs from 'file-saver';
import PQueue from 'p-queue';
import { hasRoles } from '@/plugins/auth-user';
const { t } = useI18n();
const { getDict } = useDictStore();
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
/**网元可选 */
let neOtions = ref<Record<string, any>[]>([]);
/**字典数据 */
let dict: {
/**UE 事件认证代码类型 */
ueAauthCode: DictType[];
/**UE 事件类型 */
ueEventType: DictType[];
/**UE 事件CM状态 */
ueEventCmState: DictType[];
} = reactive({
ueAauthCode: [],
ueEventType: [],
ueEventCmState: [],
});
/**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']);
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: 'MME',
neId: '001',
eventType: '',
imsi: '',
sortField: 'timestamp',
sortOrder: 'desc',
/**开始时间 */
startTime: '',
/**结束时间 */
endTime: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
eventTypes.value = [];
queryParams = Object.assign(queryParams, {
eventType: '',
imsi: '',
startTime: '',
endTime: '',
pageNum: 1,
pageSize: 20,
});
queryRangePicker.value = ['', ''];
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**记录类型 */
const eventTypes = ref<string[]>([]);
/**查询记录类型变更 */
function fnQueryEventTypeChange(value: any) {
if (Array.isArray(value)) {
queryParams.eventType = 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: 'IMSI',
dataIndex: 'eventJSON',
align: 'left',
width: 150,
customRender(opt) {
const eventJSON = opt.value;
return eventJSON.imsi;
},
},
{
title: t('views.dashboard.ue.eventType'),
dataIndex: 'eventType',
key: 'eventType',
align: 'left',
width: 150,
},
{
title: t('views.dashboard.ue.result'),
dataIndex: 'eventJSON',
key: 'result',
align: 'left',
width: 150,
},
{
title: t('views.dashboard.ue.time'),
dataIndex: 'eventJSON',
align: 'left',
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
return parseDateToStr(+cdrJSON.timestamp * 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.ue.delTip', { msg }),
onOk() {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
delMMEDataUE(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];
listMMEDataUE(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 eventJSON = item.eventJSON;
if (!eventJSON) {
Reflect.set(item, 'eventJSON', {});
}
try {
eventJSON = JSON.parse(eventJSON);
Reflect.set(item, 'eventJSON', eventJSON);
} catch (error) {
console.error(error);
Reflect.set(item, 'eventJSON', {});
}
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.ue.exportTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
const querys = toRaw(queryParams);
querys.pageSize = 10000;
exportMMEDataUE(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
saveAs(res.data, `mme_ue_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: {
/**订阅通道组
*
* MME_UE会话事件(GroupID:1011)
*/
subGroupID: `1011_${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;
}
// ueEvent MME_UE会话事件
if (data.groupId === `1011_${queryParams.neId}`) {
const ueEvent = data.data;
queue.add(async () => {
modalState.maxId += 1;
tableState.data.unshift({
id: modalState.maxId,
neType: ueEvent.neType,
neName: ueEvent.neName, // 空
rmUID: ueEvent.rmUID, // 空
timestamp: ueEvent.timestamp,
eventType: ueEvent.eventType,
eventJSON: ueEvent.eventJSON,
});
tablePagination.total += 1;
if (tableState.data.length > 100) {
tableState.data.pop();
}
await new Promise(resolve => setTimeout(resolve, 800));
});
}
}
onMounted(() => {
// 初始字典数据
Promise.allSettled([
getDict('ue_auth_code'),
getDict('ue_event_type'),
getDict('ue_event_cm_state'),
]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.ueAauthCode = resArr[0].value;
}
if (resArr[1].status === 'fulfilled') {
const ueEventType: any[] = JSON.parse(JSON.stringify(resArr[1]));
dict.ueEventType = ueEventType.map(item => {
if (item.value === 'cm-state') {
item.label = item.label.replace('CM', 'ECM');
}
return item;
});
}
if (resArr[2].status === 'fulfilled') {
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(() => {
// 获取列表数据
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="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-form-item
:label="t('views.dashboard.ue.eventType')"
name="eventType "
>
<a-select
v-model:value="eventTypes"
mode="multiple"
:options="dict.ueEventType"
:placeholder="t('common.selectPlease')"
@change="fnQueryEventTypeChange"
></a-select>
</a-form-item>
</a-col>
<a-col :lg="4" :md="12" :xs="24">
<a-form-item label="IMSI" name="imsi ">
<a-input
v-model:value="queryParams.imsi"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</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-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-popconfirm
placement="bottomLeft"
:title="
!realTimeData
? t('views.dashboard.ue.realTimeDataStart')
: t('views.dashboard.ue.realTimeDataStop')
"
ok-text="Yes"
cancel-text="No"
@confirm="fnRealTime()"
>
<a-button type="primary" :danger="realTimeData">
<template #icon><FundOutlined /> </template>
{{
!realTimeData
? t('views.dashboard.ue.realTimeDataStart')
: t('views.dashboard.ue.realTimeDataStop')
}}
</a-button>
</a-popconfirm>
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading"
@click.prevent="fnRecordDelete('0')"
v-if="!hasRoles(['student'])"
>
<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="hasRoles(['student']) ? tableColumns.filter((s:any)=>s.key !== 'id'): tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'eventType'">
<DictTag :options="dict.ueEventType" :value="record.eventType" />
</template>
<template v-if="column.key === 'result'">
<span v-if="record.eventType === 'auth-result'">
<DictTag
:options="dict.ueAauthCode"
:value="record.eventJSON.result"
/>
</span>
<span v-if="record.eventType === 'detach'">
<span>{{ t('views.dashboard.ue.resultOk') }}</span>
</span>
<span v-if="record.eventType === 'cm-state'">
<DictTag
:options="dict.ueEventCmState"
:value="record.eventJSON.result"
/>
</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.ue.ueInfo') }}
</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>
<a-divider orientation="left">
{{ t('views.dashboard.ue.rowInfo') }}
</a-divider>
<div>
<span>{{ t('views.dashboard.ue.time') }}: </span>
{{ parseDateToStr(record.eventJSON.timestamp * 1000) }}
</div>
<div>
<span>{{ t('views.dashboard.ue.eventType') }}: </span>
<DictTag :options="dict.ueEventType" :value="record.eventType" />
</div>
<div>
<span>{{ t('views.dashboard.ue.result') }}: </span>
<span v-if="record.eventType === 'auth-result'">
<DictTag
:options="dict.ueAauthCode"
:value="record.eventJSON.result"
/>
</span>
<span v-if="record.eventType === 'detach'">
{{ t('views.dashboard.ue.resultOk') }}
</span>
<span v-if="record.eventType === 'cm-state'">
<DictTag
:options="dict.ueEventCmState"
:value="record.eventJSON.result"
/>
</span>
</div>
</div>
</template>
</a-table>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -1,284 +0,0 @@
<script setup lang="ts">
import * as echarts from 'echarts/core';
import {
TitleComponent,
TitleComponentOption,
TooltipComponent,
TooltipComponentOption,
GridComponent,
GridComponentOption,
LegendComponent,
LegendComponentOption,
} from 'echarts/components';
import {
PieChart,
PieSeriesOption,
BarChart,
BarSeriesOption,
} from 'echarts/charts';
import { LabelLayout } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import { markRaw, onMounted, ref } from 'vue';
import { origGet, top3Sel } from '@/api/faultManage/actAlarm';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
const { t } = useI18n();
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
PieChart,
BarChart,
CanvasRenderer,
LabelLayout,
]);
type EChartsOption = echarts.ComposeOption<
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| LegendComponentOption
| PieSeriesOption
| BarSeriesOption
>;
/**图DOM节点实例对象 */
const alarmTypeBar = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
const alarmTypeBarChart = ref<any>(null);
/**告警类型数据 */
const alarmTypeType = ref<any>([
{
value: 0,
name: t('views.index.Critical'),
},
{
value: 0,
name: t('views.index.Major'),
},
{
value: 0,
name: t('views.index.Minor'),
},
{
value: 0,
name: t('views.index.Warning'),
},
// {
// value: 0,
// name: t('views.index.Event'),
// },
]);
/**告警类型Top数据 */
const alarmTypeTypeTop = ref<any>([
{ name: 'AMF', value: 0 },
{ name: 'UDM', value: 0 },
{ name: 'SMF', value: 0 },
]);
//
function initPicture() {
Promise.allSettled([origGet(), top3Sel()])
.then(resArr => {
if (resArr[0].status === 'fulfilled') {
const res0 = resArr[0].value;
if (res0.code === RESULT_CODE_SUCCESS && Array.isArray(res0.data)) {
for (const item of res0.data) {
let index = 0;
switch (item.name) {
case 'Critical':
index = 0;
break;
case 'Major':
index = 1;
break;
case 'Minor':
index = 2;
break;
case 'Warning':
index = 3;
break;
// case 'Event':
// index = 4;
// break;
}
alarmTypeType.value[index].value = Number(item.value);
}
}
}
if (resArr[1].status === 'fulfilled') {
const res1 = resArr[1].value;
if (res1.code === RESULT_CODE_SUCCESS && Array.isArray(res1.data)) {
alarmTypeTypeTop.value = alarmTypeTypeTop.value
.concat(res1.data)
.sort((a: any, b: any) => {
return b.value - a.value;
})
.slice(0, 3);
}
}
})
.then(() => {
const optionData: EChartsOption = {
title: [
{
show: false,
},
{
text: t('views.dashboard.overview.alarmTypeBar.topTitle'),
textStyle: {
color: '#fff',
fontSize: '14',
fontWeight: 400,
},
top: '50%',
left: '0%',
},
],
tooltip: {
trigger: 'item',
formatter: '{b} : {c}',
},
legend: {
orient: 'vertical',
right: '2%',
top: '12%',
data: alarmTypeType.value.map((item: any) => item.name), //label数组
textStyle: {
color: '#A7D6F4', // 设置图例文字颜色
},
},
grid: [
{
top: '60%',
left: '15%',
right: '25%',
bottom: '10%',
},
],
series: [
//饼图:
{
type: 'pie',
radius: '35%',
color: ['#f5222d', '#fa8c16', '#fadb14', '#1677ff', '#13c2c2'],
label: {
show: true,
position: 'inner',
formatter: (params: any) => {
if (!params.value) return '';
return `${params.value}`;
},
},
labelLine: {
show: false,
},
center: ['35%', '25%'],
data: alarmTypeType.value,
zlevel: 2, // 设置zlevel为1使得柱状图在下层显示
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
//柱状
{
type: 'bar',
barWidth: 12, // 柱子宽度
barCategoryGap: '30%', // 控制同一系列的柱间距离
label: {
show: true,
position: 'right', // 位置
color: '#A7D6F4', //淡蓝色
fontSize: 14,
distance: 14, // label与柱子距离
formatter: '{c}',
},
itemStyle: {
borderRadius: [0, 20, 20, 0], // 圆角(左上、右上、右下、左下)
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#f0f5ff' },
{ offset: 0.5, color: '#adc6ff' },
{ offset: 1, color: '#2f54eb' },
]), // 渐变
},
data: alarmTypeTypeTop.value,
},
],
// 柱状图设置
xAxis: [
{
splitLine: {
show: false,
},
type: 'value',
show: false,
},
],
yAxis: [
{
splitLine: {
show: false,
},
axisLine: {
//y轴
show: false,
},
type: 'category',
axisTick: {
show: false,
},
inverse: true,
data: alarmTypeTypeTop.value.map((item: any) => item.name),
axisLabel: {
color: '#A7D6F4',
fontSize: 14,
},
},
],
};
fnDesign(alarmTypeBar.value, optionData);
});
}
function fnDesign(container: HTMLElement | undefined, option: any) {
if (!container) return;
alarmTypeBarChart.value = markRaw(echarts.init(container, 'light'));
option && alarmTypeBarChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (alarmTypeBarChart.value) {
alarmTypeBarChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
onMounted(() => {
initPicture();
});
</script>
<template>
<div ref="alarmTypeBar" class="chart-container"></div>
</template>
<style lang="less" scoped>
.chart-container {
/* 设置图表容器大小和位置 */
width: 100%;
height: 100%;
}
</style>

View File

@@ -1,368 +0,0 @@
<script setup lang="ts">
import { onMounted, ref, nextTick, watch } from 'vue';
import * as echarts from 'echarts/core';
import { GridComponent, GridComponentOption } from 'echarts/components';
import {
BarChart,
BarSeriesOption,
PictorialBarChart,
PictorialBarSeriesOption,
} from 'echarts/charts';
import { CanvasRenderer } from 'echarts/renderers';
import { graphNodeClickID, graphNodeState } from '../../hooks/useTopology';
import useI18n from '@/hooks/useI18n';
import { markRaw } from 'vue';
const { t } = useI18n();
echarts.use([GridComponent, BarChart, PictorialBarChart, CanvasRenderer]);
type EChartsOption = echarts.ComposeOption<
GridComponentOption | BarSeriesOption | PictorialBarSeriesOption
>;
/**图DOM节点实例对象 */
const neResourcesDom = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
const neResourcesChart = ref<any>(null);
// 类别
const category = ref<any>([
{
name: t('views.dashboard.overview.resources.sysDisk'),
value: 1,
},
{
name: t('views.dashboard.overview.resources.sysMem'),
value: 1,
},
{
name: t('views.dashboard.overview.resources.sysCpu'),
value: 1,
},
{
name: t('views.dashboard.overview.resources.neCpu'),
value: 1,
},
]);
// 数据总数
const total = 100;
/**图数据 */
const optionData: EChartsOption = {
xAxis: {
max: total,
splitLine: {
show: false,
},
axisLine: {
show: false,
},
axisLabel: {
show: false,
},
axisTick: {
show: false,
},
},
grid: {
top: '1%', // 设置条形图的边距
bottom: '12%',
left: '25%',
right: '25%',
},
yAxis: [
{
type: 'category',
inverse: false,
data: category.value,
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
show: false,
},
},
],
series: [
{
// 内
type: 'bar',
barWidth: 10,
legendHoverLink: false,
silent: true,
itemStyle: {
color: function (params) {
// 红色
if (params.value && +params.value >= 70) {
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#fff1f0' },
{ offset: 0.5, color: '#ffa39e' },
{ offset: 1, color: '#f5222d' },
]);
}
// 蓝色
if (params.value && +params.value >= 30) {
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#f0f5ff' },
{ offset: 0.5, color: '#adc6ff' },
{ offset: 1, color: '#2f54eb' },
]);
}
// 绿色
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#f6ffed' },
{ offset: 0.5, color: '#b7eb8f' },
{ offset: 1, color: '#52c41a' },
]);
},
},
label: {
show: true,
position: 'left',
formatter: '{b}: ',
fontSize: 15,
color: '#fff',
},
data: category.value,
z: 1,
animationEasing: 'elasticOut',
},
{
// 分隔
type: 'pictorialBar',
itemStyle: {
color: '#0a3ca0',
},
symbolRepeat: 'fixed',
symbolMargin: 6,
symbol: 'rect',
symbolClip: true,
symbolSize: [1, 12],
symbolPosition: 'start',
symbolOffset: [0, -1],
symbolBoundingData: total,
data: category.value,
z: 2,
animationEasing: 'elasticOut',
},
{
// 外边框
type: 'pictorialBar',
symbol: 'rect',
symbolBoundingData: total,
itemStyle: {
color: 'transparent',
},
label: {
formatter: params => {
var text = `{a| ${params.value}%} `;
if (params.value && +params.value >= 70) {
text = `{c| ${params.value}%} `;
} else if (params.value && +params.value >= 30) {
text = `{b| ${params.value}%} `;
}
return text;
},
rich: {
a: {
color: '#52c41a', // 绿
fontSize: 16,
},
b: {
color: '#2f54eb', // 蓝
fontSize: 16,
},
c: {
color: '#f5222d', // 红
fontSize: 16,
},
f: {
color: '#ffffff', // 默认
fontSize: 16,
},
},
position: 'right',
distance: 0, // 向右偏移位置
show: true,
},
data: category.value,
z: 0,
animationEasing: 'elasticOut',
},
{
name: '外框',
type: 'bar',
barGap: '-120%', // 设置外框粗细
data: [total, total, total],
barWidth: 14,
itemStyle: {
color: 'transparent', // 填充色
borderColor: '#0a3ca0', // 边框色
borderWidth: 1, // 边框宽度
borderRadius: 1, //圆角半径
},
label: {
// 标签显示位置
show: false,
position: 'top', // insideTop 或者横向的 insideLeft
},
z: 0,
},
],
};
/**图数据渲染 */
function handleRanderChart(
container: HTMLElement | undefined,
option: EChartsOption
) {
if (!container) return;
neResourcesChart.value = markRaw(echarts.init(container, 'light'));
option && neResourcesChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (neResourcesChart.value) {
neResourcesChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
function fnChangeData(data: any[], itemID: string) {
let info = data.find((item: any) => item.id === itemID);
if (!info || !info.neState.online) return;
// if (!info.neState.online) {
// info = data.find((item: any) => item.id === itemID);
// graphNodeClickID.value = itemID;
// }
// console.log(info.id);
// console.log(info.neState.cpu.nfCpuUsage);
// console.log(info.neState.cpu.sysCpuUsage);
// console.log(info.neState.mem);
// console.log(info.neState.disk);
let sysCpuUsage = 0;
let nfCpuUsage = 0;
if (info.neState.cpu) {
nfCpuUsage = info.neState.cpu.nfCpuUsage;
const nfCpu = +(info.neState.cpu.nfCpuUsage / 100);
nfCpuUsage = +nfCpu.toFixed(2);
if (nfCpuUsage > 100) {
nfCpuUsage = 100;
}
sysCpuUsage = info.neState.cpu.sysCpuUsage;
let sysCpu = +(info.neState.cpu.sysCpuUsage / 100);
sysCpuUsage = +sysCpu.toFixed(2);
if (sysCpuUsage > 100) {
sysCpuUsage = 100;
}
}
let sysMemUsage = 0;
if (info.neState.mem) {
const men = info.neState.mem.sysMemUsage;
sysMemUsage = +(men / 100).toFixed(2);
if (sysMemUsage > 100) {
sysMemUsage = 100;
}
}
let sysDiskUsage = 0;
if (info.neState.disk && Array.isArray(info.neState.disk.partitionInfo)) {
let disks: any[] = info.neState.disk.partitionInfo;
disks = disks.sort((a, b) => +b.used - +a.used);
if (disks.length > 0) {
const { total, used } = disks[0];
sysDiskUsage = +((used / total) * 100).toFixed(2);
}
}
category.value[0].value = sysDiskUsage;
category.value[1].value = sysMemUsage;
category.value[2].value = sysCpuUsage;
category.value[3].value = nfCpuUsage;
neResourcesChart.value.setOption({
series: [
{
data: category.value,
},
{
data: category.value,
},
{
data: category.value,
},
{
data: category.value,
},
],
});
}
watch(
graphNodeState,
v => {
fnChangeData(v, graphNodeClickID.value);
},
{
deep: true,
}
);
watch(graphNodeClickID, v => {
fnChangeData(graphNodeState.value, v);
});
onMounted(() => {
// setInterval(function () {
// var ndata = [
// {
// name: '系统内存',
// value: Math.round(Math.random() * 100),
// },
// {
// name: '系统CPU',
// value: Math.round(Math.random() * 100),
// },
// {
// name: '网元CPU',
// value: Math.round(Math.random() * 100),
// },
// ];
// neResourcesChart.value.setOption({
// series: [
// {
// data: ndata,
// },
// {
// data: ndata,
// },
// {
// data: ndata,
// },
// ],
// });
// }, 2000);
nextTick(() => {
handleRanderChart(neResourcesDom.value, optionData);
});
});
</script>
<template>
<div ref="neResourcesDom" class="chart"></div>
</template>
<style lang="less" scoped>
.chart {
width: 100%;
height: 100%;
}
</style>

View File

@@ -1,267 +0,0 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { listAllNeInfo } from '@/api/ne/neInfo';
import { message } from 'ant-design-vue/es';
import { getGraphData } from '@/api/monitor/topology';
import { Graph, GraphData, Tooltip } from '@antv/g6';
import { parseBasePath } from '@/plugins/file-static-url';
import { edgeLineAnimateState } from '@/views/monitor/topologyBuild/hooks/registerEdge';
import { nodeImageAnimateState } from '@/views/monitor/topologyBuild/hooks/registerNode';
import {
graphG6,
graphState,
graphNodeClickID,
notNeNodes,
} from '../../hooks/useTopology';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
/**图DOM节点实例对象 */
const graphG6Dom = ref<HTMLElement | undefined>(undefined);
/**图节点展示 */
const graphNodeTooltip = new Tooltip({
offsetX: 20,
offsetY: 20,
getContent(evt) {
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
const { id, label, neState }: any = evt.item?.getModel();
if (notNeNodes.includes(id)) {
return `<div><span>${label || id}</span></div>`;
}
if (!neState) {
return `<div><span>${label || id}</span></div>`;
}
return `
<div
style="
display: flex;
flex-direction: column;
width: 200px;
"
>
<div><strong>${t('views.monitor.topology.state')}</strong><span>
${
neState.online
? t('views.monitor.topology.normalcy')
: t('views.monitor.topology.exceptions')
}
</span></div>
<div><strong>${t('views.monitor.topology.refreshTime')}</strong><span>
${neState.refreshTime ?? '--'}
</span></div>
<div>========================</div>
<div><strong>ID</strong><span>${neState.neId}</span></div>
<div><strong>${t('views.monitor.topology.name')}</strong><span>
${neState.neName ?? '--'}
</span></div>
<div><strong>IP</strong><span>${neState.neIP}</span></div>
<div><strong>${t('views.monitor.topology.version')}</strong><span>
${neState.version ?? '--'}
</span></div>
<div><strong>${t('views.monitor.topology.serialNum')}</strong><span>
${neState.sn ?? '--'}
</span></div>
<div><strong>${t('views.monitor.topology.expiryDate')}</strong><span>
${neState.expire ?? '--'}
</span></div>
</div>
`;
},
itemTypes: ['node'],
});
/**图绑定事件 */
function fnGraphEvent(graph: Graph) {
// 节点点击
graph.on('node:click', evt => {
// 获得鼠标当前目标节点
const node = evt.item?.getModel();
if (node && node.id && !notNeNodes.includes(node.id)) {
graphNodeClickID.value = node.id;
}
});
}
/**图数据渲染 */
function handleRanderGraph(
container: HTMLElement | undefined,
data: GraphData
) {
if (!container) return;
const { clientHeight, clientWidth } = container;
edgeLineAnimateState();
nodeImageAnimateState();
const graph = new Graph({
container: container,
width: clientWidth,
height: clientHeight - 36,
fitCenter: true,
fitView: true,
fitViewPadding: [20],
autoPaint: true,
modes: {
default: ['drag-canvas', 'zoom-canvas'],
},
groupByTypes: false,
nodeStateStyles: {
selected: {
fill: 'transparent',
},
},
plugins: [graphNodeTooltip],
animate: true, // 是否使用动画过度,默认为 false
animateCfg: {
duration: 500, // Number一次动画的时长
easing: 'linearEasing', // String动画函数
},
});
graph.data(data);
graph.render();
fnGraphEvent(graph);
graphG6.value = graph;
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(function (entries) {
// 当元素大小发生变化时触发回调函数
entries.forEach(function (entry) {
if (!graphG6.value) {
return;
}
graphG6.value.changeSize(
entry.contentRect.width,
entry.contentRect.height - 30
);
graphG6.value.fitCenter();
});
});
// 监听元素大小变化
observer.observe(container);
return graph;
}
/**
* 获取图组数据渲染到画布
* @param reload 是否重载数据
*/
function fnGraphDataLoad(reload: boolean = false) {
Promise.all([
getGraphData(graphState.group),
listAllNeInfo({
bandStatus: false,
}),
])
.then(resArr => {
const graphRes = resArr[0];
const neRes = resArr[1];
if (
graphRes.code === RESULT_CODE_SUCCESS &&
Array.isArray(graphRes.data.nodes) &&
graphRes.data.nodes.length > 0 &&
neRes.code === RESULT_CODE_SUCCESS &&
Array.isArray(neRes.data) &&
neRes.data.length > 0
) {
return {
graphData: graphRes.data,
neList: neRes.data,
};
} else {
message.warning({
content: t('views.monitor.topology.noData'),
duration: 5,
});
}
})
.then(res => {
if (!res) return;
const { combos, edges, nodes } = res.graphData;
// 节点过滤
const nf: Record<string, any>[] = nodes.filter(
(node: Record<string, any>) => {
Reflect.set(node, 'neState', { online: false });
// 图片路径处理
if (node.img) node.img = parseBasePath(node.img);
if (node.icon.show && node.icon?.img) {
node.icon.img = parseBasePath(node.icon.img);
}
// 遍历是否有网元数据
const nodeID: string = node.id;
const hasNe = res.neList.some(ne => {
Reflect.set(node, 'neInfo', ne.neType === nodeID ? ne : {});
return ne.neType === nodeID;
});
if (hasNe) {
return true;
}
if (notNeNodes.includes(nodeID)) {
return true;
}
return false;
}
);
// 边过滤
const ef: Record<string, any>[] = edges.filter(
(edge: Record<string, any>) => {
const edgeSource: string = edge.source;
const edgeTarget: string = edge.target;
const hasNeS = nf.some(n => n.id === edgeSource);
const hasNeT = nf.some(n => n.id === edgeTarget);
// console.log(hasNeS, edgeSource, hasNeT, edgeTarget);
if (hasNeS && hasNeT) {
return true;
}
if (hasNeS && notNeNodes.includes(edgeTarget)) {
return true;
}
if (hasNeT && notNeNodes.includes(edgeSource)) {
return true;
}
return false;
}
);
// 分组过滤
combos.forEach((combo: Record<string, any>) => {
const comboChildren: Record<string, any>[] = combo.children;
combo.children = comboChildren.filter(c => nf.some(n => n.id === c.id));
return combo;
});
// 图数据
graphState.data = { combos, edges: ef, nodes: nf };
})
.finally(() => {
if (graphState.data.length < 0) return;
// 重载数据
if (reload) {
graphG6.value.read(graphState.data);
} else {
handleRanderGraph(graphG6Dom.value, graphState.data);
}
});
}
onMounted(() => {
fnGraphDataLoad(false);
});
</script>
<template>
<div ref="graphG6Dom" class="chart"></div>
</template>
<style lang="less" scoped>
.chart {
width: 100%;
height: 100%;
}
</style>

View File

@@ -1,290 +0,0 @@
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import { listKPIData } from '@/api/perfManage/goldTarget';
import * as echarts from 'echarts/core';
import {
TooltipComponent,
TooltipComponentOption,
GridComponent,
GridComponentOption,
LegendComponent,
LegendComponentOption,
} from 'echarts/components';
import { LineChart, LineSeriesOption } from 'echarts/charts';
import { UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import { markRaw } from 'vue';
import useI18n from '@/hooks/useI18n';
import { parseDateToStr } from '@/utils/date-utils';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { upfFlowData, upfFlowParse } from '../../hooks/useUPFTotalFlow';
const { t } = useI18n();
echarts.use([
TooltipComponent,
GridComponent,
LegendComponent,
LineChart,
CanvasRenderer,
UniversalTransition,
]);
type EChartsOption = echarts.ComposeOption<
| TooltipComponentOption
| GridComponentOption
| LegendComponentOption
| LineSeriesOption
>;
/**图DOM节点实例对象 */
const upfFlow = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
const upfFlowChart = ref<any>(null);
function fnDesign(container: HTMLElement | undefined, option: EChartsOption) {
if (!container) {
return;
}
if (!upfFlowChart.value) {
upfFlowChart.value = markRaw(echarts.init(container, 'light'));
}
option && upfFlowChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (upfFlowChart.value) {
upfFlowChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
//渲染速率图
function handleRanderChart() {
const { lineXTime, lineYUp, lineYDown } = upfFlowData.value;
var yAxisSeries: any = [
{
name: t('views.dashboard.overview.upfFlow.up'),
type: 'line',
color: 'rgba(250, 219, 20)',
smooth: true,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(250, 219, 20, .5)',
},
{
offset: 1,
color: 'rgba(250, 219, 20, 0.5)',
},
]),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10,
},
symbol: 'circle',
symbolSize: 5,
formatter: '{b}',
data: lineYUp,
},
{
name: t('views.dashboard.overview.upfFlow.down'),
type: 'line',
color: 'rgba(92, 123, 217)',
smooth: true,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(92, 123, 217, .5)',
},
{
offset: 1,
color: 'rgba(92, 123, 217, 0.5)',
},
]),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10,
},
symbol: 'circle',
symbolSize: 5,
formatter: '{b}',
data: lineYDown,
},
];
const optionData: EChartsOption = {
tooltip: {
show: true, //是否显示提示框组件
trigger: 'axis',
//formatter:'{a0}:{c0}<br>{a1}:{c1}'
formatter: function (param: any) {
var tip = '';
if (param !== null && param.length > 0) {
tip += param[0].name + '<br />';
for (var i = 0; i < param.length; i++) {
tip +=
param[i].marker +
param[i].seriesName +
': ' +
param[i].value +
'<br />';
}
}
return tip;
},
},
legend: {
data: yAxisSeries.map((s: any) => s.name),
textStyle: {
fontSize: 12,
color: 'rgb(0,253,255,0.6)',
},
left: 'center', // 设置图例居中
},
grid: {
top: '14%',
left: '4%',
right: '4%',
bottom: '12%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: lineXTime,
axisLabel: {
formatter: function (params: any) {
return params.split(' ')[1];
},
fontSize: 14,
},
axisLine: {
lineStyle: {
color: 'rgb(0,253,255,0.6)',
},
},
},
yAxis: [
{
name: '(Mbps)',
nameTextStyle: {
fontSize: 12, // 设置文字距离x轴的距离
padding: [0, -10, 0, 0], // 设置名称在x轴方向上的偏移
},
type: 'value',
// splitNumber: 4,
min: 0,
//max: 300,
axisLabel: {
formatter: '{value}',
},
splitLine: {
lineStyle: {
color: 'rgb(23,255,243,0.3)',
},
},
axisLine: {
lineStyle: {
color: 'rgb(0,253,255,0.6)',
},
},
},
],
series: yAxisSeries,
};
fnDesign(upfFlow.value, optionData);
}
/**查询初始UPF数据 */
function fnGetInitData() {
// 查询5分钟前的
const nowDate = new Date().getTime();
listKPIData({
neType: 'UPF',
neId: '001',
startTime: nowDate - 5 * 60 * 1000,
endTime: nowDate,
interval: 5, // 5秒
sortField: 'timeGroup',
sortOrder: 'asc',
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
for (const item of res.data) {
upfFlowParse(item);
}
}
})
.finally(() => {
handleRanderChart();
});
}
watch(
() => upfFlowData.value,
v => {
if (upfFlowChart.value == null) return;
upfFlowChart.value.setOption({
xAxis: {
data: v.lineXTime,
},
series: [
{
data: v.lineYUp,
},
{
data: v.lineYDown,
},
],
});
},
{
deep: true,
}
);
onMounted(() => {
fnGetInitData();
// setInterval(() => {
// upfFlowData.value.lineXTime.push(parseDateToStr(new Date()));
// const upN3 = parseSizeFromKbs(+145452, 5);
// upfFlowData.value.lineYUp.push(upN3[0]);
// const downN6 = parseSizeFromKbs(+232343, 5);
// upfFlowData.value.lineYDown.push(downN6[0]);
// upfFlowChart.value.setOption({
// xAxis: {
// data: upfFlowData.value.lineXTime,
// },
// series: [
// {
// data: upfFlowData.value.lineYUp,
// },
// {
// data: upfFlowData.value.lineYDown,
// },
// ],
// });
// }, 5000);
});
</script>
<template>
<div ref="upfFlow" class="chart-container"></div>
</template>
<style lang="less" scoped>
.chart-container {
/* 设置图表容器大小和位置 */
width: 100%;
height: 100%;
}
</style>

View File

@@ -1,370 +0,0 @@
<script setup lang="ts">
import { parseDuration, parseDateToStr } from '@/utils/date-utils';
import { eventData, eventId } from '../../hooks/useUserActivity';
import useI18n from '@/hooks/useI18n';
import useDictStore from '@/store/modules/dict';
import { onMounted, reactive } from 'vue';
const { t } = useI18n();
const { getDict } = useDictStore();
/**字典数据 */
let dict: {
/**CDR SIP响应代码类别类型 */
cdrSipCode: DictType[];
/**CDR 呼叫类型 */
cdrCallType: DictType[];
/**UE 事件认证代码类型 */
ueAauthCode: DictType[];
/**UE 事件类型 */
ueEventType: DictType[];
/**UE 事件CM状态 */
ueEventCmState: DictType[];
} = reactive({
cdrSipCode: [],
cdrCallType: [],
ueAauthCode: [],
ueEventType: [],
ueEventCmState: [],
});
onMounted(() => {
// 初始字典数据
Promise.allSettled([
getDict('cdr_sip_code'),
getDict('cdr_call_type'),
getDict('ue_auth_code'),
getDict('ue_event_type'),
getDict('ue_event_cm_state'),
]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.cdrSipCode = resArr[0].value;
}
if (resArr[1].status === 'fulfilled') {
dict.cdrCallType = resArr[1].value;
}
if (resArr[2].status === 'fulfilled') {
dict.ueAauthCode = resArr[2].value;
}
if (resArr[3].status === 'fulfilled') {
dict.ueEventType = resArr[3].value;
}
if (resArr[4].status === 'fulfilled') {
dict.ueEventCmState = resArr[4].value;
}
});
});
</script>
<template>
<div class="activty">
<template v-for="item in eventData" :key="item.eId">
<!-- CDR事件IMS -->
<div
class="card-cdr"
:class="{ active: item.eId === eventId }"
v-if="item.eType === 'ims_cdr'"
>
<div class="card-cdr-item">
<div>
{{ t('views.dashboard.overview.userActivity.type') }}:&nbsp;
<span>
<DictTag
:options="dict.cdrCallType"
:value="item.data.callType"
/>
</span>
</div>
<div></div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:&nbsp;
<span :title="parseDateToStr(item.data.releaseTime * 1000)">
{{ parseDateToStr(item.data.releaseTime * 1000) }}
</span>
</div>
</div>
<div class="card-cdr-item">
<div>
{{ t('views.dashboard.overview.userActivity.caller') }}:
<span :title="item.data.callerParty">
{{ item.data.callerParty }}
</span>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.called') }}:
<span :title="item.data.calledParty">
{{ item.data.calledParty }}
</span>
</div>
<div v-if="item.data.callType !== 'sms'">
{{ t('views.dashboard.overview.userActivity.duration') }}:&nbsp;
<span>{{ parseDuration(item.data.callDuration) }}</span>
</div>
<div v-else></div>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span v-if="item.data.callType !== 'sms'">
<DictTag
:options="dict.cdrSipCode"
:value="item.data.cause"
value-default="0"
/>
</span>
<span v-else>
{{ t('views.dashboard.overview.userActivity.resultOK') }}
</span>
</div>
</div>
<!-- UE事件AMF -->
<div
class="card-ue"
:class="{ active: item.eId === eventId }"
v-if="item.eType === 'amf_ue'"
>
<div class="card-ue-item">
<div>
{{ t('views.dashboard.overview.userActivity.type') }}:&nbsp;
<span>
<DictTag :options="dict.ueEventType" :value="item.type" />
</span>
</div>
<div>
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<span
v-if="item.type === 'auth-result'"
:title="item.data.authTime"
>
{{ item.data.authTime }}
</span>
<span v-if="item.type === 'detach'" :title="item.data.detachTime">
{{ item.data.detachTime }}
</span>
<span v-if="item.type === 'cm-state'" :title="item.data.changeTime">
{{ item.data.changeTime }}
</span>
</div>
</div>
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
<div>
GNB ID: <span>{{ item.data.gNBID }}</span>
</div>
<div>
Cell ID: <span>{{ item.data.cellID }}</span>
</div>
<div>
TAC ID: <span>{{ item.data.tacID }}</span>
</div>
</div>
<div v-if="item.type === 'auth-result'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span>
<DictTag :options="dict.ueAauthCode" :value="item.data.authCode" />
</span>
</div>
<div v-if="item.type === 'detach'">
{{ t('views.dashboard.overview.userActivity.result') }}:
<span>{{ t('views.dashboard.overview.userActivity.resultOK') }}</span>
</div>
<div class="card-ue-w33" v-if="item.type === 'cm-state'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span>
<DictTag :options="dict.ueEventCmState" :value="item.data.status" />
</span>
</div>
</div>
<!-- UE事件MME -->
<div
class="card-ue"
:class="{ active: item.eId === eventId }"
v-if="item.eType === 'mme_ue'"
>
<div class="card-ue-item">
<div>
{{ t('views.dashboard.overview.userActivity.type') }}:&nbsp;
<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" />
</span>
</div>
<div>
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<span :title="item.data.timestamp">
{{ parseDateToStr(+item.data.timestamp * 1000) }}
</span>
</div>
</div>
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
<div>
ENB ID: <span>{{ item.data.eNBID }}</span>
</div>
<div>
Cell ID: <span>{{ item.data.cellID }}</span>
</div>
<div>
TAC ID: <span>{{ item.data.tacID }}</span>
</div>
</div>
<div v-if="item.type === 'auth-result'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span>
<DictTag :options="dict.ueAauthCode" :value="item.data.result" />
</span>
</div>
<div v-if="item.type === 'detach'">
{{ t('views.dashboard.overview.userActivity.result') }}:
<span>{{ t('views.dashboard.overview.userActivity.resultOK') }}</span>
</div>
<div class="card-ue-w33" v-if="item.type === 'cm-state'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span>
<DictTag :options="dict.ueEventCmState" :value="item.data.result" />
</span>
</div>
</div>
</template>
</div>
</template>
<style lang="less" scoped>
.activty {
overflow-x: hidden;
overflow-y: auto;
height: 94%;
color: #61a8ff;
font-size: 0.75rem;
& .card-ue {
border: 1px #61a8ff solid;
border-radius: 4px;
padding: 0.2rem 0.5rem;
margin-bottom: 0.3rem;
line-height: 1rem;
& span {
color: #68d8fe;
}
&-item {
display: flex;
flex-direction: row;
justify-content: flex-start;
& > div {
width: 50%;
white-space: nowrap;
text-align: start;
text-overflow: ellipsis;
overflow: hidden;
}
}
&-w33 {
display: flex;
flex-direction: row;
justify-content: flex-start;
& > div {
width: 33%;
}
}
}
& .card-cdr {
border: 1px #61a8ff solid;
border-radius: 4px;
padding: 0.2rem 0.5rem;
margin-bottom: 0.3rem;
line-height: 1rem;
& span {
color: #68d8fe;
}
&-item {
display: flex;
flex-direction: row;
justify-content: flex-start;
& > div {
flex: 1;
white-space: nowrap;
text-align: start;
text-overflow: ellipsis;
overflow: hidden;
}
}
}
& .active {
color: #faad14;
border: 1px #faad14 solid;
animation: backInRight 0.3s alternate;
& span {
color: #faad14;
}
}
/* 兼容当行显示字内容 */
@media (max-width: 1720px) {
& .card-cdr {
&-item {
display: block;
& > div {
width: 100%;
}
}
}
& .card-ue {
&-item {
display: block;
& > div {
width: 100%;
}
}
}
}
/* 修改滚动条的样式 */
&::-webkit-scrollbar {
width: 8px; /* 设置滚动条宽度 */
}
&::-webkit-scrollbar-track {
background-color: #101129; /* 设置滚动条轨道背景颜色 */
}
&::-webkit-scrollbar-thumb {
background-color: #28293f; /* 设置滚动条滑块颜色 */
}
&::-webkit-scrollbar-thumb:hover {
background-color: #68d8fe; /* 设置鼠标悬停时滚动条滑块颜色 */
}
@keyframes backInRight {
0% {
opacity: 0.7;
-webkit-transform: translateX(2000px) scale(0.7);
transform: translateX(2000px) scale(0.7);
}
80% {
opacity: 0.7;
-webkit-transform: translateX(0) scale(0.7);
transform: translateX(0) scale(0.7);
}
to {
opacity: 1;
-webkit-transform: scale(1);
transform: scale(1);
}
}
}
</style>

View File

@@ -1,277 +0,0 @@
.viewport {
/* 限定大小 */
min-width: 1024px;
max-width: 1920px;
min-height: 780px;
margin: 0 auto;
position: relative;
display: flex;
padding: 5rem 0.833rem 0;
line-height: 1.15;
background-color: #101129;
height: 100vh;
margin-bottom: -20px;
}
.column {
flex: 3;
position: relative;
}
/* 边框 */
.panel {
box-sizing: border-box;
border: 2px solid red;
border-image: url(../images/border.png) 51 38 21 132;
border-width: 2.125rem 1.583rem 0.875rem 5.5rem;
position: relative;
margin-bottom: 0.833rem;
}
.panel .inner {
/* 装内容 */
/* height: 60px; */
position: absolute;
top: -2.125rem;
right: -1.583rem;
bottom: -0.875rem;
left: -5.5rem;
padding: 1rem 1.5rem;
}
.panel h3 {
font-size: 0.833rem;
color: #fff;
}
/* 总览标题 */
.brand {
background-image: url(../images/brand.png);
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
position: absolute;
top: 0.833rem;
left: 0;
right: 0;
width: 100%;
height: 5rem;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
}
.brand .brand-title {
color: #ffffff;
font-size: 1.4rem;
font-weight: 600;
padding-top: 1rem;
padding-bottom: 0.5rem;
}
.brand .brand-desc {
color: #d9d9d9;
font-size: 0.9rem;
}
/* 实时流量 */
.upfFlow {
/* min-height: 16rem; */
height: 40%;
}
.upfFlow .inner .chart {
width: 100%;
height: 100%;
margin-top: 1rem;
}
/* 网络拓扑 */
.topology {
/* min-height: 27.8rem; */
height: 56.4%;
}
.topology .inner h3 {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: baseline;
}
.topology .inner h3 .normal {
color: #52c41a;
font-size: 1.1rem;
margin-right: 8px;
}
.topology .inner h3 .abnormal {
color: #f5222d;
font-size: 1.1rem;
}
.topology .inner .chart {
width: 100%;
height: 100%;
margin-top: 1rem;
}
/* 概览区域 */
.skim {
/* min-height: 7.78rem; */
height: 14.4%;
}
.skim .inner .data {
display: flex;
flex-direction: row;
align-items: center;
height: 90%;
}
.skim .inner .data .item {
display: flex;
flex-direction: column;
align-items: baseline;
width: 33%;
}
.skim .inner .data .item div {
font-size: 1.467rem;
color: #fff;
margin-bottom: 0;
display: flex;
align-items: baseline;
line-height: 2rem;
}
.skim .inner .data .item span {
color: #4c9bfd;
font-size: 0.833rem;
width: 100%;
position: relative;
line-height: 2rem;
white-space: nowrap;
text-align: start;
text-overflow: ellipsis;
overflow: hidden;
}
.skim .inner .data .item span::before {
content: ' ';
position: absolute;
top: 2px;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
background-image: linear-gradient(to right, #fff, #fff0);
height: 1px;
border-radius: 4px;
}
/* 概览区域 衍生基站信息 */
.skim.base {
height: 12%;
}
.skim.base .inner .data {
display: flex;
flex-direction: row;
align-items: center;
height: 75%;
}
.skim.base .inner .data .item {
display: flex;
flex-direction: column;
align-items: baseline;
width: 50%;
}
/* 用户行为 */
.userActivity {
/* min-height: 35.8rem; */
height: 54.6%;
}
.userActivity .inner .chart {
width: 100%;
height: 100%;
margin-top: 1rem;
}
/* 流量统计 */
.upfFlowTotal {
/* min-height: 7.5rem; */
height: 14.4%;
}
.upfFlowTotal .inner h3 {
display: flex;
justify-content: space-between;
}
.upfFlowTotal .inner h3 .filter {
display: flex;
}
.upfFlowTotal .inner h3 .filter span {
display: block;
height: 0.75rem;
line-height: 1;
padding: 0 0.75rem;
color: #1950c4;
font-size: 0.75rem;
border-right: 0.083rem solid #00f2f1;
cursor: pointer;
}
.upfFlowTotal .inner h3 .filter span:first-child {
padding-left: 0;
}
.upfFlowTotal .inner h3 .filter span:last-child {
border-right: none;
}
.upfFlowTotal .inner h3 .filter span.active {
color: #fff;
font-size: 0.833rem;
}
.upfFlowTotal .inner .chart {
width: 100%;
height: 100%;
margin-top: 0.1rem;
}
.upfFlowTotal .inner .chart .data {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 60%;
}
.upfFlowTotal .inner .chart .data .item {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.upfFlowTotal .inner .chart .data .item h4 {
font-size: 1.467rem;
color: #fff;
margin-bottom: 0;
}
.upfFlowTotal .inner .chart .data .item span {
color: #4c9bfd;
font-size: 0.867rem;
}
/* 资源情况 */
.resources {
/* min-height: 18rem; */
height: 34.4%;
}
.resources .inner .chart {
width: 100%;
height: 100%;
margin-top: 1rem;
}
/* 告警统计 */
.alarmType {
/* min-height: 25rem; */
height: 46%;
}
.alarmType .inner .chart {
width: 100%;
height: 100%;
margin-top: 1rem;
}
/* 跳转鼠标悬浮 */
.toRouter:hover {
cursor: pointer;
color: #fff !important;
}
.toRouter:hover > *,
.toRouter:hover > * > * {
color: #fff !important;
}

View File

@@ -1,172 +0,0 @@
import { parseDateToStr } from '@/utils/date-utils';
import { computed, reactive, ref } from 'vue';
/**非网元元素 */
export const notNeNodes = [
'5GC',
'DN',
'UE',
'Base',
'lan',
'lan1',
'lan2',
'lan3',
'lan4',
'lan5',
'lan6',
'lan7',
'LAN',
'NR',
];
/**图状态 */
export const graphState = reactive<Record<string, any>>({
/**当前图组名 */
group: '5GC System Architecture',
/**图数据 */
data: {
combos: [],
edges: [],
nodes: [],
},
});
/**图实例对象 */
export const graphG6 = ref<any>(null);
/**图点击选择 */
export const graphNodeClickID = ref<string>('UPF');
/**图节点网元信息状态 */
export const graphNodeState = computed(() =>
graphState.data.nodes.map((item: any) => ({
id: item.id,
label: item.label,
neInfo: item.neInfo,
neState: item.neState,
}))
);
/**图节点网元状态数量 */
export const graphNodeStateNum = computed(() => {
let normal = 0;
let abnormal = 0;
for (const item of graphState.data.nodes) {
const neId = item.neState.neId;
if (neId) {
if (item.neState.online) {
normal += 1;
} else {
abnormal += 1;
}
}
}
return [normal, abnormal];
});
/**网元状态请求标记 */
export const neStateRequestMap = ref<Map<string, boolean>>(new Map());
/**neStateParse 网元状态 数据解析 */
export function neStateParse(neType: string, data: Record<string, any>) {
const { combos, edges, nodes } = graphState.data;
const node = nodes.find((item: Record<string, any>) => item.id === neType);
// 更新网元状态
const newNeState = Object.assign(node.neState, data, {
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'),
online: !!data.cpu,
});
// 通过 ID 查询节点实例
const item = graphG6.value.findById(node.id);
if (item) {
const stateColor = newNeState.online ? '#52c41a' : '#f5222d'; // 状态颜色
// 图片类型不能填充
if (node.type.startsWith('image')) {
// 更新节点
if (node.label !== newNeState.neName) {
graphG6.value.updateItem(item, {
label: newNeState.neName,
});
}
// 设置状态
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
} else {
// 更新节点
graphG6.value.updateItem(item, {
label: newNeState.neName,
// neState: newNeState,
style: {
fill: stateColor, // 填充色
stroke: stateColor, // 填充色
},
// labelCfg: {
// style: {
// fill: '#ffffff', // 标签文本色
// },
// },
});
// 设置状态
graphG6.value.setItemState(item, 'stroke', newNeState.online);
}
}
// 设置边状态
for (const edge of edges) {
const edgeSource: string = edge.source;
const edgeTarget: string = edge.target;
const neS = nodes.find((n: any) => n.id === edgeSource);
const neT = nodes.find((n: any) => n.id === edgeTarget);
// console.log(neS, edgeSource, neT, edgeTarget);
if (neS && neT) {
// 通过 ID 查询节点实例
// const item = graphG6.value.findById(edge.id);
// console.log(
// `${edgeSource} - ${edgeTarget}`,
// neS.neState.online && neT.neState.online
// );
// const stateColor = neS.neState.online && neT.neState.online ? '#000000' : '#ff4d4f'; // 状态颜色
// 更新边
// graphG6.value.updateItem(item, {
// label: `${edgeSource} - ${edgeTarget}`,
// style: {
// stroke: stateColor, // 填充色
// },
// labelCfg: {
// style: {
// fill: '#ffffff', // 标签文本色
// },
// },
// });
// 设置状态
graphG6.value.setItemState(
edge.id,
'circle-move',
neS.neState.online && neT.neState.online
);
}
if (neS && notNeNodes.includes(edgeTarget)) {
graphG6.value.setItemState(edge.id, 'line-dash', neS.neState.online);
}
if (neT && notNeNodes.includes(edgeSource)) {
graphG6.value.setItemState(edge.id, 'line-dash', neT.neState.online);
}
}
// 请求标记复位
neStateRequestMap.value.set(neType, false);
}
/**属性复位 */
export function topologyReset() {
graphState.data = {
combos: [],
edges: [],
nodes: [],
};
graphG6.value = null;
graphNodeClickID.value = 'UPF';
neStateRequestMap.value = new Map();
}

View File

@@ -1,115 +0,0 @@
import { parseDateToStr } from '@/utils/date-utils';
import { parseSizeFromBits, parseSizeFromKbs } from '@/utils/parse-utils';
import { ref } from 'vue';
type FDType = {
/**时间 */
lineXTime: string[];
/**上行 N3 */
lineYUp: number[];
/**下行 N6 */
lineYDown: number[];
/**容量 */
cap: number;
};
/**UPF-流量数据 */
export const upfFlowData = ref<FDType>({
lineXTime: [],
lineYUp: [],
lineYDown: [],
cap: 0,
});
/**UPF-流量数据 数据解析 */
export function upfFlowParse(data: Record<string, string>) {
upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup']));
const upN3 = parseSizeFromKbs(+data['UPF.03'], 5);
upfFlowData.value.lineYUp.push(upN3[0]);
const downN6 = parseSizeFromKbs(+data['UPF.06'], 5);
upfFlowData.value.lineYDown.push(downN6[0]);
upfFlowData.value.cap += 1;
// 超过 25 弹出
if (upfFlowData.value.cap > 25) {
upfFlowData.value.lineXTime.shift();
upfFlowData.value.lineYUp.shift();
upfFlowData.value.lineYDown.shift();
upfFlowData.value.cap -= 1;
}
// UPF-总流量数0天 当天24小时
upfTFParse('0', {
up: upfTotalFlow.value['0'].up + +data['UPF.03'],
down: upfTotalFlow.value['0'].down + +data['UPF.06'],
});
}
type TFType = {
/**上行 N3 */
up: number;
upFrom: string;
/**下行 N6 */
down: number;
downFrom: string;
/**请求标记 */
requestFlag: boolean;
};
/**UPF-总流量数 */
export const upfTotalFlow = ref<Record<string, TFType>>({
'0': {
up: 0,
upFrom: '0 B',
down: 0,
downFrom: '0 B',
requestFlag: false,
},
'7': {
up: 0,
upFrom: '0 B',
down: 0,
downFrom: '0 B',
requestFlag: false,
},
'30': {
up: 0,
upFrom: '0 B',
down: 0,
downFrom: '0 B',
requestFlag: false,
},
});
/**UPF-总流量数 数据解析 */
export function upfTFParse(day: string, data: Record<string, number>) {
let { up, down } = data;
upfTotalFlow.value[day] = {
up: up,
upFrom: parseSizeFromBits(up),
down: down,
downFrom: parseSizeFromBits(down),
requestFlag: false,
};
}
/**UPF-总流量数 选中 */
export const upfTFActive = ref<string>('0');
/**属性复位 */
export function upfTotalFlowReset() {
upfFlowData.value = {
lineXTime: [],
lineYUp: [],
lineYDown: [],
cap: 0,
};
for (const key of Object.keys(upfTotalFlow.value)) {
upfTotalFlow.value[key] = {
up: 0,
upFrom: '0 B',
down: 0,
downFrom: '0 B',
requestFlag: false,
};
}
upfTFActive.value = '0';
}

View File

@@ -1,148 +0,0 @@
import { ref } from 'vue';
/**ueEventAMFParse UE会话事件AMF 数据解析 */
function ueEventAMFParse(
item: Record<string, any>
): false | Record<string, any> {
let evData: Record<string, any> = item.eventJSON;
if (typeof evData === 'string') {
try {
evData = JSON.parse(evData);
} catch (error) {
console.error(error);
}
}
return {
eType: 'amf_ue',
eId: `amf_ue_${item.id}_${Date.now()}`,
eTime: +item.timestamp,
id: item.id,
type: item.eventType,
data: evData,
};
}
/**ueEventMMEParse UE会话事件MME 数据解析 */
function ueEventMMEParse(
item: Record<string, any>
): false | Record<string, any> {
let evData: Record<string, any> = item.eventJSON;
if (typeof evData === 'string') {
try {
evData = JSON.parse(evData);
} catch (error) {
console.error(error);
}
}
return {
eType: 'mme_ue',
eId: `mme_ue_${item.id}_${Date.now()}`,
eTime: +item.timestamp,
id: item.id,
type: item.eventType,
data: evData,
};
}
/**cdrEventIMSParse CDR会话事件IMS 数据解析 */
function cdrEventIMSParse(
item: Record<string, any>
): false | Record<string, any> {
let evData: Record<string, any> = item.cdrJSON || item.CDR;
if (typeof evData === 'string') {
try {
evData = JSON.parse(evData);
} catch (error) {
console.error(error);
return false;
}
}
// 指定显示CDR类型MOC/MTSM
if (!['MOC', 'MTSM'].includes(evData.recordType)) {
return false;
}
return {
eType: 'ims_cdr',
eId: `ims_cdr_${item.id}_${Date.now()}`,
eTime: +item.timestamp,
id: item.id,
data: evData,
};
}
/**eventListParse 事件列表解析 */
export function eventListParse(
type: 'ims_cdr' | 'amf_ue' | 'mme_ue',
data: any
) {
eventTotal.value += data.total;
for (const item of data.rows) {
let v: false | Record<string, any> = false;
if (type === 'ims_cdr') {
v = cdrEventIMSParse(item);
}
if (type === 'amf_ue') {
v = ueEventAMFParse(item);
}
if (type === 'mme_ue') {
v = ueEventMMEParse(item);
}
if (v) {
eventData.value.push(v);
}
}
// 有数据进行排序
if (eventData.value.length > 5) {
eventData.value.sort((a, b) => b.eTime - a.eTime);
}
if (eventData.value.length > 0) {
eventId.value = eventData.value[0].eId;
}
}
/**eventItemParseAndPush 事件项解析并添加 */
export async function eventItemParseAndPush(
type: 'ims_cdr' | 'amf_ue' | 'mme_ue',
item: any
) {
let v: false | Record<string, any> = false;
if (type === 'ims_cdr') {
v = cdrEventIMSParse(item);
}
if (type === 'amf_ue') {
v = ueEventAMFParse(item);
}
if (type === 'mme_ue') {
v = ueEventMMEParse(item);
}
if (v) {
eventData.value.unshift(v);
eventTotal.value += 1;
eventId.value = v.eId;
await new Promise(resolve => setTimeout(resolve, 800));
if (eventData.value.length > 20) {
eventData.value.pop();
}
}
}
/**CDR+UE事件数据 */
export const eventData = ref<Record<string, any>[]>([]);
/**CDR+UE事件总量 */
export const eventTotal = ref<number>(0);
/**CDR/UE事件推送id */
export const eventId = ref<string>('');
/**属性复位 */
export function userActivityReset() {
eventData.value = [];
eventTotal.value = 0;
eventId.value = '';
}

View File

@@ -1,204 +0,0 @@
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import { onBeforeUnmount, onMounted } from 'vue';
import {
eventListParse,
eventItemParseAndPush,
userActivityReset,
} from './useUserActivity';
import {
upfTotalFlow,
upfTFParse,
upfFlowParse,
upfTotalFlowReset,
} from './useUPFTotalFlow';
import { topologyReset, neStateParse } from './useTopology';
import PQueue from 'p-queue';
/**websocket连接 */
export default function useWS() {
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
/**发消息 */
function wsSend(data: Record<string, any>) {
ws.send(data);
}
/**接收数据后回调 */
function wsMessage(res: Record<string, any>) {
// console.log(res);
const { code, requestId, data } = res;
if (code === RESULT_CODE_ERROR) {
console.warn(res.msg);
return;
}
// 网元状态
if (requestId && requestId.startsWith('neState')) {
const neType = requestId.split('_')[1];
neStateParse(neType, data);
return;
}
// 普通信息
switch (requestId) {
// AMF_UE会话事件
case 'amf_1010':
if (Array.isArray(data.rows)) {
eventListParse('amf_ue', data);
}
break;
// MME_UE会话事件
case 'mme_1011_001':
if (Array.isArray(data.rows)) {
eventListParse('mme_ue', data);
}
break;
// IMS_CDR会话事件
case 'ims_1005_001':
if (Array.isArray(data.rows)) {
eventListParse('ims_cdr', data);
}
break;
//UPF-总流量数
case 'upf_001_0':
upfTFParse('0', data);
break;
case 'upf_001_7':
upfTFParse('7', data);
break;
case 'upf_001_30':
upfTFParse('30', data);
break;
}
// 订阅组信息
if (!data?.groupId) {
return;
}
switch (data.groupId) {
// kpiEvent 指标UPF
case '12_001':
if (data.data) {
upfFlowParse(data.data);
}
break;
// AMF_UE会话事件
case '1010':
if (data.data) {
queue.add(() => eventItemParseAndPush('amf_ue', data.data));
}
break;
// MME_UE会话事件
case '1011_001':
if (data.data) {
queue.add(() => eventItemParseAndPush('mme_ue', data.data));
}
break;
// IMS_CDR会话事件
case '1005_001':
if (data.data) {
queue.add(() => eventItemParseAndPush('ims_cdr', data.data));
}
break;
}
}
/**UPF-总流量数 发消息*/
function upfTFSend(day: '0' | '7' | '30') {
// 请求标记检查避免重复发送
if (upfTotalFlow.value[day].requestFlag) {
return;
}
upfTotalFlow.value[day].requestFlag = true;
ws.send({
requestId: `upf_001_${day}`,
type: 'upf_tf',
data: {
neType: 'UPF',
neId: '001',
day: Number(day),
},
});
}
/**userActivitySend 用户行为事件基础列表数据 发消息*/
function userActivitySend() {
// AMF_UE会话事件
ws.send({
requestId: 'amf_1010',
type: 'amf_ue',
data: {
neType: 'AMF',
neId: '001',
sortField: 'timestamp',
sortOrder: 'desc',
pageNum: 1,
pageSize: 20,
},
});
// MME_UE会话事件
ws.send({
requestId: 'mme_1011_001',
type: 'mme_ue',
data: {
neType: 'MME',
neId: '001',
sortField: 'timestamp',
sortOrder: 'desc',
pageNum: 1,
pageSize: 20,
},
});
// IMS_CDR会话事件
ws.send({
requestId: 'ims_1005_001',
type: 'ims_cdr',
data: {
neType: 'IMS',
neId: '001',
recordType: 'MOC',
sortField: 'timestamp',
sortOrder: 'desc',
pageNum: 1,
pageSize: 20,
},
});
}
onMounted(() => {
const options: OptionsType = {
url: '/ws',
params: {
/**订阅通道组
*
* 指标UPF (GroupID:12_neId)
* AMF_UE会话事件(GroupID:1010)
* MME_UE会话事件(GroupID:1011_neId)
* IMS_CDR会话事件(GroupID:1005_neId)
*/
subGroupID: '12_001,1010,1011_001,1005_001',
},
onmessage: wsMessage,
onerror: (ev: any) => {
console.error(ev);
},
};
ws.connect(options);
});
onBeforeUnmount(() => {
ws.close();
userActivityReset();
upfTotalFlowReset();
topologyReset();
});
return {
wsSend,
userActivitySend,
upfTFSend,
};
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,493 +0,0 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import svgBase from '@/assets/svg/base.svg';
import svgUserIMS from '@/assets/svg/userIMS.svg';
import svgUserSMF from '@/assets/svg/userSMF.svg';
import useI18n from '@/hooks/useI18n';
import Topology from './components/Topology/index.vue';
import NeResources from './components/NeResources/index.vue';
import UserActivity from './components/UserActivity/index.vue';
import AlarnTypeBar from './components/AlarnTypeBar/index.vue';
import UPFFlow from './components/UPFFlow/index.vue';
import { listUDMSub } from '@/api/neData/udm_sub';
import { listUENumBySMF } from '@/api/neUser/smf';
import { listUENumByIMS } from '@/api/neUser/ims';
import { listBase5G } from '@/api/neUser/base5G';
import {
graphNodeClickID,
graphState,
notNeNodes,
graphNodeStateNum,
neStateRequestMap,
} from './hooks/useTopology';
import { upfTotalFlow, upfTFActive } from './hooks/useUPFTotalFlow';
import { useFullscreen } from '@vueuse/core';
import useWS from './hooks/useWS';
import useAppStore from '@/store/modules/app';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { useRouter } from 'vue-router';
import { hasRoles } from '@/plugins/auth-user';
const router = useRouter();
const appStore = useAppStore();
const { t } = useI18n();
const { wsSend, userActivitySend, upfTFSend } = useWS();
/**概览状态类型 */
type SkimStateType = {
/**UDM签约用户数量 */
udmSubNum: number;
/**SMF在线用户数 */
smfUeNum: number;
/**IMS在线用户数 */
imsUeNum: number;
/**5G基站数量 */
gnbNum: number;
/**5G在线用户数量 */
gnbUeNum: number;
/**4G基站数量 */
enbNum: number;
/**4G在线用户数量 */
enbUeNum: number;
};
/**概览状态信息 */
let skimState: SkimStateType = reactive({
udmSubNum: 0,
smfUeNum: 0,
imsUeNum: 0,
gnbNum: 0,
gnbUeNum: 0,
enbNum: 0,
enbUeNum: 0,
});
/**总览节点 */
const viewportDom = ref<HTMLElement | null>(null);
const { isFullscreen, toggle } = useFullscreen(viewportDom);
/**10s调度器 */
const interval10s = ref<any>(null);
/**5s调度器 */
const interval5s = ref<any>(null);
/**查询网元状态 */
function fnGetNeState() {
// 获取节点状态
for (const node of graphState.data.nodes) {
if (notNeNodes.includes(node.id)) continue;
const { neType, neId } = node.neInfo;
if (!neType || !neId) continue;
// 请求标记检查避免重复发送
if (neStateRequestMap.value.get(neType)) continue;
neStateRequestMap.value.set(neType, true);
wsSend({
requestId: `neState_${neType}_${neId}`,
type: 'ne_state',
data: {
neType: neType,
neId: neId,
},
});
}
}
/**获取概览信息 */
async function fnGetSkim() {
const resArr = await Promise.allSettled([
listUDMSub({
neid: '001',
pageNum: 1,
pageSize: 1,
}),
listUENumBySMF('001'),
listUENumByIMS('001'),
listBase5G({
neType: 'AMF',
neId: '001',
}),
listBase5G({
neType: 'MME',
neId: '001',
}),
]);
if (resArr[0].status === 'fulfilled') {
const res0 = resArr[0].value;
if (res0.code === RESULT_CODE_SUCCESS) {
skimState.udmSubNum = res0.total;
}
}
if (resArr[1].status === 'fulfilled') {
const res1 = resArr[1].value;
if (res1.code === RESULT_CODE_SUCCESS) {
skimState.smfUeNum = res1.data;
}
}
if (resArr[2].status === 'fulfilled') {
const res2 = resArr[2].value;
if (res2.code === RESULT_CODE_SUCCESS) {
skimState.imsUeNum = res2.data;
}
}
if (resArr[3].status === 'fulfilled') {
const res3 = resArr[3].value;
if (res3.code === RESULT_CODE_SUCCESS) {
skimState.gnbNum = res3.total;
skimState.gnbUeNum = 0;
res3.rows.map((item: any) => {
skimState.gnbUeNum += item.ueNum;
});
}
}
if (resArr[4].status === 'fulfilled') {
const res4 = resArr[4].value;
if (res4.code === RESULT_CODE_SUCCESS) {
skimState.enbNum = res4.total;
skimState.enbUeNum = 0;
res4.rows.map((item: any) => {
skimState.enbUeNum += item.ueNum;
});
}
}
}
/**初始数据函数 */
function loadData() {
fnGetNeState(); // 获取网元状态
userActivitySend();
upfTFSend('0');
upfTFSend('7');
upfTFSend('30');
clearInterval(interval10s.value);
interval10s.value = setInterval(() => {
if (!interval10s.value) return
if (upfTFActive.value === '0') {
upfTFSend('7');
upfTFActive.value = '7';
} else if (upfTFActive.value === '7') {
upfTFSend('30');
upfTFActive.value = '30';
} else if (upfTFActive.value === '30') {
upfTFSend('0');
upfTFActive.value = '0';
}
}, 10_000);
clearInterval(interval5s.value);
interval5s.value = setInterval(() => {
if (!interval5s.value) return
fnGetSkim(); // 获取概览信息
fnGetNeState(); // 获取网元状态
}, 5_000);
}
/**栏目信息跳转 */
function fnToRouter(name: string, query?: any) {
if (hasRoles(['student'])) return;
router.push({ name, query });
}
onMounted(() => {
fnGetSkim().then(() => {
loadData();
});
});
onBeforeUnmount(() => {
clearInterval(interval10s.value);
interval10s.value = null;
clearInterval(interval5s.value);
interval5s.value = null;
});
</script>
<template>
<div class="viewport" ref="viewportDom">
<div class="brand">
<div
class="brand-title"
@click="toggle"
:title="t('views.dashboard.overview.fullscreen')"
>
{{ t('views.dashboard.overview.title') }}
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</div>
<div class="brand-desc">{{ appStore.appName }}</div>
</div>
<div class="column">
<!--概览-->
<div class="skim panel">
<div class="inner">
<h3>
<IdcardOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.skim.userTitle') }}
</h3>
<div class="data">
<div
class="item toRouter"
@click="fnToRouter('Sub_2010')"
:title="t('views.dashboard.overview.toRouter')"
>
<div>
<UserOutlined
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
/>
{{ skimState.udmSubNum }}
</div>
<span>
{{ t('views.dashboard.overview.skim.users') }}
</span>
</div>
<div
class="item toRouter"
@click="fnToRouter('Ims_2080')"
:title="t('views.dashboard.overview.toRouter')"
style="margin: 0 12px"
>
<div>
<img :src="svgUserIMS" style="width: 18px; margin-right: 8px" />
{{ skimState.imsUeNum }}
</div>
<span>
{{ t('views.dashboard.overview.skim.imsUeNum') }}
</span>
</div>
<div
class="item toRouter"
@click="fnToRouter('Ue_2081')"
:title="t('views.dashboard.overview.toRouter')"
>
<div>
<img :src="svgUserSMF" style="width: 18px; margin-right: 8px" />
{{ skimState.smfUeNum }}
</div>
<span>
{{ t('views.dashboard.overview.skim.smfUeNum') }}
</span>
</div>
</div>
</div>
</div>
<div class="skim panel base">
<div class="inner">
<h3>
<GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp; 5G
{{ t('views.dashboard.overview.skim.baseTitle') }}
</h3>
<div class="data">
<div
class="item toRouter"
@click="fnToRouter('Base5G_2082', { neType: 'AMF' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start">
<img
:src="svgBase"
style="width: 18px; margin-right: 8px; height: 2rem"
/>
{{ skimState.gnbNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.gnbBase') }}</span>
</div>
<div
class="item toRouter"
@click="fnToRouter('Base5G_2082', { neType: 'AMF' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start">
<UserOutlined
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
/>
{{ skimState.gnbUeNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.gnbUeNum') }}</span>
</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="item toRouter"
@click="fnToRouter('Base5G_2082', { neType: 'MME' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start">
<img
:src="svgBase"
style="width: 18px; margin-right: 8px; height: 2rem"
/>
{{ skimState.enbNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.enbBase') }}</span>
</div>
<div
class="item toRouter"
@click="fnToRouter('Base5G_2082', { neType: 'MME' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start">
<UserOutlined
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
/>
{{ skimState.enbUeNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.enbUeNum') }}</span>
</div>
</div>
</div>
</div>
<!-- 用户行为 -->
<div class="userActivity panel">
<div class="inner">
<h3>
<WhatsAppOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.userActivity.title') }}
</h3>
<div class="chart">
<UserActivity />
</div>
</div>
</div>
</div>
<div class="column" style="flex: 4; margin: 1.333rem 0.833rem 0">
<!-- 实时流量 -->
<div class="upfFlow panel">
<div class="inner">
<h3
class="toRouter"
@click="fnToRouter('GoldTarget_2104')"
:title="t('views.dashboard.overview.toRouter')"
>
<AreaChartOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.upfFlow.title') }}
</h3>
<div class="chart">
<UPFFlow />
</div>
</div>
</div>
<!-- 网络拓扑 -->
<div class="topology panel">
<div class="inner">
<h3
class="toRouter"
@click="fnToRouter('TopologyArchitecture_2128')"
:title="t('views.dashboard.overview.toRouter')"
>
<span>
<ApartmentOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.topology.title') }}
</span>
<span>
{{ t('views.dashboard.overview.topology.normal') }}:
<span class="normal"> {{ graphNodeStateNum[0] }} </span>
{{ t('views.dashboard.overview.topology.abnormal') }}:
<span class="abnormal"> {{ graphNodeStateNum[1] }} </span>
</span>
</h3>
<div class="chart">
<Topology />
</div>
</div>
</div>
</div>
<div class="column">
<!-- 流量统计 -->
<div class="upfFlowTotal panel">
<div class="inner">
<h3>
<span>
<SwapOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.upfFlowTotal.title') }}
</span>
<!-- 筛选 -->
<div class="filter">
<span
:data-key="v"
:class="{ active: upfTFActive === v }"
v-for="v in ['0', '7', '30']"
:key="v"
@click="
() => {
upfTFActive = v;
}
"
>
{{
v === '0'
? '24' + t('common.units.hour')
: v + t('common.units.day')
}}
</span>
</div>
</h3>
<div class="chart">
<!-- 数据 -->
<div class="data">
<div class="item">
<span>
<ArrowUpOutlined style="color: #597ef7" />
{{ t('views.dashboard.overview.upfFlowTotal.up') }}
</span>
<h4>{{ upfTotalFlow[upfTFActive].up }}</h4>
</div>
<div class="item">
<span>
<ArrowDownOutlined style="color: #52c41a" />
{{ t('views.dashboard.overview.upfFlowTotal.down') }}
</span>
<h4>{{ upfTotalFlow[upfTFActive].down }}</h4>
</div>
</div>
</div>
</div>
</div>
<!-- 告警统计 -->
<div class="alarmType panel">
<div class="inner">
<h3
class="toRouter"
@click="fnToRouter('HistoryAlarm_2097')"
:title="t('views.dashboard.overview.toRouter')"
>
<PieChartOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.alarmTypeBar.alarmSum') }}
</h3>
<div class="chart">
<AlarnTypeBar />
</div>
</div>
</div>
<!-- 资源情况 -->
<div class="resources panel">
<div class="inner">
<h3>
<DashboardOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.resources.title') }}
{{ graphNodeClickID }}
</h3>
<div class="chart">
<NeResources />
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
@import url('./css/index.css');
</style>

View File

@@ -1,770 +0,0 @@
<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/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/es/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';
import { hasRoles } from '@/plugins/auth-user';
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;
if (typeof cdrJSON.updateTime === 'number') {
return parseDateToStr(+cdrJSON.updateTime * 1000);
}
return cdrJSON.updateTime;
},
},
{
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="hasRoles(['student']) ? tableColumns.filter((s:any)=>s.key !== 'id'): 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>
{{
typeof record.cdrJSON.updateTime === 'number'
? parseDateToStr(+record.cdrJSON.updateTime * 1000)
: record.cdrJSON.updateTime
}}
</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

@@ -1,554 +0,0 @@
<script setup lang="ts">
import { reactive, onMounted, ref, onBeforeUnmount } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import {
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import { listAllNeInfo } from '@/api/ne/neInfo';
import { message } from 'ant-design-vue/es';
import { getGraphData } from '@/api/monitor/topology';
import { parseDateToStr } from '@/utils/date-utils';
import { Graph, GraphData, Menu, Tooltip } from '@antv/g6';
import {
edgeCubicAnimateCircleMove,
edgeCubicAnimateLineDash,
edgeLineAnimateState,
} from '../topologyBuild/hooks/registerEdge';
import {
nodeCircleAnimateShapeR,
nodeCircleAnimateShapeStroke,
nodeImageAnimateState,
nodeRectAnimateState,
} from '../topologyBuild/hooks/registerNode';
import useNeOptions from '@/views/ne/neInfo/hooks/useNeOptions';
import useI18n from '@/hooks/useI18n';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import { hasRoles } from '@/plugins/auth-user';
import { parseBasePath } from '@/plugins/file-static-url';
const { t } = useI18n();
const { fnNeRestart, fnNeStop, fnNeLogFile } = useNeOptions();
const ws = new WS();
/**图DOM节点实例对象 */
const graphG6Dom = ref<HTMLElement | undefined>(undefined);
/**图状态 */
const graphState = reactive<Record<string, any>>({
/**当前图组名 */
group: '5GC System Architecture',
/**图数据 */
data: {
combos: [],
edges: [],
nodes: [],
},
});
/**非网元元素 */
const notNeNodes = [
'5GC',
'DN',
'UE',
'Base',
'lan',
'lan1',
'lan2',
'lan3',
'lan4',
'lan5',
'lan6',
'lan7',
'LAN',
'NR',
];
/**图实例对象 */
const graphG6 = ref<any>(null);
/**图节点右击菜单 */
const graphNodeMenu = new Menu({
offsetX: 6,
offseY: 10,
itemTypes: ['node'],
getContent(evt) {
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
const { id, label, neState }: any = evt.item?.getModel();
if (notNeNodes.includes(id)) {
return `<div><span>${label || id}</span></div>`;
}
if (!neState) {
return `<div><span>${label || id}</span></div>`;
}
if (hasRoles(['student'])) {
return 'Student';
}
return `
<div
style="
display: flex;
flex-direction: column;
width: 140px;
"
>
<h3 style="margin-bottom: 8px">
${t('views.monitor.topology.name')}:
${neState.neName ?? '--'}
</h3>
<div id="restart" style="cursor: pointer; margin-bottom: 4px">
> ${t('views.ne.common.restart')}
</div>
<div id="stop" style="cursor: pointer; margin-bottom: 4px;">
> ${t('views.ne.common.stop')}
</div>
<div id="log" style="cursor: pointer; margin-bottom: 4px;">
> ${t('views.ne.common.log')}
</div>
</div>
`;
},
handleMenuClick(target, item) {
const { neInfo }: any = item?.getModel();
const { neName, neType, neId } = neInfo;
const targetId = target.id;
switch (targetId) {
case 'restart':
fnNeRestart({ neName, neType, neId });
break;
case 'stop':
fnNeStop({ neName, neType, neId });
break;
case 'log':
fnNeLogFile({ neType, neId });
break;
}
},
});
/**图节点展示 */
const graphNodeTooltip = new Tooltip({
offsetX: 10,
offsetY: 20,
getContent(evt) {
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
const { id, label, neState }: any = evt.item?.getModel();
if (notNeNodes.includes(id)) {
return `<div><span>${label || id}</span></div>`;
}
if (!neState) {
return `<div><span>${label || id}</span></div>`;
}
let notStudentInfo = '';
if (hasRoles(['teacher', 'admin'])) {
notStudentInfo = `
<div><strong>${t('views.monitor.topology.serialNum')}</strong><span>
${neState.sn ?? '--'}
</span></div>
<div><strong>${t('views.monitor.topology.expiryDate')}</strong><span>
${neState.expire ?? '--'}
</span></div> `;
}
return `
<div
style="
display: flex;
flex-direction: column;
width: 200px;
"
>
<div><strong>${t('views.monitor.topology.state')}</strong><span>
${
neState.online
? t('views.monitor.topology.normalcy')
: t('views.monitor.topology.exceptions')
}
</span></div>
<div><strong>${t('views.monitor.topology.refreshTime')}</strong><span>
${neState.refreshTime ?? '--'}
</span></div>
<div>========================</div>
<div><strong>ID</strong><span>${neState.neId}</span></div>
<div><strong>${t('views.monitor.topology.name')}</strong><span>
${neState.neName ?? '--'}
</span></div>
<div><strong>IP</strong><span>${neState.neIP}</span></div>
<div><strong>${t('views.monitor.topology.version')}</strong><span>
${neState.version ?? '--'}
</span></div>
${notStudentInfo}
</div>
`;
},
itemTypes: ['node'],
});
/**注册自定义边或节点 */
function registerEdgeNode() {
// 边
edgeCubicAnimateLineDash();
edgeCubicAnimateCircleMove();
edgeLineAnimateState();
// 节点
nodeCircleAnimateShapeR();
nodeCircleAnimateShapeStroke();
nodeRectAnimateState();
nodeImageAnimateState();
}
/**图数据渲染 */
function handleRanderGraph(
container: HTMLElement | undefined,
data: GraphData
) {
if (!container) return;
const { clientHeight, clientWidth } = container;
// 注册自定义边或节点
registerEdgeNode();
const graph = new Graph({
container: container,
width: clientWidth,
height: clientHeight,
fitCenter: true,
fitView: true,
fitViewPadding: [40],
autoPaint: true,
modes: {
default: [
'drag-combo',
'drag-canvas',
'zoom-canvas',
'collapse-expand-combo',
],
},
groupByTypes: false,
nodeStateStyles: {
selected: {
fill: 'transparent',
},
},
plugins: [graphNodeMenu, graphNodeTooltip],
animate: true, // 是否使用动画过度,默认为 false
animateCfg: {
duration: 500, // Number一次动画的时长
easing: 'linearEasing', // String动画函数
},
});
graph.data(data);
graph.render();
graphG6.value = graph;
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(function (entries) {
// 当元素大小发生变化时触发回调函数
entries.forEach(function (entry) {
if (!graphG6.value) {
return;
}
graphG6.value.changeSize(
entry.contentRect.width,
entry.contentRect.height - 30
);
graphG6.value.fitCenter();
});
});
// 监听元素大小变化
observer.observe(container);
return graph;
}
/**
* 获取图组数据渲染到画布
* @param reload 是否重载数据
*/
function fnGraphDataLoad(reload: boolean = false) {
Promise.all([
getGraphData(graphState.group),
listAllNeInfo({
bandStatus: false,
}),
])
.then(resArr => {
const graphRes = resArr[0];
const neRes = resArr[1];
if (
graphRes.code === RESULT_CODE_SUCCESS &&
Array.isArray(graphRes.data.nodes) &&
graphRes.data.nodes.length > 0 &&
neRes.code === RESULT_CODE_SUCCESS &&
Array.isArray(neRes.data) &&
neRes.data.length > 0
) {
return {
graphData: graphRes.data,
neList: neRes.data,
};
} else {
message.warning({
content: t('views.monitor.topology.noData'),
duration: 5,
});
}
})
.then(res => {
if (!res) return;
const { combos, edges, nodes } = res.graphData;
// 节点过滤
const nf: Record<string, any>[] = nodes.filter(
(node: Record<string, any>) => {
Reflect.set(node, 'neState', { online: false });
// 图片路径处理
if (node.img) node.img = parseBasePath(node.img);
if (node.icon.show && node.icon?.img)
node.icon.img = parseBasePath(node.icon.img);
// 遍历是否有网元数据
const nodeID: string = node.id;
const hasNe = res.neList.some(ne => {
Reflect.set(node, 'neInfo', ne.neType === nodeID ? ne : {});
return ne.neType === nodeID;
});
if (hasNe) {
return true;
}
if (notNeNodes.includes(nodeID)) {
return true;
}
return false;
}
);
// 边过滤
const ef: Record<string, any>[] = edges.filter(
(edge: Record<string, any>) => {
const edgeSource: string = edge.source;
const edgeTarget: string = edge.target;
const hasNeS = nf.some(n => n.id === edgeSource);
const hasNeT = nf.some(n => n.id === edgeTarget);
// console.log(hasNeS, edgeSource, hasNeT, edgeTarget);
if (hasNeS && hasNeT) {
return true;
}
if (hasNeS && notNeNodes.includes(edgeTarget)) {
return true;
}
if (hasNeT && notNeNodes.includes(edgeSource)) {
return true;
}
return false;
}
);
// 分组过滤
combos.forEach((combo: Record<string, any>) => {
const comboChildren: Record<string, any>[] = combo.children;
combo.children = comboChildren.filter(c => nf.some(n => n.id === c.id));
return combo;
});
// 图数据
graphState.data = { combos, edges: ef, nodes: nf };
})
.finally(() => {
if (graphState.data.length < 0) return;
// 重载数据
if (reload) {
graphG6.value.read(graphState.data);
} else {
handleRanderGraph(graphG6Dom.value, graphState.data);
}
clearInterval(interval10s.value);
interval10s.value = null;
fnGetState();
interval10s.value = setInterval(async () => {
if (!interval10s.value) return;
fnGetState(); // 获取网元状态
}, 20_000);
});
}
/**网元状态调度器 */
const interval10s = ref<any>(null);
/**查询网元状态 */
function fnGetState() {
// 获取节点状态
for (const node of graphState.data.nodes) {
if (notNeNodes.includes(node.id)) continue;
const { neType, neId } = node.neInfo;
if (!neType || !neId) continue;
ws.send({
requestId: `${neType}_${neId}`,
type: 'ne_state',
data: {
neType: neType,
neId: neId,
},
});
}
}
/**接收数据后回调 */
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 (!requestId) return;
const [neType, neId] = requestId.split('_');
const { combos, edges, nodes } = graphState.data;
const node = nodes.find((item: Record<string, any>) => item.id === neType);
// 更新网元状态
const newNeState = Object.assign(node.neState, data, {
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'),
online: !!data.cpu,
});
// 通过 ID 查询节点实例
const item = graphG6.value.findById(node.id);
if (item) {
const stateColor = newNeState.online ? '#52c41a' : '#f5222d'; // 状态颜色
// 图片类型不能填充
if (node.type.startsWith('image')) {
// 更新节点
if (node.label !== newNeState.neName) {
graphG6.value.updateItem(item, {
label: newNeState.neName,
});
}
// 设置状态
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
} else {
// 更新节点
graphG6.value.updateItem(item, {
label: newNeState.neName,
// neState: newNeState,
style: {
fill: stateColor, // 填充色
stroke: stateColor, // 填充色
},
// labelCfg: {
// style: {
// fill: '#ffffff', // 标签文本色
// },
// },
});
// 设置状态
graphG6.value.setItemState(item, 'stroke', newNeState.online);
}
}
// 设置边状态
for (const edge of edges) {
const edgeSource: string = edge.source;
const edgeTarget: string = edge.target;
const neS = nodes.find((n: any) => n.id === edgeSource);
const neT = nodes.find((n: any) => n.id === edgeTarget);
// console.log(neS, edgeSource, neT, edgeTarget);
if (neS && neT) {
// 通过 ID 查询节点实例
// const item = graphG6.value.findById(edge.id);
// console.log(
// `${edgeSource} - ${edgeTarget}`,
// neS.neState.online && neT.neState.online
// );
// const stateColor = neS.neState.online && neT.neState.online ? '#000000' : '#ff4d4f'; // 状态颜色
// 更新边
// graphG6.value.updateItem(item, {
// label: `${edgeSource} - ${edgeTarget}`,
// style: {
// stroke: stateColor, // 填充色
// },
// labelCfg: {
// style: {
// fill: '#ffffff', // 标签文本色
// },
// },
// });
// 设置状态
graphG6.value.setItemState(
edge.id,
'circle-move',
neS.neState.online && neT.neState.online
);
}
if (neS && notNeNodes.includes(edgeTarget)) {
graphG6.value.setItemState(edge.id, 'line-dash', neS.neState.online);
}
if (neT && notNeNodes.includes(edgeSource)) {
graphG6.value.setItemState(edge.id, 'line-dash', neT.neState.online);
}
}
}
onMounted(() => {
fnGraphDataLoad(false);
// 建立链接
const options: OptionsType = {
url: '/ws',
onmessage: wsMessage,
onerror: wsError,
};
ws.connect(options);
});
onBeforeUnmount(() => {
ws.close();
clearInterval(interval10s.value);
interval10s.value = null;
});
</script>
<template>
<PageContainer>
<a-card
:bordered="false"
:body-style="{ marginBottom: '24px' }"
size="small"
>
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
<span>
{{ t('views.monitor.topologyBuild.graphGroup') }}
{{ graphState.group }}
</span>
</a-space>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-button
type="default"
size="small"
@click.prevent="fnGraphDataLoad(true)"
>
<template #icon><ReloadOutlined /></template>
{{ t('common.reloadText') }}
</a-button>
</template>
<div ref="graphG6Dom" class="chart"></div>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.chart {
width: 100%;
height: calc(100vh - 300px);
background-color: rgb(43, 47, 51);
}
</style>

View File

@@ -1,374 +0,0 @@
<script setup lang="ts">
import { reactive, toRaw, watch } from 'vue';
import { ProModal } from 'antdv-pro-modal';
import { Form, Modal, Upload, message, notification } from 'ant-design-vue/es';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { UploadRequestOption } from 'ant-design-vue/es/vc-upload/interface';
import { FileType, UploadFile } from 'ant-design-vue/es/upload/interface';
import {
exportNeConfigBackup,
importNeConfigBackup,
listNeConfigBackup,
} from '@/api/ne/neConfigBackup';
import saveAs from 'file-saver';
import { uploadFile } from '@/api/tool/file';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:open']);
const props = defineProps({
open: {
type: Boolean,
default: false,
},
/**网元ID */
neId: {
type: String,
default: '',
},
neType: {
type: String,
default: '',
},
});
/**导入状态数据 */
const importState = reactive({
typeOption: [
{ label: t('views.ne.neInfo.backConf.server'), value: 'backup' },
{ label: t('views.ne.neInfo.backConf.local'), value: 'upload' },
],
backupData: <any[]>[],
});
/**查询网元远程服务器备份文件 */
function backupSearch(name?: string) {
const { neType, neId } = modalState.from;
listNeConfigBackup({
neType,
neId,
name,
pageNum: 1,
pageSize: 20,
}).then(res => {
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,
});
});
}
});
}
/**服务器备份文件选择切换 */
function backupChange(value: any) {
if (!value) {
backupSearch();
}
}
/**类型切换 */
function typeChange(value: any) {
modalState.from.path = undefined;
if (value === 'backup') {
backupSearch();
}
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
openByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: {
neType: string;
neId: string;
type: 'upload' | 'backup';
path: string | undefined;
};
/**确定按钮 loading */
confirmLoading: boolean;
/**上传文件 */
uploadFiles: any[];
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
openByEdit: false,
title: '配置文件导入',
from: {
neType: '',
neId: '',
type: 'upload',
path: undefined,
},
confirmLoading: false,
uploadFiles: [],
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
path: [
{
required: true,
message: t('views.ne.neInfo.backConf.pathPlease'),
},
],
})
);
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
if (modalState.confirmLoading) return;
const from = toRaw(modalState.from);
modalStateFrom
.validate()
.then(e => {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
importNeConfigBackup(from)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
// 返回无引用信息
emit('ok', JSON.parse(JSON.stringify(from)));
fnModalCancel();
} 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 fnModalCancel() {
modalState.openByEdit = false;
modalState.confirmLoading = false;
modalStateFrom.resetFields();
modalState.uploadFiles = [];
emit('cancel');
emit('update:open', false);
}
/**表单上传前删除 */
function fnBeforeRemoveFile(file: UploadFile) {
modalState.from.path = undefined;
return true;
}
/**表单上传前检查或转换压缩 */
function fnBeforeUploadFile(file: FileType) {
if (modalState.confirmLoading) return false;
if (!file.name.endsWith('.zip')) {
const msg = `${t('components.UploadModal.onlyAllow')} .zip`;
message.error(msg, 3);
return Upload.LIST_IGNORE;
}
const isLt3M = file.size / 1024 / 1024 < 100;
if (!isLt3M) {
const msg = `${t('components.UploadModal.allowFilter')} 100MB`;
message.error(msg, 3);
return Upload.LIST_IGNORE;
}
return true;
}
/**表单上传文件 */
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];
file.percent = 100;
file.status = 'done';
// 预置到表单
const { fileName } = res.data;
modalState.from.path = fileName;
} else {
message.error(res.msg, 3);
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
}
/**监听是否显示,初始数据 */
watch(
() => props.open,
val => {
if (val) {
if (props.neType && props.neId) {
modalState.from.neType = props.neType;
modalState.from.neId = props.neId;
modalState.title = t('views.ne.neInfo.backConf.title');
modalState.openByEdit = true;
}
}
}
);
/**
* 网元导出配置
* @param row 网元编号ID
*/
function fnExportConf(neType: string, neId: string) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neInfo.backConf.exportTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
exportNeConfigBackup({ neType, neId })
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
notification.success({
message: t('common.tipTitle'),
description: t('views.ne.neInfo.backConf.exportMsg'),
});
saveAs(
res.data,
`${neType}_${neId}_config_backup_${Date.now()}.zip`
);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
}
// 给组件设置属性 ref="xxxBackConf"
// setup内使用 const xxxBackConf = ref();
defineExpose({
/**导出文件 */
exportConf: fnExportConf,
});
</script>
<template>
<ProModal
:drag="true"
:width="800"
:keyboard="false"
:mask-closable="false"
:open="modalState.openByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form name="modalStateFrom" layout="horizontal" :label-col="{ span: 6 }">
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neType')" name="neType">
{{ modalState.from.neType }}
</a-form-item>
<a-form-item
:label="t('views.ne.neInfo.backConf.importType')"
name="type"
>
<a-select
v-model:value="modalState.from.type"
default-value="server"
:options="importState.typeOption"
@change="typeChange"
>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neId')" name="neId">
{{ modalState.from.neId }}
</a-form-item>
<a-form-item
:label="t('views.ne.neInfo.backConf.server')"
name="fileName"
v-bind="modalStateFrom.validateInfos.path"
v-if="modalState.from.type === 'backup'"
>
<a-select
v-model:value="modalState.from.path"
:options="importState.backupData"
: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-form-item>
<a-form-item
:label="t('views.ne.neInfo.backConf.local')"
name="file"
v-bind="modalStateFrom.validateInfos.path"
v-if="modalState.from.type === 'upload'"
>
<a-upload
name="file"
v-model:file-list="modalState.uploadFiles"
accept=".zip"
list-type="text"
:max-count="1"
:show-upload-list="{
showPreviewIcon: false,
showRemoveIcon: true,
showDownloadIcon: false,
}"
:remove="fnBeforeRemoveFile"
:before-upload="fnBeforeUploadFile"
:custom-request="fnUploadFile"
:disabled="modalState.confirmLoading"
>
<a-button type="primary">
<template #icon>
<UploadOutlined />
</template>
{{ t('views.ne.neInfo.backConf.localUpload') }}
</a-button>
</a-upload>
</a-form-item>
</a-col>
</a-row>
</a-form>
</ProModal>
</template>
<style lang="less" scoped></style>

View File

@@ -1,834 +0,0 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue';
import { ProModal } from 'antdv-pro-modal';
import { message, Form, Modal } from 'ant-design-vue/es';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
import { regExpIPv4, regExpIPv6 } from '@/utils/regular-utils';
import { getNeInfo, addNeInfo, updateNeInfo } from '@/api/ne/neInfo';
import { neHostAuthorizedRSA, testNeHost } from '@/api/ne/neHost';
import useDictStore from '@/store/modules/dict';
import useI18n from '@/hooks/useI18n';
const { getDict } = useDictStore();
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:open']);
const props = defineProps({
open: {
type: Boolean,
default: false,
},
editId: {
type: String,
default: '',
},
});
/**字典数据 */
let dict: {
/**主机类型 */
neHostType: DictType[];
/**分组 */
neHostGroupId: DictType[];
/**认证模式 */
neHostAuthMode: DictType[];
} = reactive({
neHostType: [],
neHostGroupId: [],
neHostAuthMode: [],
});
/**
* 测试主机连接
*/
function fnHostTest(row: Record<string, any>) {
if (modalState.confirmLoading || !row.addr || !row.port) return;
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
testNeHost(row)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: `${row.addr}:${row.port} ${t('views.ne.neHost.testOk')}`,
duration: 2,
});
} else {
message.error({
content: `${row.addr}:${row.port} ${res.msg}`,
duration: 2,
});
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
}
/**测试主机连接-免密直连 */
function fnHostAuthorized(row: Record<string, any>) {
if (modalState.confirmLoading) return;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neHost.authRSATip'),
onOk: () => {
modalState.confirmLoading = true;
neHostAuthorizedRSA(row).then(res => {
modalState.confirmLoading = false;
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
} else {
message.error(t('common.operateErr'), 3);
}
});
},
});
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
openByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
openByEdit: false,
title: '网元',
from: {
id: undefined,
neId: '001',
neType: 'AMF',
neName: '',
ip: '',
port: 33030,
pvFlag: 'PNF',
rmUid: '4400HXAMF001',
neAddress: '',
dn: '',
vendorName: '',
province: '',
remark: '',
// 主机
hosts: [
{
hostId: undefined,
hostType: 'ssh',
groupId: '1',
title: 'SSH_NE_22',
addr: '',
port: 22,
user: 'omcuser',
authMode: '2',
password: '',
privateKey: '',
passPhrase: '',
remark: '',
},
{
hostId: undefined,
hostType: 'telnet',
groupId: '1',
title: 'Telnet_NE_4100',
addr: '',
port: 4100,
user: 'admin',
authMode: '0',
password: 'admin',
remark: '',
},
],
},
confirmLoading: false,
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
neType: [
{
required: true,
message: t('views.ne.common.neTypePlease'),
},
],
neId: [
{
required: true,
message: t('views.ne.common.neIdPlease'),
},
],
rmUid: [
{
required: true,
message: t('views.ne.common.rmUidPlease'),
},
],
ip: [
{
required: true,
validator: modalStateFromEqualIPV4AndIPV6,
},
],
neName: [
{
required: true,
message: t('views.ne.common.neNamePlease'),
},
],
})
);
/**表单验证IP地址是否有效 */
function modalStateFromEqualIPV4AndIPV6(
rule: Record<string, any>,
value: string,
callback: (error?: string) => void
) {
if (!value) {
return Promise.reject(t('views.ne.common.ipAddrPlease'));
}
if (value.indexOf('.') === -1 && value.indexOf(':') === -1) {
return Promise.reject(t('valid.ipPlease'));
}
if (value.indexOf('.') !== -1 && !regExpIPv4.test(value)) {
return Promise.reject(t('valid.ipv4Reg'));
}
if (value.indexOf(':') !== -1 && !regExpIPv6.test(value)) {
return Promise.reject(t('valid.ipv6Reg'));
}
return Promise.resolve();
}
/**
* 对话框弹出显示为 新增或者修改
* @param editId 网元id, 不传为新增
*/
function fnModalVisibleByEdit(editId: string) {
if (!editId) {
modalStateFrom.resetFields();
modalState.title = t('views.ne.neInfo.addTitle');
modalState.openByEdit = true;
} else {
if (modalState.confirmLoading) return;
const hide = message.loading(t('common.loading'), 0);
modalState.confirmLoading = true;
getNeInfo(editId).then(res => {
modalState.confirmLoading = false;
hide();
if (res.code === RESULT_CODE_SUCCESS) {
Object.assign(modalState.from, res.data);
modalState.title = t('views.ne.neInfo.editTitle');
modalState.openByEdit = true;
} else {
message.error(t('common.getInfoFail'), 2);
}
});
}
}
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
modalStateFrom
.validate()
.then(e => {
modalState.confirmLoading = true;
const from = toRaw(modalState.from);
const result = from.id ? updateNeInfo(from) : addNeInfo(from);
const hide = message.loading(t('common.loading'), 0);
result
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
// 返回无引用信息
emit('ok', JSON.parse(JSON.stringify(from)));
fnModalCancel();
} 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 fnModalCancel() {
modalState.openByEdit = false;
modalState.confirmLoading = false;
modalStateFrom.resetFields();
emit('cancel');
emit('update:open', false);
}
/**表单修改网元类型 */
function fnNeTypeChange(v: any) {
// 网元默认只含22和4100
if (modalState.from.hosts.length === 3) {
modalState.from.hosts.pop();
}
const hostsLen = modalState.from.hosts.length;
// UPF标准版本可支持5002
if (hostsLen === 2 && v === 'UPF') {
modalState.from.hosts.push({
hostId: undefined,
hostType: 'telnet',
groupId: '1',
title: 'Telnet_NE_5002',
addr: modalState.from.ip,
port: 5002,
user: 'admin',
authMode: '0',
password: 'admin',
remark: '',
});
}
// UDM可支持6379
if (hostsLen === 2 && v === 'UDM') {
modalState.from.hosts.push({
hostId: undefined,
hostType: 'redis',
groupId: '1',
title: 'REDIS_NE_6379',
addr: modalState.from.ip,
port: 6379,
user: 'udmdb',
authMode: '0',
password: 'helloearth',
dbName: '0',
remark: '',
});
}
modalState.from.rmUid = `4400HX${v}${modalState.from.neId}`; // 4400HX1AMF001
}
/**表单修改网元neId */
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) {
const v = e.target.value;
if (v.length < 7) return;
for (const host of modalState.from.hosts) {
host.addr = v;
}
}
/**监听是否显示,初始数据 */
watch(
() => props.open,
val => {
if (val) fnModalVisibleByEdit(props.editId);
}
);
onMounted(() => {
// 初始字典数据
Promise.allSettled([
getDict('ne_host_type'),
getDict('ne_host_groupId'),
getDict('ne_host_authMode'),
]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.neHostType = resArr[0].value;
}
if (resArr[1].status === 'fulfilled') {
dict.neHostGroupId = resArr[1].value;
}
if (resArr[2].status === 'fulfilled') {
dict.neHostAuthMode = resArr[2].value;
}
});
});
</script>
<template>
<ProModal
:drag="true"
:width="800"
:destroyOnClose="true"
:body-style="{ maxHeight: '600px', 'overflow-y': 'auto' }"
:keyboard="false"
:mask-closable="false"
:open="modalState.openByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="modalStateFrom"
layout="horizontal"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.common.neType')"
name="neType"
v-bind="modalStateFrom.validateInfos.neType"
>
<a-auto-complete
v-model:value="modalState.from.neType"
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
@change="fnNeTypeChange"
>
<a-input
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="32"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.ne.common.neTypeTip') }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
</a-tooltip>
</template>
</a-input>
</a-auto-complete>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neInfo.pvflag')"
name="pvFlag"
v-bind="modalStateFrom.validateInfos.pvFlag"
>
<a-select
v-model:value="modalState.from.pvFlag"
default-value="PNF"
>
<a-select-opt-group :label="t('views.ne.neInfo.pnf')">
<a-select-option value="PNF">PNF</a-select-option>
</a-select-opt-group>
<a-select-opt-group :label="t('views.ne.neInfo.vnf')">
<a-select-option value="VNF">VNF</a-select-option>
</a-select-opt-group>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.common.neId')"
name="neId"
v-bind="modalStateFrom.validateInfos.neId"
>
<a-input
v-model:value="modalState.from.neId"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="32"
@change="fnNeIdChange"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.ne.common.neIdTip') }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.common.neName')"
name="neName"
v-bind="modalStateFrom.validateInfos.neName"
>
<a-input
v-model:value="modalState.from.neName"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="64"
>
</a-input>
</a-form-item>
</a-col>
</a-row>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.common.ipAddr')"
name="ip"
v-bind="modalStateFrom.validateInfos.ip"
>
<a-input
v-model:value="modalState.from.ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="128"
@change="fnNeIPChange"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
<div>
{{ t('views.ne.common.ipAddrTip') }}
</div>
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.common.port')"
name="port"
v-bind="modalStateFrom.validateInfos.port"
>
<a-input-number
v-model:value="modalState.from.port"
style="width: 100%"
:min="1"
:max="65535"
:maxlength="5"
placeholder="<=65535"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
<div>{{ t('views.ne.common.portTip') }}</div>
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
</a-tooltip>
</template>
</a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-form-item
:label="t('views.ne.common.rmUid')"
name="rmUid"
v-bind="modalStateFrom.validateInfos.rmUid"
:label-col="{ span: 3 }"
:labelWrap="true"
>
<a-input
v-model:value="modalState.from.rmUid"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="40"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
<div>
{{ t('views.ne.common.rmUidTip') }}
</div>
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.neInfo.neAddress')" name="neAddress">
<a-input
v-model:value="modalState.from.neAddress"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="64"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
<div>{{ t('views.ne.neInfo.neAddressTip') }}</div>
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.neInfo.dn')" name="dn">
<a-input
v-model:value="modalState.from.dn"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="255"
></a-input>
</a-form-item>
</a-col>
</a-row>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neInfo.vendorName')"
name="vendorName"
>
<a-input
v-model:value="modalState.from.vendorName"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="64"
>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.neInfo.province')" name="province">
<a-input
v-model:value="modalState.from.province"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="32"
></a-input>
</a-form-item>
</a-col>
</a-row>
<a-form-item
:label="t('common.remark')"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-textarea
v-model:value="modalState.from.remark"
:auto-size="{ minRows: 1, maxRows: 6 }"
:maxlength="450"
:show-count="true"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
<!-- 主机连接配置 -->
<a-divider orientation="left">
{{ t('views.ne.neInfo.hostConfig') }}
</a-divider>
<a-collapse class="collapse" ghost>
<a-collapse-panel
v-for="host in modalState.from.hosts.filter(
(s:any) => !(s.hostType === 'telnet' && modalState.from.neType === 'OMC')
)"
:key="host.title"
>
<template #header>
<span v-if="host.hostType === 'redis'"> DB {{ host.port }} </span>
<span v-else>
{{ `${host.hostType.toUpperCase()} ${host.port}` }}
</span>
</template>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.neHost.addr')">
<a-input
v-model:value="host.addr"
allow-clear
:maxlength="128"
:placeholder="t('common.inputPlease')"
>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neHost.port')"
name="neHost.port"
>
<a-input-number
v-model:value="host.port"
:min="10"
:max="65535"
:step="1"
:maxlength="5"
style="width: 100%"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-form-item
v-if="host.hostType === 'telnet'"
:label="t('views.ne.neHost.user')"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-input
v-model:value="host.user"
allow-clear
:maxlength="32"
:placeholder="t('common.inputPlease')"
>
</a-input>
</a-form-item>
<a-row v-if="host.hostType === 'ssh'">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.neHost.user')">
<a-input
v-model:value="host.user"
allow-clear
:maxlength="32"
:placeholder="t('common.inputPlease')"
>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.neHost.authMode')">
<a-select
v-model:value="host.authMode"
default-value="0"
:options="dict.neHostAuthMode"
>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item
v-if="host.authMode === '0'"
:label="t('views.ne.neHost.password')"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-input-password
v-model:value="host.password"
:maxlength="128"
:placeholder="t('common.inputPlease')"
>
</a-input-password>
</a-form-item>
<template v-if="host.authMode === '1'">
<a-form-item
:label="t('views.ne.neHost.privateKey')"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-textarea
v-model:value="host.privateKey"
:auto-size="{ minRows: 4, maxRows: 6 }"
:maxlength="3000"
:show-count="true"
:placeholder="t('views.ne.neHost.privateKeyPlease')"
/>
</a-form-item>
<a-form-item
:label="t('views.ne.neHost.passPhrase')"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-input-password
v-model:value="host.passPhrase"
:maxlength="128"
:placeholder="t('common.inputPlease')"
>
</a-input-password>
</a-form-item>
</template>
<a-form-item
v-if="host.hostType === 'mysql'"
:label="t('views.ne.neHost.database')"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-input
v-model:value="host.dbName"
allow-clear
:maxlength="32"
:placeholder="t('common.inputPlease')"
>
</a-input>
</a-form-item>
<a-form-item
:label="t('common.remark')"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-textarea
v-model:value="host.remark"
:auto-size="{ minRows: 1, maxRows: 6 }"
:maxlength="450"
:show-count="true"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
<!-- 测试 -->
<a-form-item
:label="t('views.ne.neHost.test')"
name="test"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-button
type="primary"
shape="round"
@click="fnHostTest(host)"
:loading="modalState.confirmLoading"
>
<template #icon><LinkOutlined /></template>
</a-button>
<a-button
type="link"
@click="fnHostAuthorized(host)"
:loading="modalState.confirmLoading"
v-if="host.hostType === 'ssh' && host.authMode !== '2'"
>
{{ t('views.ne.neHost.authRSA') }}
</a-button>
</a-form-item>
</a-collapse-panel>
</a-collapse>
</a-form>
</ProModal>
</template>
<style lang="less" scoped>
.collapse :deep(.ant-collapse-item) > .ant-collapse-header {
padding-left: 0;
padding-right: 0;
}
.collapse-header {
flex: 1;
display: flex;
flex-direction: row;
justify-content: space-between;
}
</style>

View File

@@ -1,322 +0,0 @@
<script setup lang="ts">
import { reactive, toRaw, watch } from 'vue';
import { ProModal } from 'antdv-pro-modal';
import { message, Form } from 'ant-design-vue/es';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { getOAMFile, saveOAMFile } from '@/api/ne/neInfo';
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:open']);
const props = defineProps({
open: {
type: Boolean,
default: false,
},
/**网元ID */
neId: {
type: String,
default: '',
},
neType: {
type: String,
default: '',
},
});
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
openByEdit: boolean;
/**标题 */
title: string;
/**是否同步 */
sync: boolean;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
openByEdit: false,
title: 'OAM Configuration',
sync: true,
from: {
omcIP: '',
oamEnable: true,
oamPort: 33030,
snmpEnable: true,
snmpPort: 4957,
kpiEnable: true,
kpiTimer: 60,
},
confirmLoading: false,
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
kpiTimer: [
{
required: true,
message: t('views.ne.neInfo.oam.kpiTimerPlease'),
},
],
})
);
/**
* 对话框弹出显示为 新增或者修改
* @param neType 网元类型
* @param neId 网元ID
*/
function fnModalVisibleByTypeAndId(neType: string, neId: string) {
const hide = message.loading(t('common.loading'), 0);
getOAMFile(neType, neId)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
const data = res.data;
Object.assign(modalState.from, {
omcIP: data.oamConfig[data.oamConfig.ipType],
oamEnable: data.oamConfig.enable,
oamPort: data.oamConfig.port,
snmpEnable: data.snmpConfig.enable,
snmpPort: data.snmpConfig.port,
kpiEnable: data.kpiConfig.enable,
kpiTimer: data.kpiConfig.timer,
});
modalState.title = t('views.ne.neInfo.oam.title');
modalState.openByEdit = true;
} else {
message.error(res.msg, 3);
}
})
.finally(() => {
modalState.confirmLoading = false;
hide();
});
}
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
modalStateFrom
.validate()
.then(e => {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
const from = toRaw(modalState.from);
saveOAMFile({
neType: props.neType,
neId: props.neId,
content: from,
sync: modalState.sync,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
emit('ok');
fnModalCancel();
} 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 fnModalCancel() {
modalState.openByEdit = false;
modalState.confirmLoading = false;
modalStateFrom.resetFields();
emit('cancel');
emit('update:open', false);
}
/**监听是否显示,初始数据 */
watch(
() => props.open,
val => {
if (val) {
if (props.neType && props.neId) {
fnModalVisibleByTypeAndId(props.neType, props.neId);
}
}
}
);
</script>
<template>
<ProModal
:drag="true"
:destroyOnClose="true"
:body-style="{ maxHeight: '600px', 'overflow-y': 'auto' }"
:keyboard="false"
:mask-closable="false"
:open="modalState.openByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="modalStateFrom"
layout="horizontal"
:label-col="{ span: 12 }"
:labelWrap="true"
>
<a-form-item
:label="t('views.ne.neInfo.oam.sync')"
name="sync"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-switch
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
v-model:checked="modalState.sync"
></a-switch>
</a-form-item>
<a-collapse class="collapse" ghost>
<a-collapse-panel header="OAM">
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neInfo.oam.oamEnable')"
name="oamEnable"
>
<a-switch
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
v-model:checked="modalState.from.oamEnable"
></a-switch>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neInfo.oam.oamPort')"
name="oamPort"
v-bind="modalStateFrom.validateInfos.oamPort"
>
<a-input-number
:min="3000"
:max="65535"
:step="1"
:maxlength="5"
v-model:value="modalState.from.oamPort"
style="width: 100%"
></a-input-number>
</a-form-item>
</a-col>
</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 header="SNMP">
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neInfo.oam.snmpEnable')"
name="snmpEnable"
>
<a-switch
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
v-model:checked="modalState.from.snmpEnable"
></a-switch>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neInfo.oam.snmpPort')"
name="snmpPort"
v-bind="modalStateFrom.validateInfos.snmpPort"
>
<a-input-number
:min="3000"
:max="65535"
:step="1"
:maxlength="5"
v-model:value="modalState.from.snmpPort"
style="width: 100%"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
</a-collapse-panel>
<a-collapse-panel header="KPI">
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neInfo.oam.kpiEnable')"
name="kpiEnable"
>
<a-switch
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
v-model:checked="modalState.from.kpiEnable"
></a-switch>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.neInfo.oam.kpiTimer')"
name="kpiTimer"
v-bind="modalStateFrom.validateInfos.kpiTimer"
>
<a-input-number
:min="5"
:max="3600"
:step="1"
:maxlength="4"
v-model:value="modalState.from.kpiTimer"
style="width: 100%"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
</a-collapse-panel>
</a-collapse>
</a-form>
</ProModal>
</template>
<style lang="less" scoped>
.collapse :deep(.ant-collapse-item) > .ant-collapse-header {
padding-left: 0;
padding-right: 0;
}
.collapse-header {
flex: 1;
display: flex;
flex-direction: row;
justify-content: space-between;
}
</style>

View File

@@ -1,153 +0,0 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { Modal, message } from 'ant-design-vue/es';
import useI18n from '@/hooks/useI18n';
import { useRouter } from 'vue-router';
import { updateNeConfigReload } from '@/api/configManage/configParam';
import { serviceNeAction } from '@/api/ne/neInfo';
import useMaskStore from '@/store/modules/mask';
export default function useNeOptions() {
const router = useRouter();
const { t } = useI18n();
const maskStore = useMaskStore();
/**
* 网元启动
* @param row {neName,neType,neId}
*/
function fnNeStart(row: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.common.startTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
serviceNeAction({
neType: row.neType,
neId: row.neId,
action: 'start',
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
}
/**
* 网元重启
* @param row {neName,neType,neId}
*/
function fnNeRestart(row: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.common.restartTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
serviceNeAction({
neType: row.neType,
neId: row.neId,
action: 'restart',
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// OMC自升级
if (row.neType.toUpperCase() === 'OMC') {
if (res.code === RESULT_CODE_SUCCESS) {
maskStore.handleMaskType('reload');
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
return;
}
message.success(t('common.operateOk'), 3);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
}
/**
* 网元停止
* @param row {neName,neType,neId}
*/
function fnNeStop(row: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.common.stopTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
serviceNeAction({
neType: row.neType,
neId: row.neId,
action: 'stop',
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
}
/**
* 网元重新加载
* @param row {neName,neType,neId}
*/
function fnNeReload(row: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.common.reloadTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
updateNeConfigReload(row.neType, row.neId)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
}
/**
* 跳转网元日志文件页面
* @param row {neType,neId}
*/
function fnNeLogFile(row: Record<string, any>) {
router.push({
name: 'NeFile_2123',
query: {
neType: row.neType,
neId: row.neId,
},
});
}
return { fnNeStart, fnNeRestart, fnNeStop, fnNeReload, fnNeLogFile };
}

View File

@@ -1,790 +0,0 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, defineAsyncComponent, ref } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/es/table';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useNeInfoStore from '@/store/modules/neinfo';
import { listNeInfo, delNeInfo, stateNeInfo } from '@/api/ne/neInfo';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
import { hasRoles } from '@/plugins/auth-user';
import useDictStore from '@/store/modules/dict';
import useNeOptions from './hooks/useNeOptions';
const { getDict } = useDictStore();
const { t } = useI18n();
const { fnNeStart, fnNeRestart, fnNeStop, fnNeReload, fnNeLogFile } =
useNeOptions();
// 异步加载组件
const EditModal = defineAsyncComponent(
() => import('./components/EditModal.vue')
);
const OAMModal = defineAsyncComponent(
() => import('./components/OAMModal.vue')
);
// 配置备份文件导入
const BackConfModal = defineAsyncComponent(
() => import('./components/BackConfModal.vue')
);
const backConf = ref(); // 引用句柄,取导出函数
/**字典数据 */
let dict: {
/**网元信息状态 */
neInfoStatus: DictType[];
} = reactive({
neInfoStatus: [],
});
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: '',
/**带状态信息 */
bandStatus: true,
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
neType: '',
pageNum: 1,
pageSize: 20,
});
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: Record<string, any>[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: false,
data: [],
selectedRowKeys: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
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('views.ne.common.rmUid'),
dataIndex: 'rmUid',
align: 'left',
width: 150,
},
{
title: t('views.ne.common.neName'),
dataIndex: 'neName',
align: 'left',
width: 150,
},
{
title: t('views.ne.common.ipAddr'),
dataIndex: 'ip',
align: 'left',
width: 150,
},
{
title: t('views.ne.common.port'),
dataIndex: 'port',
align: 'left',
width: 100,
},
{
title: t('views.ne.neInfo.state'),
dataIndex: 'status',
key: 'status',
align: 'left',
width: 100,
},
{
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 = {
/**配置备份框是否显示 */
openByBackConf: boolean;
/**OAM文件配置框是否显示 */
openByOAM: boolean;
/**新增框或修改框是否显示 */
openByEdit: boolean;
/**新增框或修改框ID */
editId: string;
/**OAM框网元类型ID */
neId: string;
neType: string;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
openByBackConf: false,
openByOAM: false,
openByEdit: false,
editId: '',
neId: '',
neType: '',
confirmLoading: false,
});
/**
* 对话框弹出显示为 新增或者修改
* @param noticeId 网元id, 不传为新增
*/
function fnModalVisibleByEdit(row?: Record<string, any>) {
if (!row) {
modalState.editId = '';
} else {
modalState.editId = row.id;
}
modalState.openByEdit = !modalState.openByEdit;
}
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalEditOk(from: Record<string, any>) {
// 新增时刷新列表
if (!from.id) {
fnGetList();
return;
}
// 编辑时局部更新信息
stateNeInfo(from.neType, from.neId)
.then(res => {
// 找到编辑更新的网元
const item = tableState.data.find(s => s.id === from.id);
if (item && res.code === RESULT_CODE_SUCCESS) {
item.neType = from.neType;
item.neId = from.neId;
item.rmUid = from.rmUid;
item.neName = from.neName;
item.ip = from.ip;
item.port = from.port;
if (item.status !== '2') {
item.status = res.data.online ? '1' : '0';
}
Object.assign(item.serverState, res.data);
const resouresUsage = parseResouresUsage(item.serverState);
Reflect.set(item, 'resoures', resouresUsage);
}
})
.finally(() => {
useNeInfoStore().fnRefreshNelist();
});
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalEditCancel() {
modalState.editId = '';
modalState.openByEdit = false;
modalState.openByOAM = false;
modalState.openByBackConf = false;
}
/**
* 记录删除
* @param id 编号
*/
function fnRecordDelete(id: string) {
if (!id || modalState.confirmLoading) return;
let msg = t('views.ne.neInfo.delTip');
if (id === '0') {
msg = `${msg} ...${tableState.selectedRowKeys.length}`;
id = tableState.selectedRowKeys.join(',');
}
Modal.confirm({
title: t('common.tipTitle'),
content: msg,
onOk() {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
delNeInfo(id)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
// 过滤掉删除的id
tableState.data = tableState.data.filter(item => {
if (id.indexOf(',') > -1) {
return !tableState.selectedRowKeys.includes(item.id);
} else {
return item.id !== id;
}
});
// 刷新缓存
useNeInfoStore().fnRefreshNelist();
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
},
});
}
/**
* 记录多项选择
*/
function fnRecordMore(type: string | number, row: Record<string, any>) {
switch (type) {
case 'delete':
fnRecordDelete(row.id);
break;
case 'start':
fnNeStart(row);
break;
case 'restart':
fnNeRestart(row);
break;
case 'stop':
fnNeStop(row);
break;
case 'reload':
fnNeReload(row);
break;
case 'log':
fnNeLogFile(row);
break;
case 'oam':
modalState.neId = row.neId;
modalState.neType = row.neType;
modalState.openByOAM = !modalState.openByOAM;
break;
case 'backConfExport':
backConf.value.exportConf(row.neType, row.neId);
break;
case 'backConfImport':
modalState.neId = row.neId;
modalState.neType = row.neType;
modalState.openByBackConf = !modalState.openByBackConf;
break;
default:
console.warn(type);
break;
}
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
listNeInfo(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;
// 遍历处理资源情况数值
tableState.data = res.rows.map(item => {
let resouresUsage = {
sysDiskUsage: 0,
sysMemUsage: 0,
sysCpuUsage: 0,
nfCpuUsage: 0,
};
const neState = item.serverState;
if (neState) {
resouresUsage = parseResouresUsage(neState);
} else {
item.serverState = { online: false };
}
Reflect.set(item, 'resoures', resouresUsage);
return item;
});
}
tableState.loading = false;
});
}
/**解析网元状态携带的资源利用率 */
function parseResouresUsage(neState: Record<string, any>) {
let sysCpuUsage = 0;
let nfCpuUsage = 0;
if (neState.cpu) {
nfCpuUsage = neState.cpu.nfCpuUsage;
const nfCpu = +(nfCpuUsage / 100);
nfCpuUsage = +nfCpu.toFixed(2);
if (nfCpuUsage > 100) {
nfCpuUsage = 100;
}
sysCpuUsage = neState.cpu.sysCpuUsage;
const sysCpu = +(sysCpuUsage / 100);
sysCpuUsage = +sysCpu.toFixed(2);
if (sysCpuUsage > 100) {
sysCpuUsage = 100;
}
}
let sysMemUsage = 0;
if (neState.mem) {
const men = neState.mem.sysMemUsage;
sysMemUsage = +(men / 100).toFixed(2);
if (sysMemUsage > 100) {
sysMemUsage = 100;
}
}
let sysDiskUsage = 0;
if (neState.disk && Array.isArray(neState.disk.partitionInfo)) {
let disks: any[] = neState.disk.partitionInfo;
disks = disks.sort((a, b) => +b.used - +a.used);
if (disks.length > 0) {
const { total, used } = disks[0];
sysDiskUsage = +((used / total) * 100).toFixed(2);
}
}
return {
sysDiskUsage,
sysMemUsage,
sysCpuUsage,
nfCpuUsage,
};
}
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('ne_info_status')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.neInfoStatus = resArr[0].value;
}
});
// 刷新缓存的网元信息
useNeInfoStore()
.fnRefreshNelist()
.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
:placeholder="t('common.inputPlease')"
/>
</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" v-roles:has="['admin']">
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
<template #icon><PlusOutlined /></template>
{{ t('common.addText') }}
</a-button>
<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-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>
<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 * 120 }"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<DictTag :options="dict.neInfoStatus" :value="record.status" />
</template>
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<span v-roles:has="['admin']">
<a-tooltip>
<template #title>{{ t('common.editText') }}</template>
<a-button
type="link"
@click.prevent="fnModalVisibleByEdit(record)"
>
<template #icon><FormOutlined /></template>
</a-button>
</a-tooltip>
</span>
<span v-roles:has="['admin', 'teacher']">
<a-tooltip>
<template #title>
{{ t('views.ne.common.restart') }}
</template>
<a-button
type="link"
@click.prevent="fnRecordMore('restart', record)"
>
<template #icon><UndoOutlined /></template>
</a-button>
</a-tooltip>
</span>
<a-tooltip placement="left">
<template #title>{{ t('common.moreText') }}</template>
<a-dropdown placement="bottomRight" trigger="click">
<a-button type="link">
<template #icon><EllipsisOutlined /> </template>
</a-button>
<template #overlay>
<a-menu @click="({ key }:any) => fnRecordMore(key, record)">
<a-menu-item key="log">
<FileTextOutlined />
{{ t('views.ne.common.log') }}
</a-menu-item>
<a-menu-item key="start" v-if="hasRoles(['admin'])">
<ThunderboltOutlined />
{{ t('views.ne.common.start') }}
</a-menu-item>
<a-menu-item key="stop" v-if="hasRoles(['admin'])">
<CloseSquareOutlined />
{{ t('views.ne.common.stop') }}
</a-menu-item>
<a-menu-item
key="reload"
v-if="
!['OMC', 'PCF', 'IMS', 'MME'].includes(
record.neType
) && hasRoles(['admin'])
"
>
<SyncOutlined />
{{ t('views.ne.common.reload') }}
</a-menu-item>
<a-menu-item key="delete" v-if="hasRoles(['admin'])">
<DeleteOutlined />
{{ t('common.deleteText') }}
</a-menu-item>
<a-menu-item key="oam" v-if="hasRoles(['admin'])">
<FileTextOutlined />
{{ t('views.ne.common.oam') }}
</a-menu-item>
<!-- 配置备份 -->
<a-menu-item key="backConfExport">
<ExportOutlined />
{{ t('views.ne.neInfo.backConf.export') }}
</a-menu-item>
<a-menu-item key="backConfImport">
<ImportOutlined />
{{ t('views.ne.neInfo.backConf.import') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
</template>
</template>
<template #expandedRowRender="{ record }">
<a-row :gutter="16">
<a-col :offset="2" :lg="8" :md="8" :xs="8">
<a-divider orientation="left">
{{ t('views.ne.neInfo.info') }}
</a-divider>
<div>
<span>{{ t('views.ne.neInfo.serviceState') }}</span>
<a-tag
:color="record.serverState.online ? 'processing' : 'error'"
>
{{
record.serverState.online
? t('views.ne.common.normalcy')
: t('views.ne.common.exceptions')
}}
</a-tag>
</div>
<div>
<span>{{ t('views.ne.neVersion.version') }}</span>
<span>{{ record.serverState.version }}</span>
</div>
<div>
<span>{{ t('views.ne.common.serialNum') }}</span>
<span>{{ record.serverState.sn }}</span>
</div>
<div>
<span>{{ t('views.ne.common.expiryDate') }}</span>
<span>{{ record.serverState.expire }}</span>
</div>
</a-col>
<a-col :offset="2" :lg="8" :md="8" :xs="8">
<a-divider orientation="left">
{{ t('views.ne.neInfo.resourceInfo') }}
</a-divider>
<div>
<span>{{ t('views.ne.neInfo.neCpu') }}</span>
<a-progress
status="normal"
:stroke-color="
record.resoures.nfCpuUsage < 30
? '#52c41a'
: record.resoures.nfCpuUsage > 70
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.nfCpuUsage"
/>
</div>
<div>
<span>{{ t('views.ne.neInfo.sysCpu') }}</span>
<a-progress
status="normal"
:stroke-color="
record.resoures.sysCpuUsage < 30
? '#52c41a'
: record.resoures.sysCpuUsage > 70
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.sysCpuUsage"
/>
</div>
<div>
<span>{{ t('views.ne.neInfo.sysMem') }}</span>
<a-progress
status="normal"
:stroke-color="
record.resoures.sysMemUsage < 30
? '#52c41a'
: record.resoures.sysMemUsage > 70
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.sysMemUsage"
/>
</div>
<div>
<span>{{ t('views.ne.neInfo.sysDisk') }}</span>
<a-progress
status="normal"
:stroke-color="
record.resoures.sysDiskUsage < 30
? '#52c41a'
: record.resoures.sysDiskUsage > 70
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.sysDiskUsage"
/>
</div>
</a-col>
</a-row>
</template>
</a-table>
</a-card>
<!-- 新增框或修改框 -->
<EditModal
v-model:open="modalState.openByEdit"
:edit-id="modalState.editId"
@ok="fnModalEditOk"
@cancel="fnModalEditCancel"
></EditModal>
<!-- OAM编辑框 -->
<OAMModal
v-model:open="modalState.openByOAM"
:ne-id="modalState.neId"
:ne-type="modalState.neType"
@cancel="fnModalEditCancel"
></OAMModal>
<!-- 配置文件备份框 -->
<BackConfModal
ref="backConf"
v-model:open="modalState.openByBackConf"
:ne-id="modalState.neId"
:ne-type="modalState.neType"
@cancel="fnModalEditCancel"
></BackConfModal>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/** /**
* =============== Configuration File Description =============== * =============== Configuration File Description ===============
* *
* - Nginx Deployment * - Nginx Deployment
* Delete the file with the same name under the same level of loading.js, Nginx proxy address: /omc-api * Delete the file with the same name under the same level of loading.js, Nginx proxy address: /omc-api
* *
@@ -10,12 +10,19 @@
* *
*/ */
(function () { (function () {
// host = ip:port // baseUrl = protocol://ip:port
// const host = '192.168.8.100:33030'; // baseUrl = 'http://192.168.8.100:33030';
const host = `${window.location.hostname}:33030`; const protocol = window.location.protocol
let wsprotocol = "ws:"
const hostname = window.location.hostname
let host = `${hostname}:33030`;
if (protocol === 'https:') {
host = `${hostname}:33443`;
wsprotocol = "wss:"
}
// Service Address // Service Address
sessionStorage.setItem('baseUrl', `http://${host}`); sessionStorage.setItem('baseUrl', `${protocol}//${host}`);
// websocket Address // websocket Address
sessionStorage.setItem('wsUrl', `ws://${host}`); sessionStorage.setItem('wsUrl', `${wsprotocol}//${host}`);
})(); })();

BIN
public/nbStateImput/en.xlsx Normal file

Binary file not shown.

BIN
public/nbStateImput/zh.xlsx Normal file

Binary file not shown.

28
public/svg/base4G.svg Normal file
View File

@@ -0,0 +1,28 @@
<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg">
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
<g>
<title>background</title>
<rect fill="none" id="canvas_background" height="1026" width="1026" y="-1" x="-1"/>
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
<rect fill="url(#gridpattern)" stroke-width="0" y="1" x="1" height="768" width="1024"/>
</g>
</g>
<g>
<title>Layer 1</title>
<g stroke="null" id="svg_15">
<path stroke="null" id="svg_4" fill="#B5D6FB" d="m512.094844,961.632039c-1.327621,0 -2.560412,-0.405439 -3.793202,-1.114958l-405.588164,-251.575028c-2.275921,-1.419037 -3.698372,-4.054392 -3.698372,-6.892467l0,-90.007504c0,-2.838074 1.422451,-5.473429 3.698372,-6.892467l405.588164,-255.426701c1.137961,-0.709519 2.465582,-1.114958 3.793202,-1.114958s2.655242,0.405439 3.793202,1.114958l405.493334,255.426701c2.275921,1.419037 3.698372,4.054392 3.698372,6.892467l0,90.007504c0,2.838074 -1.422451,5.473429 -3.698372,6.892467l-405.588164,251.575028c-1.137961,0.709519 -2.465582,1.114958 -3.698372,1.114958z"/>
<path stroke="null" id="svg_5" fill="#0276F7" d="m512.094844,356.615382l405.398504,255.426701l0,90.007504l-66.096551,40.94936l-339.301952,210.625668l-339.491613,-210.625668l-66.096551,-40.94936l0,-90.007504l405.588164,-255.426701m0,-16.014849c-2.655242,0 -5.215653,0.709519 -7.586405,2.229916l-405.588164,255.426701c-4.551843,2.838074 -7.396745,8.108784 -7.396745,13.784933l0,90.007504c0,5.676149 2.844902,10.946859 7.491575,13.886293l66.096551,41.05072l339.491613,210.625668c2.275921,1.419037 4.931163,2.128556 7.491575,2.128556s5.215653,-0.709519 7.491575,-2.128556l339.301952,-210.625668l66.096551,-40.94936c4.646673,-2.838074 7.491575,-8.108784 7.491575,-13.886293l0,-90.007504c0,-5.676149 -2.844902,-10.946859 -7.396745,-13.784933l-405.398504,-255.426701c-2.370751,-1.520397 -5.025993,-2.331275 -7.586405,-2.331275z"/>
<path stroke="null" id="svg_6" fill="#FFFFFF" d="m106.50668,612.042083l405.493334,253.298145l405.493334,-253.298145l-405.398504,-255.426701l-405.588164,255.426701z"/>
<path stroke="null" id="svg_7" fill="#D4E4FC" d="m501.473877,64.192353l-254.9032,498.487506l263.343075,161.162085l266.662127,-162.074323l-275.102002,-497.575268z"/>
<path stroke="null" id="svg_8" fill="#0276F7" d="m229.975417,602.311542c-1.232791,0 -2.465582,-0.304079 -3.698372,-1.013598c-3.603542,-2.128556 -4.931163,-6.993826 -2.844902,-10.845499l279.653845,-532.13896c1.327621,-2.533995 3.793202,-4.054392 6.543274,-4.054392c2.655242,0 5.120823,1.520397 6.543274,4.054392l284.395348,532.13896c2.086261,3.851672 0.75864,8.716943 -2.750072,10.946859c-3.603542,2.128556 -8.155385,0.810878 -10.241646,-2.939434l-277.852074,-519.874424l-273.205401,519.671704c-1.422451,2.635355 -3.982862,4.054392 -6.543274,4.054392z"/>
<path stroke="null" id="svg_9" fill="#0276F7" d="m509.913752,755.567562c-4.172523,0 -7.491575,-3.547593 -7.491575,-8.007424l0,-666.744777c0,-4.459831 3.319052,-8.007424 7.491575,-8.007424s7.491575,3.547593 7.491575,8.007424l0,666.846137c0,4.358471 -3.413882,7.906065 -7.491575,7.906065z"/>
<path stroke="null" id="svg_10" fill="#0276F7" d="m509.913752,731.849369c-1.327621,0 -2.560412,-0.405439 -3.698372,-1.013598l-263.343075,-161.162085c-3.603542,-2.229916 -4.836333,-7.095186 -2.750072,-10.946859c2.086261,-3.851672 6.638104,-5.16935 10.241646,-2.939434l259.549873,158.83081l262.963755,-159.844408c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.946859l-266.662127,162.074323c-1.137961,0.709519 -2.465582,1.013598 -3.698372,1.013598z"/>
<path stroke="null" id="svg_11" fill="#0276F7" d="m509.913752,579.708306c-1.327621,0 -2.560412,-0.405439 -3.793202,-1.114958l-201.988026,-125.686154c-3.603542,-2.229916 -4.741503,-7.095186 -2.750072,-10.946859c2.086261,-3.851672 6.638104,-5.16935 10.241646,-2.838074l198.289654,123.354879l201.798366,-122.138561c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.845499l-205.496739,124.469837c-1.137961,0.709519 -2.465582,1.013598 -3.698372,1.013598zm-2.465582,-157.513132c-1.232791,0 -2.370751,-0.304079 -3.508712,-0.912238l-140.917468,-79.668804c-3.698372,-2.128556 -5.025993,-6.892467 -3.129392,-10.845499c1.896601,-3.953032 6.448444,-5.37207 10.146816,-3.344873l137.503586,77.742968l143.00373,-79.871524c3.698372,-2.027196 8.155385,-0.506799 10.146816,3.344873c1.896601,3.953032 0.47415,8.716943 -3.129392,10.845499l-146.512442,81.79736c-1.232791,0.608159 -2.370751,0.912238 -3.603542,0.912238zm2.465582,-148.49211c-1.232791,0 -2.465582,-0.304079 -3.508712,-0.912238l-82.312492,-47.436387c-3.603542,-2.128556 -5.025993,-6.993826 -3.034562,-10.845499c1.991431,-3.953032 6.543274,-5.27071 10.146816,-3.243514l78.708949,45.409191l78.329629,-47.537747c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.946859l-81.933171,49.666303c-1.232791,0.608159 -2.560412,0.912238 -3.793202,0.912238z"/>
<path stroke="null" id="svg_12" fill="#0276F7" d="m509.913752,579.708306l-0.28449,0l-263.248245,-9.021022c-4.172523,-0.10136 -7.396745,-3.851672 -7.207085,-8.210144c0.09483,-4.459831 4.077693,-7.703345 7.681235,-7.703345l263.343075,9.021022c4.172523,0.10136 7.396745,3.851672 7.207085,8.210144c-0.18966,4.257112 -3.508712,7.703345 -7.491575,7.703345zm0,152.141063c-1.612111,0 -3.224222,-0.608159 -4.646673,-1.723117c-3.224222,-2.736715 -3.793202,-7.804705 -1.232791,-11.250938l205.496739,-276.610899c2.560412,-3.446233 7.301915,-4.054392 10.526137,-1.317677c3.224222,2.736715 3.793202,7.804705 1.232791,11.250938l-205.496739,276.610899c-1.517281,2.027196 -3.698372,3.040794 -5.879464,3.040794z"/>
<path stroke="null" id="svg_13" fill="#0276F7" d="m509.913752,579.708306c-1.422451,0 -2.750072,-0.405439 -4.077693,-1.216318c-3.508712,-2.432635 -4.457013,-7.297906 -2.275921,-11.048218l144.14169,-239.310492c2.275921,-3.750313 6.922594,-4.763911 10.336476,-2.432635c3.508712,2.432635 4.457013,7.297906 2.275921,11.048218l-144.14169,239.310492c-1.422451,2.331275 -3.793202,3.648953 -6.258784,3.648953zm-2.465582,-157.513132c-1.043131,0 -2.086261,-0.20272 -3.129392,-0.709519c-3.793202,-1.824476 -5.405313,-6.588387 -3.698372,-10.642779l84.398753,-198.158413c1.706941,-4.054392 6.069124,-5.777509 9.957156,-3.953032c3.793202,1.824476 5.405313,6.588387 3.698372,10.642779l-84.303923,198.158413c-1.327621,2.939434 -4.077693,4.662551 -6.922594,4.662551z"/>
<path stroke="null" id="svg_14" fill="#0276F7" d="m591.846924,375.062866c-2.750072,0 -5.405313,-1.621757 -6.732934,-4.459831c-1.801771,-3.953032 -0.28449,-8.716943 3.413882,-10.642779l129.253371,-67.302908l-365.759539,-178.089172l20.862613,208.091673l133.994874,-64.262114c3.698372,-1.824476 8.155385,0 9.862326,4.054392c1.706941,4.054392 0,8.716943 -3.793202,10.541419l-143.38305,68.823305c-2.181091,1.013598 -4.646673,0.912238 -6.827764,-0.405439c-2.086261,-1.317677 -3.413882,-3.547593 -3.698372,-6.081588l-23.328195,-233.026185c-0.28449,-2.838074 0.853471,-5.676149 3.034562,-7.297906c2.181091,-1.621757 5.025993,-2.027196 7.491575,-0.810878l392.217126,190.961867c2.655242,1.317677 4.362183,4.054392 4.362183,7.196546c0,3.142154 -1.612111,5.980228 -4.172523,7.297906l-143.57271,74.600814c-1.043131,0.608159 -2.181091,0.810878 -3.224222,0.810878zm-283.921198,78.959286c-3.603542,0 -6.827764,-2.838074 -7.396745,-6.791107c-0.56898,-4.358471 2.181091,-8.412864 6.258784,-9.122382l199.617275,-31.826978c4.077693,-0.608159 7.870895,2.331275 8.534705,6.689747c0.56898,4.358471 -2.181091,8.412864 -6.258784,9.122382l-199.617275,31.826978c-0.47415,0.10136 -0.853471,0.10136 -1.137961,0.10136z"/>
</g>
<text stroke="null" font-style="italic" transform="matrix(6.577099502228161,0,0,7.449448263868419,-1073.2057632249744,-908.8606073938396) " xml:space="preserve" text-anchor="start" font-family="Arvo, sans-serif" font-size="24" id="svg_16" y="177.898525" x="178.621382" stroke-width="0" fill="#B5D6FB">4G</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

28
public/svg/base5G.svg Normal file
View File

@@ -0,0 +1,28 @@
<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg">
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
<g>
<title>background</title>
<rect fill="none" id="canvas_background" height="1026" width="1026" y="-1" x="-1"/>
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
<rect fill="url(#gridpattern)" stroke-width="0" y="1" x="1" height="768" width="1024"/>
</g>
</g>
<g>
<title>Layer 1</title>
<g stroke="null" id="svg_15">
<path stroke="null" id="svg_4" fill="#B5D6FB" d="m512.094844,961.632039c-1.327621,0 -2.560412,-0.405439 -3.793202,-1.114958l-405.588164,-251.575028c-2.275921,-1.419037 -3.698372,-4.054392 -3.698372,-6.892467l0,-90.007504c0,-2.838074 1.422451,-5.473429 3.698372,-6.892467l405.588164,-255.426701c1.137961,-0.709519 2.465582,-1.114958 3.793202,-1.114958s2.655242,0.405439 3.793202,1.114958l405.493334,255.426701c2.275921,1.419037 3.698372,4.054392 3.698372,6.892467l0,90.007504c0,2.838074 -1.422451,5.473429 -3.698372,6.892467l-405.588164,251.575028c-1.137961,0.709519 -2.465582,1.114958 -3.698372,1.114958z"/>
<path stroke="null" id="svg_5" fill="#0276F7" d="m512.094844,356.615382l405.398504,255.426701l0,90.007504l-66.096551,40.94936l-339.301952,210.625668l-339.491613,-210.625668l-66.096551,-40.94936l0,-90.007504l405.588164,-255.426701m0,-16.014849c-2.655242,0 -5.215653,0.709519 -7.586405,2.229916l-405.588164,255.426701c-4.551843,2.838074 -7.396745,8.108784 -7.396745,13.784933l0,90.007504c0,5.676149 2.844902,10.946859 7.491575,13.886293l66.096551,41.05072l339.491613,210.625668c2.275921,1.419037 4.931163,2.128556 7.491575,2.128556s5.215653,-0.709519 7.491575,-2.128556l339.301952,-210.625668l66.096551,-40.94936c4.646673,-2.838074 7.491575,-8.108784 7.491575,-13.886293l0,-90.007504c0,-5.676149 -2.844902,-10.946859 -7.396745,-13.784933l-405.398504,-255.426701c-2.370751,-1.520397 -5.025993,-2.331275 -7.586405,-2.331275z"/>
<path stroke="null" id="svg_6" fill="#FFFFFF" d="m106.50668,612.042083l405.493334,253.298145l405.493334,-253.298145l-405.398504,-255.426701l-405.588164,255.426701z"/>
<path stroke="null" id="svg_7" fill="#D4E4FC" d="m501.473877,64.192353l-254.9032,498.487506l263.343075,161.162085l266.662127,-162.074323l-275.102002,-497.575268z"/>
<path stroke="null" id="svg_8" fill="#0276F7" d="m229.975417,602.311542c-1.232791,0 -2.465582,-0.304079 -3.698372,-1.013598c-3.603542,-2.128556 -4.931163,-6.993826 -2.844902,-10.845499l279.653845,-532.13896c1.327621,-2.533995 3.793202,-4.054392 6.543274,-4.054392c2.655242,0 5.120823,1.520397 6.543274,4.054392l284.395348,532.13896c2.086261,3.851672 0.75864,8.716943 -2.750072,10.946859c-3.603542,2.128556 -8.155385,0.810878 -10.241646,-2.939434l-277.852074,-519.874424l-273.205401,519.671704c-1.422451,2.635355 -3.982862,4.054392 -6.543274,4.054392z"/>
<path stroke="null" id="svg_9" fill="#0276F7" d="m509.913752,755.567562c-4.172523,0 -7.491575,-3.547593 -7.491575,-8.007424l0,-666.744777c0,-4.459831 3.319052,-8.007424 7.491575,-8.007424s7.491575,3.547593 7.491575,8.007424l0,666.846137c0,4.358471 -3.413882,7.906065 -7.491575,7.906065z"/>
<path stroke="null" id="svg_10" fill="#0276F7" d="m509.913752,731.849369c-1.327621,0 -2.560412,-0.405439 -3.698372,-1.013598l-263.343075,-161.162085c-3.603542,-2.229916 -4.836333,-7.095186 -2.750072,-10.946859c2.086261,-3.851672 6.638104,-5.16935 10.241646,-2.939434l259.549873,158.83081l262.963755,-159.844408c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.946859l-266.662127,162.074323c-1.137961,0.709519 -2.465582,1.013598 -3.698372,1.013598z"/>
<path stroke="null" id="svg_11" fill="#0276F7" d="m509.913752,579.708306c-1.327621,0 -2.560412,-0.405439 -3.793202,-1.114958l-201.988026,-125.686154c-3.603542,-2.229916 -4.741503,-7.095186 -2.750072,-10.946859c2.086261,-3.851672 6.638104,-5.16935 10.241646,-2.838074l198.289654,123.354879l201.798366,-122.138561c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.845499l-205.496739,124.469837c-1.137961,0.709519 -2.465582,1.013598 -3.698372,1.013598zm-2.465582,-157.513132c-1.232791,0 -2.370751,-0.304079 -3.508712,-0.912238l-140.917468,-79.668804c-3.698372,-2.128556 -5.025993,-6.892467 -3.129392,-10.845499c1.896601,-3.953032 6.448444,-5.37207 10.146816,-3.344873l137.503586,77.742968l143.00373,-79.871524c3.698372,-2.027196 8.155385,-0.506799 10.146816,3.344873c1.896601,3.953032 0.47415,8.716943 -3.129392,10.845499l-146.512442,81.79736c-1.232791,0.608159 -2.370751,0.912238 -3.603542,0.912238zm2.465582,-148.49211c-1.232791,0 -2.465582,-0.304079 -3.508712,-0.912238l-82.312492,-47.436387c-3.603542,-2.128556 -5.025993,-6.993826 -3.034562,-10.845499c1.991431,-3.953032 6.543274,-5.27071 10.146816,-3.243514l78.708949,45.409191l78.329629,-47.537747c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.946859l-81.933171,49.666303c-1.232791,0.608159 -2.560412,0.912238 -3.793202,0.912238z"/>
<path stroke="null" id="svg_12" fill="#0276F7" d="m509.913752,579.708306l-0.28449,0l-263.248245,-9.021022c-4.172523,-0.10136 -7.396745,-3.851672 -7.207085,-8.210144c0.09483,-4.459831 4.077693,-7.703345 7.681235,-7.703345l263.343075,9.021022c4.172523,0.10136 7.396745,3.851672 7.207085,8.210144c-0.18966,4.257112 -3.508712,7.703345 -7.491575,7.703345zm0,152.141063c-1.612111,0 -3.224222,-0.608159 -4.646673,-1.723117c-3.224222,-2.736715 -3.793202,-7.804705 -1.232791,-11.250938l205.496739,-276.610899c2.560412,-3.446233 7.301915,-4.054392 10.526137,-1.317677c3.224222,2.736715 3.793202,7.804705 1.232791,11.250938l-205.496739,276.610899c-1.517281,2.027196 -3.698372,3.040794 -5.879464,3.040794z"/>
<path stroke="null" id="svg_13" fill="#0276F7" d="m509.913752,579.708306c-1.422451,0 -2.750072,-0.405439 -4.077693,-1.216318c-3.508712,-2.432635 -4.457013,-7.297906 -2.275921,-11.048218l144.14169,-239.310492c2.275921,-3.750313 6.922594,-4.763911 10.336476,-2.432635c3.508712,2.432635 4.457013,7.297906 2.275921,11.048218l-144.14169,239.310492c-1.422451,2.331275 -3.793202,3.648953 -6.258784,3.648953zm-2.465582,-157.513132c-1.043131,0 -2.086261,-0.20272 -3.129392,-0.709519c-3.793202,-1.824476 -5.405313,-6.588387 -3.698372,-10.642779l84.398753,-198.158413c1.706941,-4.054392 6.069124,-5.777509 9.957156,-3.953032c3.793202,1.824476 5.405313,6.588387 3.698372,10.642779l-84.303923,198.158413c-1.327621,2.939434 -4.077693,4.662551 -6.922594,4.662551z"/>
<path stroke="null" id="svg_14" fill="#0276F7" d="m591.846924,375.062866c-2.750072,0 -5.405313,-1.621757 -6.732934,-4.459831c-1.801771,-3.953032 -0.28449,-8.716943 3.413882,-10.642779l129.253371,-67.302908l-365.759539,-178.089172l20.862613,208.091673l133.994874,-64.262114c3.698372,-1.824476 8.155385,0 9.862326,4.054392c1.706941,4.054392 0,8.716943 -3.793202,10.541419l-143.38305,68.823305c-2.181091,1.013598 -4.646673,0.912238 -6.827764,-0.405439c-2.086261,-1.317677 -3.413882,-3.547593 -3.698372,-6.081588l-23.328195,-233.026185c-0.28449,-2.838074 0.853471,-5.676149 3.034562,-7.297906c2.181091,-1.621757 5.025993,-2.027196 7.491575,-0.810878l392.217126,190.961867c2.655242,1.317677 4.362183,4.054392 4.362183,7.196546c0,3.142154 -1.612111,5.980228 -4.172523,7.297906l-143.57271,74.600814c-1.043131,0.608159 -2.181091,0.810878 -3.224222,0.810878zm-283.921198,78.959286c-3.603542,0 -6.827764,-2.838074 -7.396745,-6.791107c-0.56898,-4.358471 2.181091,-8.412864 6.258784,-9.122382l199.617275,-31.826978c4.077693,-0.608159 7.870895,2.331275 8.534705,6.689747c0.56898,4.358471 -2.181091,8.412864 -6.258784,9.122382l-199.617275,31.826978c-0.47415,0.10136 -0.853471,0.10136 -1.137961,0.10136z"/>
</g>
<text stroke="null" font-style="italic" transform="matrix(6.577099502228161,0,0,7.449448263868419,-1073.2057632249744,-908.8606073938396) " xml:space="preserve" text-anchor="start" font-family="Arvo, sans-serif" font-size="24" id="svg_16" y="177.898525" x="178.621382" stroke-width="0" fill="#D4E4FC">5G</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -1,7 +1,7 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch'; import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils'; import { parseObjLineToHump } from '@/utils/parse-utils';
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr, YYYY_MM_DD_HH_MM_SS } from '@/utils/date-utils';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
/** /**
@@ -122,7 +122,7 @@ export function updateConfirm(data: Record<string, any>) {
const userName = useUserStore().userName; const userName = useUserStore().userName;
let finalData = { let finalData = {
alarm: { alarm: {
ack_time: parseDateToStr(time), ack_time: parseDateToStr(time, YYYY_MM_DD_HH_MM_SS),
ack_user: userName, ack_user: userName,
ack_state: '1', ack_state: '1',
}, },
@@ -145,7 +145,7 @@ export function cancelConfirm(data: (string | number)[]) {
const userName = useUserStore().userName; const userName = useUserStore().userName;
let finalData = { let finalData = {
alarm: { alarm: {
ack_time: parseDateToStr(time), ack_time: parseDateToStr(time, YYYY_MM_DD_HH_MM_SS),
ack_user: '', ack_user: '',
ack_state: '0', ack_state: '0',
}, },
@@ -216,7 +216,7 @@ export function clearAlarm(data: Record<string, any>) {
const userName = useUserStore().userName; const userName = useUserStore().userName;
let finalData = { let finalData = {
data: { data: {
clear_time: parseDateToStr(time), clear_time: parseDateToStr(time, YYYY_MM_DD_HH_MM_SS),
clear_type: '2', clear_type: '2',
alarm_status: '0', alarm_status: '0',
clear_user: userName, clear_user: userName,

View File

@@ -1,8 +1,6 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch'; import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils'; import { parseObjLineToHump } from '@/utils/parse-utils';
import { parseDateToStr } from '@/utils/date-utils';
import useUserStore from '@/store/modules/user';
/** /**
* 查询列表 * 查询列表

View File

@@ -63,7 +63,11 @@ export async function getAlarmSet() {
} }
} }
if (Object.keys(resultData).length === 0) { if (Object.keys(resultData).length === 0) {
return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_ERROR[language], data: {} }; return {
code: RESULT_CODE_ERROR,
msg: RESULT_MSG_ERROR[language],
data: {},
};
} }
return { return {
code: RESULT_CODE_SUCCESS, code: RESULT_CODE_SUCCESS,
@@ -117,7 +121,11 @@ export async function updateAlarmSet(data: Record<string, any>) {
} }
// 无变更时 // 无变更时
if (resultNum === 0) { if (resultNum === 0) {
return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_ERROR[language], data: 0 }; return {
code: RESULT_CODE_ERROR,
msg: RESULT_MSG_ERROR[language],
data: 0,
};
} }
return { return {
code: RESULT_CODE_SUCCESS, code: RESULT_CODE_SUCCESS,
@@ -166,11 +174,10 @@ export async function getForwardSet() {
*/ */
export async function updateForwardSet(data: Record<string, any>) { export async function updateForwardSet(data: Record<string, any>) {
// return false; // return false;
console.log(data) let obj: any = [
let obj:any=[ { interface: 'Email', to_user: data.emailObj },
{interface:"Email",to_user:data.emailObj}, { interface: 'SMS', to_user: data.smsObj },
{interface:"SMS",to_user:data.smsObj} ];
]
const result = await request({ const result = await request({
url: `/api/rest/databaseManagement/v1/omc_db/config?WHERE=config_tag='forwardAlarm'`, url: `/api/rest/databaseManagement/v1/omc_db/config?WHERE=config_tag='forwardAlarm'`,
method: 'put', method: 'put',

View File

@@ -1,7 +1,7 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch'; import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils'; import { parseObjLineToHump } from '@/utils/parse-utils';
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr, YYYY_MM_DD_HH_MM_SS } from '@/utils/date-utils';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
/** /**
@@ -79,7 +79,7 @@ export function updateConfirm(data: Record<string, any>) {
const userName = useUserStore().userName; const userName = useUserStore().userName;
let finalData = { let finalData = {
alarm: { alarm: {
ack_time: parseDateToStr(time), ack_time: parseDateToStr(time, YYYY_MM_DD_HH_MM_SS),
ack_user: userName, ack_user: userName,
ack_state: '1', ack_state: '1',
}, },
@@ -101,7 +101,7 @@ export function cancelConfirm(data: (string | number)[]) {
var time = new Date(); var time = new Date();
let finalData = { let finalData = {
alarm: { alarm: {
ack_time: parseDateToStr(time), ack_time: parseDateToStr(time, YYYY_MM_DD_HH_MM_SS),
ack_user: '', ack_user: '',
ack_state: '0', ack_state: '0',
}, },

View File

@@ -1,3 +1,5 @@
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
import { sessionGet } from '@/utils/cache-session-utils';
import { request } from '@/plugins/http-fetch'; import { request } from '@/plugins/http-fetch';
/** /**
@@ -51,3 +53,43 @@ export function delFile(query: Record<string, any>) {
params: query, params: query,
}); });
} }
/**
* 更新FTP信息
* @param data 数据
* @returns object
*/
export function updateFTPInfo(data: Record<string, any>) {
return request({
url: `/lm/table/ftp`,
method: 'post',
data: data,
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
});
}
/**
* 获取FTP信息
* @param data 数据
* @returns object
*/
export function getFTPInfo() {
return request({
url: `/lm/table/ftp`,
method: 'get',
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
});
}
/**
* 发送FTP文件
* @param data 数据
* @returns object
*/
export function putFTPInfo(filePath: string, fileName: string) {
return request({
url: `/lm/table/ftp`,
method: 'put',
data: { filePath, fileName },
});
}

View File

@@ -1,3 +1,5 @@
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
import { sessionGet } from '@/utils/cache-session-utils';
import { request } from '@/plugins/http-fetch'; import { request } from '@/plugins/http-fetch';
// 登录方法 // 登录方法
@@ -7,7 +9,7 @@ export function login(data: Record<string, string>) {
method: 'post', method: 'post',
data: data, data: data,
whithToken: false, whithToken: false,
crypto: true, crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
}); });
} }
@@ -22,7 +24,7 @@ export function register(data: Record<string, any>) {
method: 'post', method: 'post',
data: data, data: data,
whithToken: false, whithToken: false,
crypto: true, crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
}); });
} }

View File

@@ -1,3 +1,5 @@
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
import { sessionGet } from '@/utils/cache-session-utils';
import { request } from '@/plugins/http-fetch'; import { request } from '@/plugins/http-fetch';
/** /**
@@ -36,7 +38,7 @@ export function addNeInfo(data: Record<string, any>) {
url: `/ne/info`, url: `/ne/info`,
method: 'post', method: 'post',
data: data, data: data,
crypto: true, crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
timeout: 30_000, timeout: 30_000,
}); });
} }
@@ -51,7 +53,7 @@ export function updateNeInfo(data: Record<string, any>) {
url: `/ne/info`, url: `/ne/info`,
method: 'put', method: 'put',
data: data, data: data,
crypto: true, crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
timeout: 30_000, timeout: 30_000,
}); });
} }

View File

@@ -10,6 +10,7 @@ export function listAMFDataUE(query: Record<string, any>) {
url: '/neData/amf/ue/list', url: '/neData/amf/ue/list',
method: 'get', method: 'get',
params: query, params: query,
timeout: 60_000,
}); });
} }
@@ -40,3 +41,90 @@ export function exportAMFDataUE(data: Record<string, any>) {
timeout: 60_000, timeout: 60_000,
}); });
} }
/**
* AMF-接入基站信息列表
* @param query 查询参数 neId=001&id=1
* @returns object
*/
export function listAMFNblist(query: Record<string, any>) {
return request({
url: '/neData/amf/nb/list',
method: 'get',
params: query,
timeout: 60_000,
});
}
/**
* AMF-接入基站状态信息列表
* @param query 查询参数 neId=001&state=1
* @returns object
*/
export function listAMFNbStatelist(query: Record<string, any>) {
return request({
url: '/neData/amf/nb/list-cfg',
method: 'get',
params: query,
timeout: 60_000,
});
}
/**
* AMF-接入基站状态信息新增
* @param neId 网元ID
* @param data 数据 { "index": 1, "name": "Gnb", "address": "192.168.8.1", "position": "Area-B" }
* @returns object
*/
export function addAMFNbState(neId: string, data: Record<string, any>) {
return request({
url: `/ne/config/data`,
method: 'post',
data: {
neType: 'AMF',
neId: neId,
paramName: 'gnbList',
paramData: data,
loc: `${data.index}`,
},
});
}
/**
* AMF-接入基站状态信息修改
* @param neId 网元ID
* @param data 数据 { "index": 1, "name": "Gnb", "address": "192.168.8.1", "position": "Area-B" }
* @returns object
*/
export function editAMFNbState(neId: string, data: Record<string, any>) {
return request({
url: `/ne/config/data`,
method: 'put',
data: {
neType: 'AMF',
neId: neId,
paramName: 'gnbList',
paramData: data,
loc: `${data.index}`,
},
});
}
/**
* AMF-接入基站状态信息删除
* @param neId 网元ID
* @param index 数据index
* @returns object
*/
export function delAMFNbState(neId: string, index: string | number) {
return request({
url: `/ne/config/data`,
method: 'delete',
params: {
neType: 'AMF',
neId: neId,
paramName: 'gnbList',
loc: `${index}`,
},
});
}

View File

@@ -10,6 +10,7 @@ export function listIMSDataCDR(query: Record<string, any>) {
url: '/neData/ims/cdr/list', url: '/neData/ims/cdr/list',
method: 'get', method: 'get',
params: query, params: query,
timeout: 60_000,
}); });
} }

View File

@@ -10,6 +10,7 @@ export function listMMEDataUE(query: Record<string, any>) {
url: '/neData/mme/ue/list', url: '/neData/mme/ue/list',
method: 'get', method: 'get',
params: query, params: query,
timeout: 60_000,
}); });
} }
@@ -40,3 +41,90 @@ export function exportMMEDataUE(data: Record<string, any>) {
timeout: 60_000, timeout: 60_000,
}); });
} }
/**
* MME-接入基站信息列表
* @param query 查询参数 neId=001&id=1
* @returns object
*/
export function listMMENblist(query: Record<string, any>) {
return request({
url: '/neData/mme/nb/list',
method: 'get',
params: query,
timeout: 60_000,
});
}
/**
* MME-接入基站状态信息列表
* @param query 查询参数 neId=001&state=1
* @returns object
*/
export function listMMENbStatelist(query: Record<string, any>) {
return request({
url: '/neData/mme/nb/list-cfg',
method: 'get',
params: query,
timeout: 60_000,
});
}
/**
* MME-接入基站状态信息新增
* @param neId 网元ID
* @param data 数据 { "index": 1, "name": "Enb", "address": "192.168.8.1", "position": "Area-B" }
* @returns object
*/
export function addMMENbState(neId: string, data: Record<string, any>) {
return request({
url: `/ne/config/data`,
method: 'post',
data: {
neType: 'MME',
neId: neId,
paramName: 'enbList',
paramData: data,
loc: `${data.index}`,
},
});
}
/**
* MME-接入基站状态信息修改
* @param neId 网元ID
* @param data 数据 { "index": 1, "name": "Enb", "address": "192.168.8.1", "position": "Area-B" }
* @returns object
*/
export function editMMENbState(neId: string, data: Record<string, any>) {
return request({
url: `/ne/config/data`,
method: 'put',
data: {
neType: 'MME',
neId: neId,
paramName: 'enbList',
paramData: data,
loc: `${data.index}`,
},
});
}
/**
* MME-接入基站状态信息删除
* @param neId 网元ID
* @param index 数据index
* @returns object
*/
export function delMMENbState(neId: string, index: string | number) {
return request({
url: `/ne/config/data`,
method: 'delete',
params: {
neType: 'MME',
neId: neId,
paramName: 'enbList',
loc: `${index}`,
},
});
}

View File

@@ -0,0 +1,30 @@
import { request } from '@/plugins/http-fetch';
/**
* 历史记录列表
* @param query 查询参数
* @returns object
*/
export function listNBState(query: Record<string, any>) {
return request({
url: '/neData/nb-state/list',
method: 'get',
params: query,
timeout: 60_000,
});
}
/**
* 历史记录列表导出
* @param data 查询列表条件
* @returns object
*/
export function exportNBState(data: Record<string, any>) {
return request({
url: '/neData/nb-state/export',
method: 'post',
data,
responseType: 'blob',
timeout: 60_000,
});
}

43
src/api/neData/sgwc.ts Normal file
View File

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

View File

@@ -10,6 +10,7 @@ export function listSMFDataCDR(query: Record<string, any>) {
url: '/neData/smf/cdr/list', url: '/neData/smf/cdr/list',
method: 'get', method: 'get',
params: query, params: query,
timeout: 60_000,
}); });
} }
@@ -41,14 +42,27 @@ export function exportSMFDataCDR(data: Record<string, any>) {
}); });
} }
/**
* SMF-在线订阅用户数量
* @param query 查询参数
* @returns object
*/
export function listSMFSubNum(neId: string) {
return request({
url: '/neData/smf/sub/num',
method: 'get',
params: { neId },
});
}
/** /**
* SMF-在线订阅用户列表信息 * SMF-在线订阅用户列表信息
* @param query 查询参数 * @param query 查询参数
* @returns object * @returns object
*/ */
export function listSMFSubscribers(query: Record<string, any>) { export function listSMFSubList(query: Record<string, any>) {
return request({ return request({
url: '/neData/smf/subscribers', url: '/neData/smf/sub/list',
method: 'get', method: 'get',
params: query, params: query,
}); });

View File

@@ -10,6 +10,7 @@ export function listSMSCDataCDR(query: Record<string, any>) {
url: '/neData/smsc/cdr/list', url: '/neData/smsc/cdr/list',
method: 'get', method: 'get',
params: query, params: query,
timeout: 60_000,
}); });
} }

View File

@@ -0,0 +1,51 @@
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
import { sessionGet } from '@/utils/cache-session-utils';
import { request } from '@/plugins/http-fetch';
/**
* 获取下拉框数据
* @returns object
*/
export function getBakFile() {
return request({
url: '/ue/table/list',
method: 'get',
});
}
/**
* 获取对应类型的文件列表
* @param query 查询参数
* @returns object
*/
export function getBakFileList(query: Record<string, any>) {
return request({
url: '/ue/file/list',
method: 'get',
params: query,
});
}
/**
* 下载远端文件
* @param query 查询参数
* @returns object
*/
export function downFile(query: Record<string, any>) {
return request({
url: `/ue/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: `/ue/file/${query.fileName}`,
method: 'delete',
params: query,
});
}

View File

@@ -1,7 +1,4 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch'; import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
import { parseDateToStr } from '@/utils/date-utils';
/** /**
* 新 查询自定义指标数据 * 新 查询自定义指标数据
@@ -14,6 +11,7 @@ export async function listCustomData(query: Record<string, any>) {
url: `/pm/kpiC/report`, url: `/pm/kpiC/report`,
method: 'get', method: 'get',
params: query, params: query,
timeout: 60_000,
}); });
return result; return result;
} }

View File

@@ -1,7 +1,4 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch'; import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
import { parseDateToStr } from '@/utils/date-utils';
/** /**
* 查询自定义指标 * 查询自定义指标

View File

@@ -1,7 +1,6 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch'; import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils'; import { parseObjLineToHump } from '@/utils/parse-utils';
import { parseDateToStr } from '@/utils/date-utils';
/** /**
* 查询任务列表 * 查询任务列表

View File

@@ -1,120 +0,0 @@
import { request } from '@/plugins/http-fetch';
/**
* 保存为示例配置 (仅管理员操作)
* @param query 查询参数
* @returns object
*/
export function ptSaveAsDefault(neType: string, neid: string) {
return request({
url: `/pt/neConfigData/saveAsDefault`,
method: 'post',
data: { neType, neid },
});
}
/**
* 重置为示例配置 (仅学生/教师操作)
* @param query 查询参数
* @returns object
*/
export function ptResetAsDefault(neType: string) {
return request({
url: `/pt/neConfigData/resetAsDefault`,
method: 'post',
data: { neType },
});
}
/**
* 数据比较示例
* @param params 查询参数
* @returns object
*/
export function ptContrastAsDefault(params: Record<string, any>) {
return request({
url: `/pt/neConfigData/contrast`,
params,
method: 'get',
});
}
/**
* 配置数据导出Excel
* @param student 仅教师 student
* @returns object
*/
export function ptExport(student: string | undefined) {
return request({
url: `/pt/neConfigData/export`,
method: 'get',
params: { student },
responseType: 'blob',
timeout: 180_000,
});
}
/**
* 配置数据导出Excel (仅教师全量)
* @returns object
*/
export function ptExportAll() {
return request({
url: `/pt/neConfigData/export-all`,
method: 'get',
responseType: 'blob',
timeout: 180_000,
});
}
/**
* 网元参数配置信息
* @param params 数据 {neType,paramName}
* @returns object
*/
export function getPtNeConfigData(params: Record<string, any>) {
return request({
url: `/pt/neConfigData`,
params,
method: 'get',
});
}
/**
* 网元参数配置数据更新
* @param data 数据 {neType,paramName:"参数名",paramData:{参数},loc:"层级index仅array"}
* @returns object
*/
export function editPtNeConfigData(data: Record<string, any>) {
return request({
url: `/pt/neConfigData`,
method: 'put',
data: data,
});
}
/**
* 网元参数配置新增array
* @param data 数据 {neType,paramName:"参数名",paramData:{参数},loc:"层级index"}
* @returns object
*/
export function addPtNeConfigData(data: Record<string, any>) {
return request({
url: `/pt/neConfigData`,
method: 'post',
data: data,
});
}
/**
* 网元参数配置删除array
* @param params 数据 {neType,paramName:"参数名",loc:"层级index"}
* @returns object
*/
export function delPtNeConfigData(params: Record<string, any>) {
return request({
url: `/pt/neConfigData`,
method: 'delete',
params,
});
}

View File

@@ -1,53 +0,0 @@
import { request } from '@/plugins/http-fetch';
/**
* 班级学生列表 (仅教师操作)
* @param params 数据 {userName}
* @returns object
*/
export function getPtClassStudents(params?: Record<string, any>) {
return request({
url: `/pt/neConfigApply/students`,
params,
method: 'get',
});
}
/**
* 网元参数配置应用申请列表
* @param params 数据 {neType,paramName}
* @returns object
*/
export function getPtNeConfigApplyList(params: Record<string, any>) {
return request({
url: `/pt/neConfigApply/list`,
params,
method: 'get',
});
}
/**
* 网元参数配置应用申请提交(仅学生操作)
* @param data 数据 { "neType": "MME", "status": "1" }
* @returns object
*/
export function stuPtNeConfigApply(data: Record<string, any>) {
return request({
url: `/pt/neConfigApply`,
method: 'post',
data: data,
});
}
/**
* 网元参数配置应用申请状态变更(仅管理员/教师操作)
* @param data 数据 { "applyId": "1", "neType": "MME", "status": "3", "backInfo": "sgw参数错误" }
* @returns object
*/
export function updatePtNeConfigApply(data: Record<string, any>) {
return request({
url: `/pt/neConfigApply`,
method: 'put',
data: data,
});
}

View File

@@ -1,27 +0,0 @@
import { request } from '@/plugins/http-fetch';
/**
* 网元参数配置数据变更日志信息
* @param params 数据 {neType,paramName}
* @returns object
*/
export function getPtNeConfigDataLogList(params: Record<string, any>) {
return request({
url: `/pt/neConfigDataLog`,
params,
method: 'get',
});
}
/**
* 网元参数配置数据变更日志还原到数据
* @param data 数据 { "id": "1", "value": "old" }
* @returns object
*/
export function restorePtNeConfigDataLog(data: Record<string, any>) {
return request({
url: `/pt/neConfigDataLog/restore`,
method: 'put',
data: data,
});
}

View File

@@ -1,42 +0,0 @@
import { request } from '@/plugins/http-fetch';
/**
* 导入用户模板数据
* @param data 表单数据对象
* @returns object
*/
export function importData(data: FormData) {
return request({
url: '/pt/system/user/importData',
method: 'post',
data,
dataType: 'form-data',
timeout: 180_000,
});
}
/**
* 导入用户模板下载
* @returns bolb
*/
export function importTemplate() {
return request({
url: '/pt/system/user/importTemplate',
method: 'get',
responseType: 'blob',
});
}
/**
* 用户列表导出
* @param query 查询参数
* @returns bolb
*/
export function exportUser(query: Record<string, any>) {
return request({
url: '/pt/system/user/export',
method: 'post',
data: query,
responseType: 'blob',
});
}

View File

@@ -24,7 +24,7 @@ export function getNeFile(query: Record<string, any>) {
method: 'get', method: 'get',
params: query, params: query,
responseType: 'blob', responseType: 'blob',
timeout: 180_000, timeout: 600_000,
}); });
} }
@@ -35,7 +35,7 @@ export function getNeDirZip(data: Record<string, any>) {
method: 'get', method: 'get',
params: data, params: data,
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 600_000,
}); });
} }

View File

@@ -1,71 +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 listTraceData(query: Record<string, any>) {
let totalSQL = 'select count(*) as total from trace_data where 1=1 ';
let rowsSQL = 'select * from trace_data where 1=1 ';
// 查询
let querySQL = '';
if (query.imsi) {
querySQL += ` and imsi like '%${query.imsi}%' `;
}
if (query.msisdn) {
querySQL += ` and msisdn like '%${query.msisdn}%' `;
}
// 分页
const pageNum = (query.pageNum - 1) * query.pageSize;
const limtSql = ` limit ${pageNum},${query.pageSize} `;
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/omc_db/trace_data`,
method: 'get',
params: {
totalSQL: totalSQL + querySQL,
rowsSQL: rowsSQL + querySQL + 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_data'];
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;
}
/**
* 信令数据解析HTML
* @param id 任务ID
* @returns
*/
export function getTraceRawInfo(id: Record<string, string>) {
return request({
url: `/api/rest/traceManagement/v1/decMessage/${id}`,
method: 'get',
responseType: 'text',
});
}

View File

@@ -62,3 +62,18 @@ export function packetKeep(taskNo: string, duration: number = 120) {
data: { taskNo, duration }, data: { taskNo, duration },
}); });
} }
/**
* 信令跟踪文件
* @param taskNo 对象
* @returns object
*/
export function packetPCAPFile(taskNo: string) {
return request({
url: '/trace/packet/filePull',
method: 'get',
params: { taskNo },
responseType: 'blob',
timeout: 680_000,
});
}

View File

@@ -1,6 +1,4 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch'; import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
/** /**
* 查询跟踪任务列表 * 查询跟踪任务列表
@@ -76,29 +74,31 @@ export function filePullTask(traceId: string) {
method: 'get', method: 'get',
params: { traceId }, params: { traceId },
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 600_000,
}); });
} }
/** /**
* 获取网元跟踪接口列表 * 跟踪任务数据列表
* @param query 查询参数
* @returns object * @returns object
*/ */
export async function getNeTraceInterfaceAll() { export async function listTraceData(query: Record<string, any>) {
// 发起请求 return request({
const result = await request({ url: '/trace/data/list',
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/ne_info`, method: 'get',
params: query,
});
}
/**
* 查询跟踪任务数据信息
* @param id ID
* @returns object
*/
export async function getTraceData(id: string | number) {
return request({
url: `/trace/data/${id}`,
method: 'get', 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

@@ -78,6 +78,6 @@ export function filePullTaskHLR(query: Record<string, any>) {
method: 'get', method: 'get',
params: query, params: query,
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 600_000,
}); });
} }

View File

@@ -1,5 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'; import { message } from 'ant-design-vue/es';
import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { FitAddon } from '@xterm/addon-fit'; import { FitAddon } from '@xterm/addon-fit';
import { Terminal } from '@xterm/xterm'; import { Terminal } from '@xterm/xterm';
import '@xterm/xterm/css/xterm.css'; import '@xterm/xterm/css/xterm.css';
@@ -31,8 +32,91 @@ const terminalDom = ref<HTMLElement | undefined>(undefined);
/**终端输入实例对象 */ /**终端输入实例对象 */
const terminal = ref<any>(null); const terminal = ref<any>(null);
/**终端输入命令 */ /**终端输入文字状态 */
const terminalCmd = ref<string>(''); const terminalState = reactive<{
/**输入值 */
text: string;
/**历史 */
history: {
label?: string;
value: string;
}[];
}>({
text: '',
history: [
{
value: 'info server',
},
{
value: 'info replication',
},
{
value: 'keys ausf:*',
},
{
value: 'quit',
},
],
});
/**自动完成根据输入项进行筛选 */
function fnAutoCompleteFilter(input: string, option: any) {
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0;
}
/**自动完成按键触发 */
function fnAutoCompleteKeydown(evt: KeyboardEvent) {
if (evt.key === 'Enter') {
// 阻止默认的换行行为
evt.preventDefault();
// 按下 Shift + Enter 键时换行
if (evt.shiftKey && evt.target) {
// 插入换行符
const textarea = evt.target as HTMLInputElement;
const start = textarea.selectionStart || 0;
const end = textarea.selectionEnd || 0;
const text = textarea.value;
textarea.value = text.substring(0, start) + '\n' + text.substring(end);
terminalState.text = textarea.value;
// 更新光标位置
textarea.selectionStart = textarea.selectionEnd = start + 1;
} else {
// ws未连接
if (ws.state() !== WebSocket.OPEN) {
message.error('disconnected');
return;
}
// 输入历史
const cmdStr = terminalState.text.trim().replace(/\n/g, '\r\n');
const hisIndex = terminalState.history.findIndex(
item => item.value === cmdStr
);
if (hisIndex === -1) {
terminalState.history.push({
value: cmdStr,
});
}
// 发送文本
terminal.value.scrollToBottom();
terminal.value.writeln('\r\n> ' + cmdStr);
ws.send({
requestId: `redis_${props.hostId}`,
type: 'redis',
data: `${cmdStr}\r\n`,
});
terminalState.text = '';
// 退出登录
if (['q', 'quit', 'exit'].includes(cmdStr)) {
setTimeout(() => {
ws.close();
}, 1000);
}
}
}
}
/**终端输入渲染 */ /**终端输入渲染 */
function handleRanderXterm(container: HTMLElement | undefined) { function handleRanderXterm(container: HTMLElement | undefined) {
@@ -49,47 +133,13 @@ function handleRanderXterm(container: HTMLElement | undefined) {
scrollback: 1000, scrollback: 1000,
scrollSensitivity: 15, scrollSensitivity: 15,
tabStopWidth: 4, tabStopWidth: 4,
disableStdin: false, // 禁止输入 disableStdin: true, // 禁止输入
}); });
// 挂载 // 挂载
xterm.open(container); xterm.open(container);
// 自适应尺寸 // 自适应尺寸
const fitAddon = new FitAddon(); const fitAddon = new FitAddon();
xterm.loadAddon(fitAddon); xterm.loadAddon(fitAddon);
// 终端输入字符按键监听
xterm.onKey(({ key, domEvent }) => {
// console.log(key, domEvent);
// 单键输入
switch (domEvent.key) {
case 'Enter':
const cmdStr = terminalCmd.value.trim();
// 发送文本
terminal.value.scrollToBottom();
terminal.value.writeln('\r\n');
ws.send({
requestId: `redis_${props.hostId}`,
type: 'redis',
data: `${cmdStr}\r\n`,
});
terminalCmd.value = '';
// 退出登录
if ('quit' === cmdStr) {
setTimeout(() => {
ws.close();
}, 1000);
}
break;
case 'Backspace':
// 处理退格键,删除最后一个字符
xterm.write('\b \b');
break;
default:
xterm.write(key);
terminalCmd.value += key;
return;
}
});
// 创建 ResizeObserver 实例 // 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => { var observer = new ResizeObserver(entries => {
fitAddon.fit(); fitAddon.fit();
@@ -213,7 +263,21 @@ defineExpose({
<template> <template>
<div class="terminal"> <div class="terminal">
<div ref="terminalDom" :id="id" class="terminal"></div> <div ref="terminalDom" style="height: calc(100% - 36px)" :id="id"></div>
<a-auto-complete
v-model:value="terminalState.text"
:dropdown-match-select-width="500"
style="width: 100%"
:options="terminalState.history"
:filter-option="fnAutoCompleteFilter"
:defaultActiveFirstOption="false"
>
<a-textarea
:auto-size="{ minRows: 1, maxRows: 6 }"
placeholder="Execute command. Shift+Enter to line feed, Enter to send"
@keypress="fnAutoCompleteKeydown"
/>
</a-auto-complete>
</div> </div>
</template> </template>

View File

@@ -80,16 +80,16 @@ function fnAutoCompleteFilter(input: string, option: any) {
} }
/**自动完成按键触发 */ /**自动完成按键触发 */
function fnAutoCompleteKeydown(evt: any) { function fnAutoCompleteKeydown(evt: KeyboardEvent) {
if (evt.key === 'Enter') { if (evt.key === 'Enter') {
// 阻止默认的换行行为 // 阻止默认的换行行为
evt.preventDefault(); evt.preventDefault();
// 按下 Shift + Enter 键时换行 // 按下 Shift + Enter 键时换行
if (evt.shiftKey) { if (evt.shiftKey && evt.target) {
// 插入换行符 // 插入换行符
const textarea = evt.target; const textarea = evt.target as HTMLInputElement;
const start = textarea.selectionStart; const start = textarea.selectionStart || 0;
const end = textarea.selectionEnd; const end = textarea.selectionEnd || 0;
const text = textarea.value; const text = textarea.value;
textarea.value = text.substring(0, start) + '\n' + text.substring(end); textarea.value = text.substring(0, start) + '\n' + text.substring(end);
terminalState.text = textarea.value; terminalState.text = textarea.value;
@@ -121,7 +121,7 @@ function fnAutoCompleteKeydown(evt: any) {
type: 'telnet', type: 'telnet',
data: `${cmdStr}\r\n`, data: `${cmdStr}\r\n`,
}); });
terminalState.text = ' '; terminalState.text = '';
// 退出登录 // 退出登录
if (['q', 'quit', 'exit'].includes(cmdStr)) { if (['q', 'quit', 'exit'].includes(cmdStr)) {
@@ -296,11 +296,12 @@ defineExpose({
style="width: 100%" style="width: 100%"
:options="terminalState.history" :options="terminalState.history"
:filter-option="fnAutoCompleteFilter" :filter-option="fnAutoCompleteFilter"
@keydown="fnAutoCompleteKeydown" :defaultActiveFirstOption="false"
> >
<a-textarea <a-textarea
:auto-size="{ minRows: 1, maxRows: 6 }" :auto-size="{ minRows: 1, maxRows: 6 }"
placeholder="Execute command. Shift+Enter to line feed, Enter to send" placeholder="Execute command. Shift+Enter to line feed, Enter to send"
@keypress="fnAutoCompleteKeydown"
/> />
</a-auto-complete> </a-auto-complete>
</div> </div>

View File

@@ -1,3 +1,6 @@
/**会话缓存-接口加密 */
export const CACHE_SESSION_CRYPTO_API = 'cache:session:cryptoApi';
/**会话缓存-网络请求 */ /**会话缓存-网络请求 */
export const CACHE_SESSION_FATCH = 'cache:session:fatch'; export const CACHE_SESSION_FATCH = 'cache:session:fatch';

View File

@@ -4,6 +4,7 @@ export const NE_TYPE_LIST = [
'IMS', 'IMS',
'AMF', 'AMF',
'AUSF', 'AUSF',
'UDR',
'UDM', 'UDM',
'SMF', 'SMF',
'PCF', 'PCF',
@@ -19,6 +20,8 @@ export const NE_TYPE_LIST = [
'SMSF', 'SMSF',
'CBC', 'CBC',
'CHF', 'CHF',
'HLR',
'SGWC',
]; ];
/** /**

View File

@@ -16,7 +16,6 @@ export default {
errorFields: 'Please fill in the required information in {num} correctly!', errorFields: 'Please fill in the required information in {num} correctly!',
tablePaginationTotal: 'Total {total} items', tablePaginationTotal: 'Total {total} items',
noData: "No Data", noData: "No Data",
zebra:'Tabular zebra pattern',
ok: 'Ok', ok: 'Ok',
cancel: 'Cancel', cancel: 'Cancel',
close: 'Close', close: 'Close',
@@ -131,7 +130,7 @@ export default {
}, },
LockScreen: { LockScreen: {
inputPlacePwd:'Lock Screen Password', inputPlacePwd:'Lock Screen Password',
validSucc:'Validation Passed', enter:'Enter',
validError:'Validation Failure', validError:'Validation Failure',
backLogin:'Logout to Relogin', backLogin:'Logout to Relogin',
backReload:'Restarting now, please wait...', backReload:'Restarting now, please wait...',
@@ -222,12 +221,11 @@ export default {
capability: 'Capability', capability: 'Capability',
serialNum: 'Serial Number', serialNum: 'Serial Number',
expiryDate: 'Expiry Date', expiryDate: 'Expiry Date',
neStatus: 'NE status is abnormal', neStatus: 'Status Abnormal',
runStatus:'Running Status', runStatus: 'Runing Status',
mark:'Brief Information', mark:'Information',
object:'Object', object:'Object',
versionNum:'Version', versionNum:'Version',
systemStatus:'Status',
realNeStatus:'Status', realNeStatus:'Status',
reloadTime:'Refresh Time', reloadTime:'Refresh Time',
Critical:'Critical', Critical:'Critical',
@@ -341,7 +339,7 @@ export default {
profile: { profile: {
phonenumber: "Phone", phonenumber: "Phone",
email: "Email", email: "Email",
deptName: "Class", deptName: "Department",
postGroup: "Possession of posts", postGroup: "Possession of posts",
roleGroup: "Ownership", roleGroup: "Ownership",
loginIp: "Log in", loginIp: "Log in",
@@ -351,184 +349,6 @@ export default {
description: "No data yet, try refreshing", description: "No data yet, try refreshing",
}, },
}, },
configManage: {
neManage: {
addNe:'Add Network Element',
delSure:'Confirm deleting the data item with network element name {msg}',
editNe:'Edit Network Element',
exportSure:'Confirm exporting the configuration information with the network element name {msg}',
exportTip:'Export successful, please go to backup management for download',
getInfo:'Failed to get network element information',
neType:'NE Type',
neTypePlease: 'Select network element type',
neId:'NE ID',
neName:'NE Name',
neTypeTip:'Fill in the type of network element created, such as:SMF',
uid:'RM UID',
uidTip:'Please enter a unique resource identifier',
ip:'IP Address',
mac:'NE MAC address',
macTip:'Able to locate the physical address (MAC) of the network element',
port:'Port',
portTip:'Maximum range 0~65535',
pvflag:'PV Flag',
pnf:'Physical Network Element',
vnf:'Virtual Network Element',
province:'Region',
vendorName:'Vendor Name',
dn:'Network Identification',
reload: 'Reload',
restart: 'Restart',
totalSure:'Confirm the network element with {operator} network element name {msg}',
stop: 'Stop',
start: 'Start',
log: 'Logs',
export: 'Export',
import: 'Import',
fileForm:'File Source',
selectPlease:'Please select the source of the import file',
server:'Server File',
local:'Local File',
fileSelect:'Please select the current import file',
sync:'Synchronize to NE',
open:'Open',
close:'Close',
addFail:'Add failed',
operFail:'Operation Failed'
},
backupManage: {
setBackupTask: 'Set automatic backup time',
neTypePlease: 'Query network element type',
neType: 'NE Type',
neID: 'NE ID',
fileName: 'File Name',
createAt: 'Create at',
remark:'Remark',
edit:'Edit Backup File',
totalSure:'Confirm that {oper} records item number {id}?',
},
softwareManage: {
sendBtn: 'Distribute',
runBtn: 'Activate',
backBtn: 'Rollback',
historyBtn: 'Distribution Record',
neTypePlease: 'Select network element type',
neType: 'NE Type',
fileName: 'File Name',
version: 'Version',
versionPlease: 'Version number cannot be empty',
updateTime: 'Uploaded Time',
description: 'Description',
deleteTip: 'Are you sure to delete the data item with software [{fileName}]?',
downloadTip: 'Are you sure to download the data item with software [{fileName}]?',
updateComment: 'Comment',
updateCommentPlease: 'Please enter the software description',
updateFile: 'Software File',
updateFilePlease: 'Please upload the updated software file',
verifyFile: 'Verify File',
selectFile: 'SELECT FILE',
sendTitle: 'Distribute software version',
sendContent: 'Are you sure to send the file with the software package [{fileName}] to the corresponding network element?',
runTitle: 'Activate software version',
runContent: 'Are you sure to activate the software version of [{fileName}] that has been issued to the corresponding network element?',
backTitle: 'Fallback software version',
backContent: 'Confirm that the software version of [{fileName}] has been issued for the corresponding network element rollback?',
neId: 'Corresponding network element',
neIdPlease: 'Please select the corresponding network element',
versions:'Version',
upVersions:'Version before upgrade',
backVersions:'Version before rollback',
status:'Status',
letUpTime:'Activation time',
createTime:'Creation time',
onlyAble:'Only upload file format {fileText} is supported',
nullVersion:'There is no rollback version for the current network element.',
},
license: {
neTypePlease: 'Select network element type',
neType: 'NE Type',
serialNum: 'Serial Num',
createTime: 'Time',
comment: 'Description',
updateComment: 'License Description',
updateCommentPlease: 'Please enter a license description',
updateFile: 'License File',
updateFilePlease: 'Please upload and update the License file',
selectFile: 'SELECT FILE',
neId: 'NE ID',
neIdPlease: 'Please select the corresponding network element',
},
configParam:{
dataNull:'No configuration item data yet',
editSuss:'Modification successful',
editFail:'Edit failed',
Unable:'Illegal operation of attribute value',
delSure:'Confirm to delete the data item with Index [{value}]?',
addSuss:'Add successfully',
addFail:'Add failed',
delArraySure:'Confirm to delete the data item with {arrayChildTitle} Index as [{value}]?',
parUnable:'The parameter value is not within the reasonable range',
ipv4Tip:'Not a legal IPV4 address',
ipv6Tip:'Not a legal IPV6 address',
enumTip:'Not a reasonable enumeration value',
boolTip:'Not a reasonable Boolean value',
default:'The input value is of unknown type',
reloadSuss:'Network element reloading completed',
reloadFail:'Network element reloading failed',
neNUll:'No network element list data yet',
reload:'Reload',
post:'Submit',
editSure:'Are you sure you want to update this attribute value? ',
arraryEdit:'Are you sure to submit the record whose updated Index is [{value}]? ',
addSure:'Are you sure to submit the new record of Index: [{value}]? '
},
configParamForm: {
treeTitle: "Navigation Configuration",
treeSelectTip: "Select configuration item information in the left configuration navigation!",
neType: 'NE Type',
neTypePleace: "Please select the network element type",
noConfigData: "No data on configuration items",
updateValue: "[ {num} ] parameter value modified successfully.",
updateValueErr: "Attribute value modification failure",
updateItem: "Modify Index to {num}.",
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",
ptDiff: 'Comparison Example',
ptDiffExample: 'Example Configuration',
ptDiffSelf: 'Current Individuals',
ptDiffLoad: 'Load More',
ptDiffMerge: 'Comparative Differences',
ptDiffRest: 'Restore this version',
ptHistory: 'History',
ptReset: 'Reset To Example',
ptResetTip: 'Confirmed to reset to the sample configuration?',
ptLoad: 'Load Current Configuration',
ptLoadTip: 'Confirm that you want to load the current network element configuration?',
ptExport: "Export Excel",
ptExportTip: "Confirm that you want to export the network element configuration data to an Excel file?",
ptExportAll: "导出所有学生配置",
ptApplyShow: 'View Student',
ptApply: 'request',
ptApplyNE: 'Application To NE',
ptApplyStu: 'Application To {ne}',
ptApplyStuTip: 'Confirm that you want to initiate a Configure Application to {ne} request to the teacher?',
ptApplyStuRack: 'Return Request',
ptApplyStuNE: 'Application Request',
},
},
dashboard: { dashboard: {
overview:{ overview:{
title: "Core Network Dashboard", title: "Core Network Dashboard",
@@ -600,14 +420,18 @@ export default {
resultFail: "Fail", 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', chargingID: 'Charging ID',
smfSubscriptionIDData: 'Subscription ID Data', smfSubscriptionIDData: 'Subscription ID Data',
smfSubscriptionIDType: 'Subscription ID Type', smfSubscriptionIDType: 'Subscription ID Type',
smfDataVolumeUplink: 'Data Volume Uplink', smfDataVolumeUplink: 'Data Volume Uplink',
smfDataVolumeDownlink: 'Data Volume Downlink', smfDataVolumeDownlink: 'Data Volume Downlink',
smfDataTotalVolume: 'Data Total Volume', smfDataTotalVolume: 'Data Total Volume',
smfDuration: 'Duration', durationTime: 'Duration',
smfInvocationTime: 'Invocation Time', invocationTime: 'Invocation Time',
sgwcServedIMSI: 'IMSI',
sgwcServedMSISDN: 'MSISDN',
sgwcVolumeGPRSUplink: 'GPRS Uplink',
sgwcVolumeGPRSDownlink: 'GPRS Downlink',
}, },
ue: { ue: {
eventType: "Event Type", eventType: "Event Type",
@@ -679,7 +503,7 @@ export default {
delTip: 'Confirm deletion of network element information data items?', delTip: 'Confirm deletion of network element information data items?',
oam: { oam: {
title: 'OAM Configuration', title: 'OAM Configuration',
sync: 'Sync to NE', restart: 'Restart NE',
oamEnable: 'Service', oamEnable: 'Service',
oamPort: 'Port', oamPort: 'Port',
snmpEnable: 'Service', snmpEnable: 'Service',
@@ -782,6 +606,7 @@ export default {
upgradeDone: 'Update complete, service being reloaded', upgradeDone: 'Update complete, service being reloaded',
upgradeFail: 'The update fails, please check whether the software file exists and whether the service terminal environment is available!', upgradeFail: 'The update fails, please check whether the software file exists and whether the service terminal environment is available!',
upgradeModal: 'Network Element Version Updates', upgradeModal: 'Network Element Version Updates',
noPath: 'Package File Not Found',
}, },
neLicense: { neLicense: {
status: "License Status", status: "License Status",
@@ -805,7 +630,9 @@ export default {
treeSelectTip: "Select configuration item information in the left configuration navigation!", treeSelectTip: "Select configuration item information in the left configuration navigation!",
neType: 'NE Type', neType: 'NE Type',
neTypePleace: "Please select the network element type", neTypePleace: "Please select the network element type",
neIdSyncPleace: "Please select the synchronized network element",
noConfigData: "No data on configuration items", noConfigData: "No data on configuration items",
noConfigdDisabled: "The configuration item is not normal",
updateValue: "[ {num} ] parameter value modified successfully.", updateValue: "[ {num} ] parameter value modified successfully.",
updateValueErr: "Attribute value modification failure", updateValueErr: "Attribute value modification failure",
updateItem: "Modify Index to {num}.", updateItem: "Modify Index to {num}.",
@@ -866,7 +693,7 @@ export default {
installSourceUpload: 'New Upload', installSourceUpload: 'New Upload',
installSelect: 'Select Record', installSelect: 'Select Record',
installUpload: 'Upload File', installUpload: 'Upload File',
installText: 'Installed', installText: 'Install',
licenseTitle: "Licenses", licenseTitle: "Licenses",
licenseDesc: "Network element service authorization certification", licenseDesc: "Network element service authorization certification",
licenseResultTitle: "Whether to authorize activation immediately", licenseResultTitle: "Whether to authorize activation immediately",
@@ -879,6 +706,30 @@ export default {
licenseTip2: '2. Clicking [Finish] will end the installation process.', licenseTip2: '2. Clicking [Finish] will end the installation process.',
}, },
}, },
neData: {
baseStation: {
list: "List",
topology: "Topology",
nbName: "RanNodeName",
ueNum: "UE Number",
topologyTitle: "Radio State Graph",
name: "Name",
namePlease: "text content length 0~64",
position: "Position",
positionPlease: "location description. Prohibition of spaces, length of text content 0-64",
address: "IP Address",
addressPlease: "text content length 0~64",
state: "State",
online: "Online",
offline: "Offline",
time: "Change Time",
addRadio: "Add Radio Info",
editRadio: "Edit Radio Info",
history: "Status History",
exportTip: "Confirm exporting xlsx table files based on search criteria?",
importDataEmpty: "Imported data is empty",
},
},
neUser: { neUser: {
auth: { auth: {
authInfo:' Authentication Info', authInfo:' Authentication Info',
@@ -889,6 +740,7 @@ export default {
checkExport : 'Check Export', checkExport : 'Check Export',
checkExportConfirm: 'Confirm exporting the checked authenticated user data?', checkExportConfirm: 'Confirm exporting the checked authenticated user data?',
import: 'Import', import: 'Import',
importFail: 'Failure Record',
loadDataConfirm: 'Are you sure you want to reload the data?', loadDataConfirm: 'Are you sure you want to reload the data?',
loadData: 'Load Data', loadData: 'Load Data',
loadDataTip: 'Successfully fetched loaded data: {num} items, the system is internally updating the data, it will take about {timer} seconds, please wait!!!!!.', loadDataTip: 'Successfully fetched loaded data: {num} items, the system is internally updating the data, it will take about {timer} seconds, please wait!!!!!.',
@@ -917,6 +769,7 @@ export default {
checkExport : 'Check Export', checkExport : 'Check Export',
checkExportConfirm: 'Are you sure to export the data of the checked subscribers?', checkExportConfirm: 'Are you sure to export the data of the checked subscribers?',
import: 'Import', import: 'Import',
importFail: 'Failure Record',
loadDataConfirm: 'Are you sure you want to reload the data?', loadDataConfirm: 'Are you sure you want to reload the data?',
loadData: 'Load Data', loadData: 'Load Data',
loadDataTip: 'Successfully fetched loaded data: {num} items, the system is internally updating the data, it will take about {timer} seconds, please wait!!!!!.', loadDataTip: 'Successfully fetched loaded data: {num} items, the system is internally updating the data, it will take about {timer} seconds, please wait!!!!!.',
@@ -1111,6 +964,7 @@ export default {
expressionModal:'Expression Modal', expressionModal:'Expression Modal',
expressionErrorTip:'Please check the expression, the wrong indicator is {kpiId}', expressionErrorTip:'Please check the expression, the wrong indicator is {kpiId}',
expressionNoIdTip:'Please check the expression, no valid indicator is found', expressionNoIdTip:'Please check the expression, no valid indicator is found',
unitSelect:'To better display the image, the same unit needs to be selected. The current unit is:',
}, },
kpiKeyTarget:{ kpiKeyTarget:{
"time":"Time", "time":"Time",
@@ -1129,8 +983,14 @@ export default {
}, },
kpiOverView:{ kpiOverView:{
"kpiName":"NE Metrics Name", "kpiName":"NE Metrics Name",
"maxValue":"Max Value", "maxValue":"Max",
"minValue":"Min Value", "maxValueTip":"The max value of metric data within the user's filtered time range.",
"minValue":"Min",
"minValueTip":"The min value of metric data within the user's filtered time range.",
"avgValue":"Avg",
"avgValueTip":"The average value of metric data within the user's filtered time range.",
"totalValue":"Sum",
"totalValueTip":"The sum value of metric data within the user's filtered time range.",
"kpiChartTitle":"Overview of NE metrics", "kpiChartTitle":"Overview of NE metrics",
"changeLine":"Change to Line Charts", "changeLine":"Change to Line Charts",
"changeBar":"Change to Bar Charts", "changeBar":"Change to Bar Charts",
@@ -1139,25 +999,6 @@ export default {
}, },
}, },
traceManage: { traceManage: {
analysis: {
imsi: 'IMSI',
imsiPlease: 'Please enter IMSI',
msisdn: 'MSISDN',
msisdnPlease: 'Please enter MSISDN',
trackTaskId: 'Task ID',
srcIp: 'Source IP Address',
dstIp: 'Destination IP Address',
signalType: 'Signaling Type',
msgDirect: 'Message Direction',
msgType: 'Message Type',
rowTime: 'Record Time',
signalData: 'Signaling Data',
signalDetail: 'Signaling Details',
noData: 'No information content',
taskTitle: 'Task {num}',
taskDownText: 'Download HTML',
taskDownTip: 'Confirm downloading the signaling details HTML file?',
},
pcap: { pcap: {
capArgPlease: 'Please enter tcpdump -i any support parameter', capArgPlease: 'Please enter tcpdump -i any support parameter',
cmd: 'Command', cmd: 'Command',
@@ -1208,30 +1049,35 @@ export default {
imsiTip: 'Mobile communication IMSI number', imsiTip: 'Mobile communication IMSI number',
srcIp: 'Source IP Address', srcIp: 'Source IP Address',
srcIpPlease: 'Please enter the IP address', srcIpPlease: 'Please enter the IP address',
srcIpTip: 'Current sender IPv4 address', srcIpTip: 'sending IPv4 address',
dstIp: 'Destination IP Address', dstIp: 'Destination IP Address',
dstIpPlease: 'Please enter the IP address', dstIpPlease: 'Please enter the IP address',
dstIpTip: 'IPv4 address of the receiving end of the other party', dstIpTip: 'receiving end IPv4 address',
interfaces: 'Signaling Interface', interfaces: 'Signaling Interface',
interfacesPlease: 'Please enter the signaling interface', interfacesPlease: 'Please enter the signaling interface',
signalPort: 'Signal Port', rangePicker: 'Task Time',
signalPortPlease: 'Please enter the signaling port',
signalPortTip: 'Port of the side corresponding to the destination IP address or source IP address',
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',
remark: 'Remark', remark: 'Remark',
remarkPlease: 'Task description can be entered', remarkPlease: 'Task description can be entered',
addTask: 'Add Task', addTask: 'Add Task',
editTask: 'Modify Task',
viewTask: 'View Task', viewTask: 'View Task',
errorTaskInfo: 'Failed to obtain task information', errorTaskInfo: 'Failed to obtain task information',
delTaskTip: 'Are you sure to delete the data item with record ID {id} ?', delTaskTip: 'Are you sure to delete the data item with record ID {id} ?',
stopTask: 'Successful cessation of tasks {id}', stopTask: 'Successful cessation of tasks {id}',
stopTaskTip: 'Confirm stopping the task with record ID {id} ?', stopTaskTip: 'Confirm stopping the task with record ID {id} ?',
pcapView: "Tracking Data Analysis", pcapView: "Track Data Analysis",
traceFile: "Tracking File", traceFile: "Track File",
errMsg: "Error Message", errMsg: "Error Message",
imsiORmsisdn: "imsi or msisdn is null, cannot start task", imsiORmsisdn: "imsi or msisdn is null, cannot start task",
dataView: "Track Data",
protocolOrInterface: "Protocol/Interface",
msgNe: 'Network Element',
msgEvent: 'Event',
msgType: 'Type',
msgDirect: 'Direction',
msgLen: 'Length',
rowTime: 'Time',
taskInfo: 'Task information',
}, },
}, },
faultManage: { faultManage: {
@@ -1341,12 +1187,13 @@ export default {
size: "Size", size: "Size",
modifiedTime: "Modified Time", modifiedTime: "Modified Time",
fileName: "File Name", fileName: "File Name",
downTipZip: "Confirm downloading the directory [{fileName}] as a ZIP file?",
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', viewAs: 'View Action',
reload: "Reload", reload: "Reload",
follow: 'Monitoring Content', follow: 'Enable Instant Update',
tailChar: 'End Characters', tailChar: 'End Characters',
tailLines: 'End Lines', tailLines: 'End Lines',
}, },
@@ -1729,7 +1576,7 @@ export default {
account: 'Account', account: 'Account',
userName: 'Nick Name', userName: 'Nick Name',
permission: 'Role', permission: 'Role',
className: 'Class', className: 'Department',
loginIp: 'Login Address', loginIp: 'Login Address',
loginTime: 'Login Time', loginTime: 'Login Time',
status: 'Status', status: 'Status',
@@ -1754,7 +1601,7 @@ export default {
userTop:'User profile', userTop:'User profile',
sex:'User Gender', sex:'User Gender',
email:'E-mail', email:'E-mail',
fromClass:'Class', fromClass:'Department',
userWork:'User position', userWork:'User position',
userWorkPlease: 'Please select user post', userWorkPlease: 'Please select user post',
userTip:'User Description', userTip:'User Description',
@@ -1855,8 +1702,8 @@ export default {
role:{ role:{
allScopeOptions:'All data permissions', allScopeOptions:'All data permissions',
byMyselfScopeOptions:'Custom data permissions', byMyselfScopeOptions:'Custom data permissions',
onlyClassScopeOptions:'Data permissions of this class', onlyClassScopeOptions:'Data permissions of this department',
classAllScopeOptions:'Data permissions for this class and the following', classAllScopeOptions:'Data permissions for this department and the following',
myselfScopeOptions:'Only personal data permissions', myselfScopeOptions:'Only personal data permissions',
roleId:'Role Number', roleId:'Role Number',
roleName:'Role Name', roleName:'Role Name',
@@ -1897,20 +1744,20 @@ export default {
batchCancel:'Batch cancellation of authorization', batchCancel:'Batch cancellation of authorization',
}, },
dept:{ dept:{
classInfo:' Class Information', classInfo:' Department Information',
className:'Class Name', className:'Department Name',
classId:'Class Number', classId:'Department Number',
classSort:'Class Sorting', classSort:'Department Sorting',
status:'Class Status', status:'Department Status',
createTime:'Creation Time', createTime:'Creation Time',
highClass:'Higher Office', highClass:'Higher Office',
emailTip:'Please input the correct email address', emailTip:'Please input the correct email address',
phoneTip:'Please enter the correct phone number', phoneTip:'Please enter the correct phone number',
node:'Root Node', node:'Root Node',
delSure:'Are you sure to delete the data item with class number [{deptId}]?', delSure:'Are you sure to delete the data item with department number [{deptId}]?',
open:'Exhibition', open:'Exhibition',
close:'Fold', close:'Fold',
addClass:'Add new sub-class', addClass:'Add new sub-department',
admin:'Principal', admin:'Principal',
phone:'Contact Number', phone:'Contact Number',
email:'Mail', email:'Mail',

View File

@@ -16,7 +16,6 @@ export default {
errorFields: '请正确填写 {num} 处必填信息!', errorFields: '请正确填写 {num} 处必填信息!',
tablePaginationTotal: '总共 {total} 条', tablePaginationTotal: '总共 {total} 条',
noData: "暂无数据", noData: "暂无数据",
zebra:'表格斑马纹',
ok: '确定', ok: '确定',
cancel: '取消', cancel: '取消',
close: '关闭', close: '关闭',
@@ -131,7 +130,7 @@ export default {
}, },
LockScreen: { LockScreen: {
inputPlacePwd:'请输入锁屏密码', inputPlacePwd:'请输入锁屏密码',
validSucc:'校验通过', enter:'进入',
validError:'校验失败', validError:'校验失败',
backLogin:'退出并重新登录', backLogin:'退出并重新登录',
backReload:'正在重启,请稍等...', backReload:'正在重启,请稍等...',
@@ -222,13 +221,12 @@ export default {
capability: '用户容量', capability: '用户容量',
serialNum: '序列号', serialNum: '序列号',
expiryDate: '许可证到期日期', expiryDate: '许可证到期日期',
neStatus:'网元状态异常', neStatus:'状态异常',
runStatus:'运行状态', runStatus:'运行状态',
mark:'简略信息', mark:'信息',
object:'对象', object:'对象',
versionNum:'版本号', versionNum:'版本号',
systemStatus:'系统状态', realNeStatus:'状态',
realNeStatus:'网元状态',
reloadTime:'刷新时间', reloadTime:'刷新时间',
Critical:'严重告警', Critical:'严重告警',
Major:'主要告警', Major:'主要告警',
@@ -341,8 +339,8 @@ export default {
profile: { profile: {
phonenumber: "手机号码", phonenumber: "手机号码",
email: "用户邮箱", email: "用户邮箱",
deptName: "所属班级", deptName: "所属部门",
postGroup: "拥有位", postGroup: "拥有位",
roleGroup: "拥有角色", roleGroup: "拥有角色",
loginIp: "登录地址", loginIp: "登录地址",
loginDate: "登录时间", loginDate: "登录时间",
@@ -351,184 +349,6 @@ export default {
description: "暂无数据,尝试刷新看看", description: "暂无数据,尝试刷新看看",
}, },
}, },
configManage: {
neManage: {
addNe:'添加网元',
delSure:'确认删除网元名称为{msg}的数据项 ',
editNe:'修改网元',
exportSure:'确认导出网元名称为 {msg} 的配置信息',
exportTip:'导出成功,请到备份管理进行下载',
getInfo:'获取网元信息失败',
neType:'网元类型',
neTypePlease: '请输入网元类型',
neId:'网元内部标识',
neName:'网元名称',
neTypeTip:'填写创建的网元类型,如:SMF',
uid:'资源唯一标识',
uidTip:'请输入资源唯一标识',
ip:'IP地址',
mac:'网元物理地址',
macTip:'能够定位网元的物理地址(MAC)',
port:'端口',
portTip:'最大范围0~65535',
pvflag:'网元虚拟化标识',
pnf:'物理网元',
vnf:'虚拟网元',
province:'网元服务省份',
vendorName:'厂商名称',
dn:'网络标识',
reload: '重载',
restart: '重启',
totalSure:'确认{oper}网元名称为 {msg} 的网元',
stop: '停止',
start: '启动',
log: '日志',
export: '导出',
import: '导入',
fileForm:'文件来源',
selectPlease:'请选择导入文件来源',
server:'服务器文件',
local:'本地文件',
fileSelect:'请选择当前导入文件',
sync:'同步到网元',
open:'开',
close:'关',
addFail:'新增失败',
operFail:'操作失败'
},
backupManage: {
setBackupTask: '设置自动备份时间',
neTypePlease: '查询网元类型',
neType: '网元类型',
neID: '网元内部标识',
fileName: '文件名',
createAt: '创建时间',
remark:'备份说明',
edit:'编辑备份文件',
totalSure:'确认{oper}记录编号为 {id} 的数据项?',
},
softwareManage: {
sendBtn: '下发',
runBtn: '激活',
backBtn: '回退',
historyBtn: '下发记录',
neTypePlease: '选择网元类型',
neType: '网元类型',
fileName: '文件名',
version: '版本号',
versionPlease: '版本号不能为空',
updateTime: '上传时间',
description: '功能描述',
deleteTip: '确认删除 【{fileName}】 的软件数据项?',
downloadTip: '确认下载 【{fileName}】 的软件数据项?',
updateComment: '软件说明',
updateCommentPlease: '请输入软件说明',
updateFile: '软件文件',
updateFilePlease: '请上传更新软件文件',
verifyFile: '校验文件',
selectFile: '选择文件',
sendTitle: '下发软件版本',
sendContent: '确认下发软件包为【{fileName}】的文件到对应网元?',
runTitle: '激活软件版本',
runContent: '确认在对应网元激活已下发【{fileName}】的软件版本?',
backTitle: '回退软件版本',
backContent: '确认在对应网元回退已下发【{fileName}】的软件版本?',
neId: '对应网元',
neIdPlease: '请选择对应网元',
versions:'版本',
upVersions:'升级前版本',
backVersions:'回退前版本',
status:'状态',
letUpTime:'激活时间',
createTime:'创建时间',
onlyAble:'只支持上传文件格式 {fileText}',
nullVersion:'当前网元无可回退版本',
},
license: {
neTypePlease: '选择网元类型',
neType: '网元类型',
serialNum: '序列号',
createTime: '时间',
comment: '说明',
updateComment: 'License说明',
updateCommentPlease: '请输入License说明',
updateFile: 'License文件',
updateFilePlease: '请上传更新License文件',
selectFile: '选择文件',
neId: '网元内部标识',
neIdPlease: '请选择对应网元',
},
configParam:{
dataNull:'暂无配置项数据',
editSuss:'修改成功',
editFail:'修改失败',
unable:'非法操作属性值',
delSure:'确认删除Index为 【{value}】 的数据项?',
addSuss:'新增成功',
addFail:'新增失败',
delArraySure:'确认删除{arrayChildTitle} Index 为 【{value}】 的数据项?',
parUnable:'参数值不在合理范围',
ipv4Tip:'不是合法的IPV4地址',
ipv6Tip:'不是合法的IPV6地址',
enumTip:'不是合理的枚举值',
boolTip:'不是合理的布尔类型的值',
default:'输入值是未知类型',
reloadSuss:'网元重新加载完成',
reloadFail:'网元重新加载失败',
neNUll:'暂无网元列表数据',
reload:'重载',
post:'提交',
editSure:'确认更新该属性值吗?',
arraryEdit:'确认提交更新 Index 为 【{value}】 的记录吗?',
addSure:'确认提交新增 Index :【{value}】 的记录吗?',
},
configParamForm: {
treeTitle: "配置导航",
treeSelectTip: "左侧配置导航中选择配置项信息!",
neType: "网元类型",
neTypePleace: "请选择网元类型",
noConfigData: "暂无配置项数据",
updateValue: "【 {num} 】 属性值修改成功",
updateValueErr: "属性值修改失败",
updateItem: "修改 Index 为 {num} 记录成功",
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: "展开",
ptDiff: '对比示例',
ptDiffExample: '示例配置',
ptDiffSelf: '当前个人',
ptDiffLoad: '加载更多',
ptDiffMerge: '差异对比',
ptDiffRest: '还原此版本',
ptHistory: '历史记录',
ptReset: '重置为示例',
ptResetTip: '确认要重置为示例配置吗?',
ptLoad: '载入当前网元配置',
ptLoadTip: '确认要载入当前网元配置吗?',
ptExport: "导出Excel",
ptExportTip: "确认要导出网元配置数据到Excel文件中吗",
ptExportAll: "导出所有学生配置",
ptApplyShow: '查看学生',
ptApply: '申请',
ptApplyNE: '应用配置到网元',
ptApplyStu: '申请配置应用到 {ne}',
ptApplyStuTip: '确认要向教师发起配置应用到 {ne} 的申请吗?',
ptApplyStuRack: '退回该学生配置',
ptApplyStuNE: '应用该学生配置',
},
},
dashboard: { dashboard: {
overview:{ overview:{
title: "核心网系统看板", title: "核心网系统看板",
@@ -600,14 +420,18 @@ export default {
resultFail: "失败", resultFail: "失败",
delTip: "确认删除编号为【{msg}】的数据项?", delTip: "确认删除编号为【{msg}】的数据项?",
exportTip: "确认导出当前查询条件的话单数据吗?(导出最大支持一万条)", exportTip: "确认导出当前查询条件的话单数据吗?(导出最大支持一万条)",
smfChargingID: '计费ID', chargingID: '计费ID',
smfSubscriptionIDData: '订阅 ID 数据', smfSubscriptionIDData: '订阅 ID 数据',
smfSubscriptionIDType: '订阅 ID 类型', smfSubscriptionIDType: '订阅 ID 类型',
smfDataVolumeUplink: '数据量上行链路', smfDataVolumeUplink: '数据量上行链路',
smfDataVolumeDownlink: '数据量下行链路', smfDataVolumeDownlink: '数据量下行链路',
smfDataTotalVolume: '数据总量', smfDataTotalVolume: '数据总量',
smfDuration: '持续时间', durationTime: '持续时间',
smfInvocationTime: '调用时间', invocationTime: '调用时间',
sgwcServedIMSI: 'IMSI',
sgwcServedMSISDN: 'MSISDN',
sgwcVolumeGPRSUplink: 'GPRS 上行链路',
sgwcVolumeGPRSDownlink: 'GPRS 下行链路',
}, },
ue: { ue: {
eventType: "事件类型", eventType: "事件类型",
@@ -679,7 +503,7 @@ export default {
delTip: '确认删除网元信息数据项吗?', delTip: '确认删除网元信息数据项吗?',
oam: { oam: {
title: 'OAM配置', title: 'OAM配置',
sync: '同步到网元', restart: '下发后重启网元',
oamEnable: '服务', oamEnable: '服务',
oamPort: '端口', oamPort: '端口',
snmpEnable: '服务', snmpEnable: '服务',
@@ -782,6 +606,7 @@ export default {
upgradeDone: '更新完成,服务正在重载', upgradeDone: '更新完成,服务正在重载',
upgradeFail: '更新失败,请检查软件文件是否存在且服务终端环境是否可用!', upgradeFail: '更新失败,请检查软件文件是否存在且服务终端环境是否可用!',
upgradeModal: '网元版本更新', upgradeModal: '网元版本更新',
noPath: '软件包文件未发现',
}, },
neLicense: { neLicense: {
status: "许可证状态", status: "许可证状态",
@@ -805,7 +630,9 @@ export default {
treeSelectTip: "左侧配置导航中选择配置项信息!", treeSelectTip: "左侧配置导航中选择配置项信息!",
neType: "网元类型", neType: "网元类型",
neTypePleace: "请选择网元类型", neTypePleace: "请选择网元类型",
neIdSyncPleace: "请选择同步网元",
noConfigData: "暂无配置项数据", noConfigData: "暂无配置项数据",
noConfigdDisabled: "配置项网元未正常服务",
updateValue: "【 {num} 】 属性值修改成功", updateValue: "【 {num} 】 属性值修改成功",
updateValueErr: "属性值修改失败", updateValueErr: "属性值修改失败",
updateItem: "修改 Index 为 {num} 记录成功", updateItem: "修改 Index 为 {num} 记录成功",
@@ -879,6 +706,30 @@ export default {
licenseTip2: '2. 点击【结束】将结束安装过程', licenseTip2: '2. 点击【结束】将结束安装过程',
}, },
}, },
neData: {
baseStation: {
list: "列表",
topology: "拓扑图",
nbName: "设备名称",
ueNum: "在线用户数",
topologyTitle: "基站状态关系图",
name: "基站名称",
namePlease: "文本内容长度0~64",
position: "基站位置",
positionPlease: "位置描述。禁止空格文本内容长度0-64",
address: "IP地址",
addressPlease: "文本内容长度0~64",
state: "基站状态",
online: "在线",
offline: "离线",
time: "变更时间",
addRadio: "添加基站信息",
editRadio: "更新基站信息",
history: "历史记录",
exportTip: "确认根据搜索条件导出xlsx表格文件吗?",
importDataEmpty: "导入数据为空",
},
},
neUser: { neUser: {
auth: { auth: {
authInfo:'鉴权信息', authInfo:'鉴权信息',
@@ -889,6 +740,7 @@ export default {
checkExport : '勾选导出', checkExport : '勾选导出',
checkExportConfirm: '确认导出已勾选的鉴权用户数据吗?', checkExportConfirm: '确认导出已勾选的鉴权用户数据吗?',
import: '导入', import: '导入',
importFail: '失败记录',
loadDataConfirm: '确认要重新加载数据吗?', loadDataConfirm: '确认要重新加载数据吗?',
loadData: '加载数据', loadData: '加载数据',
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!', loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!',
@@ -917,6 +769,7 @@ export default {
checkExport : '勾选导出', checkExport : '勾选导出',
checkExportConfirm: '确认导出已勾选的签约用户数据吗?', checkExportConfirm: '确认导出已勾选的签约用户数据吗?',
import: '导入', import: '导入',
importFail: '失败记录',
loadDataConfirm: '确认要重新加载数据吗?', loadDataConfirm: '确认要重新加载数据吗?',
loadData: '加载数据', loadData: '加载数据',
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!', loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!',
@@ -1111,6 +964,7 @@ export default {
expressionModal:'表达式模块', expressionModal:'表达式模块',
expressionErrorTip:'请检查表达式,错误的指标为{kpiId}', expressionErrorTip:'请检查表达式,错误的指标为{kpiId}',
expressionNoIdTip:'请检查表达式,没有找到任何有效的指标', expressionNoIdTip:'请检查表达式,没有找到任何有效的指标',
unitSelect:'为更好展示图需选择相同单位,当前单位为:',
}, },
kpiKeyTarget:{ kpiKeyTarget:{
"time":"时间", "time":"时间",
@@ -1130,7 +984,13 @@ export default {
kpiOverView:{ kpiOverView:{
"kpiName":"指标名", "kpiName":"指标名",
"maxValue":"最大值", "maxValue":"最大值",
"maxValueTip":"用户筛选时间范围内度量数据的最大值。",
"minValue":"最小值", "minValue":"最小值",
"minValueTip":"用户筛选时间范围内度量数据的最小值。",
"avgValue":"平均值",
"avgValueTip":"用户筛选时间范围内度量数据的平均值。",
"totalValue":"总值",
"totalValueTip":"用户筛选时间范围内度量数据的总值。",
"kpiChartTitle":"网元指标概览", "kpiChartTitle":"网元指标概览",
"changeLine":"切换为折线图", "changeLine":"切换为折线图",
"changeBar":"切换为柱状图", "changeBar":"切换为柱状图",
@@ -1139,25 +999,6 @@ export default {
}, },
}, },
traceManage: { traceManage: {
analysis: {
imsi: 'IMSI',
imsiPlease: '请输入IMSI',
msisdn: 'MSISDN',
msisdnPlease: '请输入MSISDN',
trackTaskId: '跟踪任务标记',
srcIp: '源IP地址',
dstIp: '目标IP地址',
signalType: '信令类型',
msgDirect: '消息元',
msgType: '消息类型',
rowTime: '记录时间',
signalData: '信令数据',
signalDetail: '信令详情',
noData: '无信息内容',
taskTitle: '任务 {num}',
taskDownText: '下载HTML',
taskDownTip: '确认下载信令详情HTML文件?',
},
pcap: { pcap: {
capArgPlease: '请输入tcpdump -i any支持参数', capArgPlease: '请输入tcpdump -i any支持参数',
cmd: '命令', cmd: '命令',
@@ -1208,21 +1049,17 @@ export default {
imsiTip: '移动通信IMSI编号', imsiTip: '移动通信IMSI编号',
srcIp: '源IP地址', srcIp: '源IP地址',
srcIpPlease: '请输入源IP地址', srcIpPlease: '请输入源IP地址',
srcIpTip: '当前发送端IPv4地址', srcIpTip: '发送端IPv4地址',
dstIp: '目标IP地址', dstIp: '目标IP地址',
dstIpPlease: '请输入目标IP地址', dstIpPlease: '请输入目标IP地址',
dstIpTip: '对方接收端IPv4地址', dstIpTip: '接收端IPv4地址',
interfaces: '信令接口', interfaces: '信令接口',
interfacesPlease: '请输入信令接口', interfacesPlease: '请输入信令接口',
signalPort: '信令端口', rangePicker: '任务时间',
signalPortPlease: '请输入信令端口',
signalPortTip: '目标IP地址或源IP地址对应一方的端口',
rangePicker: '开始结束时间',
rangePickerPlease: '请选择任务时间开始结束时间', rangePickerPlease: '请选择任务时间开始结束时间',
remark: '说明', remark: '说明',
remarkPlease: '可输入任务说明', remarkPlease: '可输入任务说明',
addTask: '添加任务', addTask: '添加任务',
editTask: '修改任务',
viewTask: '查看任务', viewTask: '查看任务',
errorTaskInfo: '获取任务信息失败', errorTaskInfo: '获取任务信息失败',
delTaskTip: '确认删除记录编号为 {id} 的数据项?', delTaskTip: '确认删除记录编号为 {id} 的数据项?',
@@ -1232,6 +1069,15 @@ export default {
traceFile: "跟踪文件", traceFile: "跟踪文件",
errMsg: "错误信息", errMsg: "错误信息",
imsiORmsisdn: "imsi 或 msisdn 是空值,不能开始任务", imsiORmsisdn: "imsi 或 msisdn 是空值,不能开始任务",
dataView: "跟踪数据",
protocolOrInterface: "协议/接口",
msgNe: '消息网元',
msgEvent: '消息事件',
msgType: '消息类型',
msgDirect: '消息方向',
msgLen: '消息长度',
rowTime: '消息时间',
taskInfo: '任务信息',
}, },
}, },
faultManage: { faultManage: {
@@ -1317,7 +1163,7 @@ export default {
type:'网元类型', type:'网元类型',
neId:'网元唯一标识', neId:'网元唯一标识',
MML:'MML', MML:'MML',
logTime:'记录时间', logTime:'log Time'
}, },
forwarding:{ forwarding:{
type:'网元类型', type:'网元类型',
@@ -1341,6 +1187,7 @@ export default {
size: "文件大小", size: "文件大小",
modifiedTime: "修改时间", modifiedTime: "修改时间",
fileName: "文件名称", fileName: "文件名称",
downTipZip: "确认将目录 【{fileName}】 下载为ZIP文件?",
downTip: "确认下载文件名为 【{fileName}】 文件?", downTip: "确认下载文件名为 【{fileName}】 文件?",
downTipErr: "文件获取失败", downTipErr: "文件获取失败",
dirCd: "进入目录", dirCd: "进入目录",
@@ -1729,7 +1576,7 @@ export default {
account: '登录账号', account: '登录账号',
userName: '用户昵称', userName: '用户昵称',
permission: '用户权限', permission: '用户权限',
className: '班级名称', className: '部门名称',
loginIp: '登录地址', loginIp: '登录地址',
loginTime: '登录时间', loginTime: '登录时间',
status: '用户状态', status: '用户状态',
@@ -1754,9 +1601,9 @@ export default {
userTop:'用户头像', userTop:'用户头像',
sex:'用户性别', sex:'用户性别',
email:'电子邮箱', email:'电子邮箱',
fromClass:'所属班级', fromClass:'所属部门',
userWork:'用户位', userWork:'用户位',
userWorkPlease: '请选择用户位', userWorkPlease: '请选择用户位',
userTip:'用户说明', userTip:'用户说明',
loginPwd:'登录密码', loginPwd:'登录密码',
updateSure:'是否更新已经存在的数据', updateSure:'是否更新已经存在的数据',
@@ -1855,8 +1702,8 @@ export default {
role:{ role:{
allScopeOptions:'全部数据权限', allScopeOptions:'全部数据权限',
byMyselfScopeOptions:'自定数据权限', byMyselfScopeOptions:'自定数据权限',
onlyClassScopeOptions:'本班级数据权限', onlyClassScopeOptions:'本部门数据权限',
classAllScopeOptions:'本班级及以下数据权限', classAllScopeOptions:'本部门及以下数据权限',
myselfScopeOptions:'仅本人数据权限', myselfScopeOptions:'仅本人数据权限',
roleId:'角色编号', roleId:'角色编号',
roleName:'角色名称', roleName:'角色名称',
@@ -1897,36 +1744,36 @@ export default {
batchCancel:'批量取消授权', batchCancel:'批量取消授权',
}, },
dept:{ dept:{
classInfo:'班级信息', classInfo:'部门信息',
className:'班级名称', className:'部门名称',
classId:'班级编号', classId:'部门编号',
classSort:'班级排序', classSort:'部门排序',
status:'班级状态', status:'部门状态',
createTime:'创建时间', createTime:'创建时间',
highClass:'上级班级', highClass:'上级部门',
emailTip:'请输入正确的邮箱地址', emailTip:'请输入正确的邮箱地址',
phoneTip:'请输入正确的手机号码', phoneTip:'请输入正确的手机号码',
node:'根节点', node:'根节点',
delSure:'确认删除班级编号为 【{deptId}】 的数据项?', delSure:'确认删除部门编号为 【{deptId}】 的数据项?',
open:'展', open:'展',
close:'折', close:'折',
addClass:'新增子班级', addClass:'新增子部门',
admin:'负责人', admin:'负责人',
phone:'联系电话', phone:'联系电话',
email:'邮箱', email:'邮箱',
}, },
post:{ post:{
positionInfo:'位信息', positionInfo:'位信息',
positionId:'位编号', positionId:'位编号',
positionCode:'位编码', positionCode:'位编码',
positionName:'位名称', positionName:'位名称',
positionSort:'位排序', positionSort:'位排序',
positionStatus:'位状态', positionStatus:'位状态',
positionMark:'位说明', positionMark:'位说明',
createTime:'创建时间', createTime:'创建时间',
codeTip:'请正确输入位编码', codeTip:'请正确输入位编码',
nameTip:'请正确输入位名称', nameTip:'请正确输入位名称',
delSure:'确认删除位编号为 【{postId}】 的数据项?', delSure:'确认删除位编号为 【{postId}】 的数据项?',
}, },
log:{ log:{
operate:{ operate:{

View File

@@ -1,18 +1,6 @@
import { ADMIN_PERMISSION, ADMIN_ROLE_KEY } from '@/constants/admin-constants'; import { ADMIN_PERMISSION, ADMIN_ROLE_KEY } from '@/constants/admin-constants';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
/**
* 是否系统管理员
* @returns true | false
*/
export function isSystemAdmin(): boolean {
const userPermissions = useUserStore().permissions;
if (userPermissions.includes(ADMIN_PERMISSION)) return true;
const userRoles = useUserStore().roles;
if (userRoles.includes(ADMIN_ROLE_KEY)) return true;
return false;
}
/** /**
* 只需含有其中权限 * 只需含有其中权限
* @param role 权限字符数组 * @param role 权限字符数组

View File

@@ -198,7 +198,7 @@ function beforeRequest(options: OptionsType): OptionsType | Promise<any> {
if (options.method === 'get') return options; if (options.method === 'get') return options;
// 非get参数提交 // 非get参数提交
let body = options.data let body = options.data;
if (body instanceof FormData) { if (body instanceof FormData) {
options.body = body; options.body = body;
} else if (body) { } else if (body) {

View File

@@ -119,17 +119,15 @@ export class WS {
}; };
// 用于指定当从服务器接受到信息时的回调函数。 // 用于指定当从服务器接受到信息时的回调函数。
ws.onmessage = ev => { ws.onmessage = ev => {
if (ev.type !== 'message') return;
// 解析文本消息 // 解析文本消息
if (ev.type === 'message') { try {
const data = ev.data; const jsonData = JSON.parse(ev.data);
try { if (typeof options.onmessage === 'function') {
const jsonData = JSON.parse(data); options.onmessage(jsonData);
if (typeof options.onmessage === 'function') {
options.onmessage(jsonData);
}
} catch (error) {
console.error('websocket message formatting error', error);
} }
} catch (error) {
console.error('websocket message formatting error', error);
} }
}; };
// 用于指定连接关闭后的回调函数。 // 用于指定连接关闭后的回调函数。
@@ -221,7 +219,7 @@ export class WS {
this.heartInterval = window.setInterval(() => { this.heartInterval = window.setInterval(() => {
this.send({ this.send({
requestId: `${Date.now()}`, requestId: `${Date.now()}`,
type: 'ping', type: 'PING',
}); });
}, heartTimer); }, heartTimer);
} }

View File

@@ -182,7 +182,12 @@ router.beforeEach(async (to, from, next) => {
next({ name: 'Index' }); next({ name: 'Index' });
} }
const token = getToken(); let token = getToken();
// 免用户登录认证
if (!appStore.loginAuth) {
token = '== Not Login Auth ==';
}
// 没有token // 没有token
if (!token) { if (!token) {

View File

@@ -1,9 +1,10 @@
import { getSysConf } from '@/api'; import { getSysConf } from '@/api';
import { CACHE_LOCAL_I18N } from '@/constants/cache-keys-constants'; import { CACHE_LOCAL_I18N, CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { removeToken } from '@/plugins/auth-token'; import { removeToken } from '@/plugins/auth-token';
import { parseUrlPath } from '@/plugins/file-static-url'; import { parseUrlPath } from '@/plugins/file-static-url';
import { localGet, localSet } from '@/utils/cache-local-utils'; import { localGet, localSet } from '@/utils/cache-local-utils';
import { sessionSet } from '@/utils/cache-session-utils';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
/**应用参数类型 */ /**应用参数类型 */
@@ -20,6 +21,10 @@ type AppStore = {
buildTime: string; buildTime: string;
/**系统引导使用 */ /**系统引导使用 */
bootloader: boolean; bootloader: boolean;
// 用户登录认证
loginAuth: boolean;
// 用户接口加密
cryptoApi: boolean;
// 序列号 // 序列号
serialNum: string; serialNum: string;
/**应用版权声明 */ /**应用版权声明 */
@@ -52,6 +57,8 @@ const useAppStore = defineStore('app', {
version: `-`, version: `-`,
buildTime: `-`, buildTime: `-`,
bootloader: false, bootloader: false,
loginAuth: true,
cryptoApi: true,
serialNum: `-`, serialNum: `-`,
copyright: `Copyright ©2023 For ${import.meta.env.VITE_APP_NAME}`, copyright: `Copyright ©2023 For ${import.meta.env.VITE_APP_NAME}`,
logoType: 'icon', logoType: 'icon',
@@ -85,6 +92,9 @@ const useAppStore = defineStore('app', {
if (this.bootloader) { if (this.bootloader) {
removeToken(); removeToken();
} }
this.loginAuth = res.data.loginAuth !== 'false';
this.cryptoApi = res.data.cryptoApi !== 'false';
sessionSet(CACHE_SESSION_CRYPTO_API, res.data.cryptoApi);
this.serialNum = res.data.serialNum; this.serialNum = res.data.serialNum;
this.appName = res.data.title; this.appName = res.data.title;
this.copyright = res.data.copyright; this.copyright = res.data.copyright;

View File

@@ -20,7 +20,7 @@ type MaskStateType = {
const useMaskStore = defineStore('mask', { const useMaskStore = defineStore('mask', {
state: (): MaskStateType => ({ state: (): MaskStateType => ({
type: (localGet(CACHE_LOCAL_MASK) || 'none') as MaskStateType['type'], type: (localGet(CACHE_LOCAL_MASK) || 'none') as MaskStateType['type'],
lockPasswd: localGet(CACHE_LOCAL_LOCK_PASSWD) || '', lockPasswd: atob(localGet(CACHE_LOCAL_LOCK_PASSWD) || ''),
lockTimeout: 0, lockTimeout: 0,
}), }),
getters: {}, getters: {},
@@ -59,7 +59,7 @@ const useMaskStore = defineStore('mask', {
}, 5_000); }, 5_000);
} }
if (type === 'lock') { if (type === 'lock') {
localSet(CACHE_LOCAL_LOCK_PASSWD, this.lockPasswd); localSet(CACHE_LOCAL_LOCK_PASSWD, btoa(this.lockPasswd));
} else { } else {
localRemove(CACHE_LOCAL_LOCK_PASSWD); localRemove(CACHE_LOCAL_LOCK_PASSWD);
} }

View File

@@ -2,7 +2,6 @@ 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/trace/task';
import { getNePerformanceList } from '@/api/perfManage/taskManage'; import { getNePerformanceList } from '@/api/perfManage/taskManage';
/**网元信息类型 */ /**网元信息类型 */
@@ -13,8 +12,6 @@ type NeInfo = {
neCascaderOptions: Record<string, any>[]; neCascaderOptions: Record<string, any>[];
/**选择器单级父类型 */ /**选择器单级父类型 */
neSelectOtions: Record<string, any>[]; neSelectOtions: Record<string, any>[];
/**跟踪接口列表 */
traceInterfaceList: Record<string, any>[];
/**性能测量数据集 */ /**性能测量数据集 */
perMeasurementList: Record<string, any>[]; perMeasurementList: Record<string, any>[];
}; };
@@ -24,7 +21,6 @@ const useNeInfoStore = defineStore('neinfo', {
neList: [], neList: [],
neCascaderOptions: [], neCascaderOptions: [],
neSelectOtions: [], neSelectOtions: [],
traceInterfaceList: [],
perMeasurementList: [], perMeasurementList: [],
}), }),
getters: { getters: {
@@ -61,7 +57,7 @@ const useNeInfoStore = defineStore('neinfo', {
const res = await listAllNeInfo({ const res = await listAllNeInfo({
bandStatus: false, bandStatus: false,
}); });
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS) {
// 原始列表 // 原始列表
this.neList = JSON.parse(JSON.stringify(res.data)); this.neList = JSON.parse(JSON.stringify(res.data));
@@ -79,24 +75,6 @@ const useNeInfoStore = defineStore('neinfo', {
} }
return res; return res;
}, },
// 刷新跟踪接口列表
async fnRefreshNeTraceInterface() {
this.traceInterfaceList = [];
const res = await this.fnNeTraceInterface();
return res;
},
// 获取跟踪接口列表
async fnNeTraceInterface() {
// 有数据不请求
if (this.traceInterfaceList.length > 0) {
return { code: 1, data: this.traceInterfaceList, msg: 'success' };
}
const res = await getNeTraceInterfaceAll();
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
this.traceInterfaceList = res.data;
}
return res;
},
// 获取性能测量数据集列表 // 获取性能测量数据集列表
async fnNeTaskPerformance() { async fnNeTaskPerformance() {
// 有数据不请求 // 有数据不请求
@@ -104,7 +82,7 @@ const useNeInfoStore = defineStore('neinfo', {
return { code: 1, data: this.perMeasurementList, msg: 'success' }; return { code: 1, data: this.perMeasurementList, msg: 'success' };
} }
const res = await getNePerformanceList(); const res = await getNePerformanceList();
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS) {
this.perMeasurementList = res.data; this.perMeasurementList = res.data;
} }
return res; return res;

View File

@@ -133,18 +133,12 @@ const useUserStore = defineStore('user', {
} }
// 水印文字信息=用户昵称 手机号 // 水印文字信息=用户昵称 手机号
let waterMarkContent = this.userName; // let waterMarkContent = this.userName;
if (this.phonenumber) { // if (this.phonenumber) {
waterMarkContent = `${this.userName} ${this.phonenumber}`; // waterMarkContent = `${this.userName} ${this.phonenumber}`;
} // }
// useLayoutStore().changeWaterMark(waterMarkContent); // useLayoutStore().changeWaterMark(waterMarkContent);
useLayoutStore().changeWaterMark(''); useLayoutStore().changeWaterMark('');
// 学生布局用不一样的
if (this.roles.includes('student')) {
useLayoutStore().changeConf('layout', 'side');
useLayoutStore().changeConf('menuTheme', 'dark');
useLayoutStore().changeConf('tabRender', false);
}
} }
// 网络错误时退出登录状态 // 网络错误时退出登录状态
if (res.code === 0) { if (res.code === 0) {

View File

@@ -7,8 +7,10 @@ declare module '*.vue' {
export default component; export default component;
} }
// "vue3-smooth-dnd": "^0.0.6"
declare module 'vue3-smooth-dnd'; declare module 'vue3-smooth-dnd';
// "intl-tel-input": "^25.2.0"
declare module 'intl-tel-input/intlTelInputWithUtils' { declare module 'intl-tel-input/intlTelInputWithUtils' {
import intlTelInput from 'intl-tel-input'; import intlTelInput from 'intl-tel-input';
export default intlTelInput; export default intlTelInput;

View File

@@ -20,6 +20,9 @@ export const YYYYMMDDHHMMSS = 'YYYYMMDDHHmmss';
/**年-月-日 时:分:秒 列如2022-12-30 01:01:59 */ /**年-月-日 时:分:秒 列如2022-12-30 01:01:59 */
export const YYYY_MM_DD_HH_MM_SS = 'YYYY-MM-DD HH:mm:ss'; export const YYYY_MM_DD_HH_MM_SS = 'YYYY-MM-DD HH:mm:ss';
/**年-月-日 时:分:秒 列如2022-12-30T01:01:59+08:00 */
export const YYYY_MM_DD_HH_MM_SSZ = 'YYYY-MM-DDTHH:mm:ssZZ';
/** /**
* 格式时间字符串 * 格式时间字符串
* @param dateStr 时间字符串 * @param dateStr 时间字符串
@@ -36,12 +39,12 @@ export function parseStrToDate(
/** /**
* 格式时间 * 格式时间
* @param date 可转的Date对象 * @param date 可转的Date对象
* @param formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss * @param formatStr 时间格式 默认YYYY-MM-DD HH:mm:ssZZ
* @returns 时间格式字符串 * @returns 时间格式字符串
*/ */
export function parseDateToStr( export function parseDateToStr(
date: string | number | Date, date: string | number | Date,
formatStr: string = YYYY_MM_DD_HH_MM_SS formatStr: string = YYYY_MM_DD_HH_MM_SSZ
): string { ): string {
return dayjs(date).format(formatStr); return dayjs(date).format(formatStr);
} }

View File

@@ -42,7 +42,7 @@ export async function readLoalXlsx(
/** /**
* 读取表格数据 工作表 * 读取表格数据 工作表
* @param fileBolb 文件对象 * @param fileBolb 文件对象
* @param index 文件保存路径 * @param index SheetName索引
* @return 表格对象列表 * @return 表格对象列表
*/ */
export async function readSheet( export async function readSheet(

View File

@@ -13,13 +13,41 @@ export function generateColorHEX(): string {
* @returns rgb(24 144 255) / rgba(0,0,0,.85) * @returns rgb(24 144 255) / rgba(0,0,0,.85)
*/ */
export function generateColorRGBA(hasAlpha: boolean = false) { export function generateColorRGBA(hasAlpha: boolean = false) {
const red = Math.floor(Math.random() * 256); const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
const green = Math.floor(Math.random() * 256);
const blue = Math.floor(Math.random() * 256); let red: number;
let green: number;
let blue: number;
if (isDark) {
// 暗色模式下生成较亮的颜色
red = Math.floor(Math.random() * 156) + 100; // 100-255
green = Math.floor(Math.random() * 156) + 100; // 100-255
blue = Math.floor(Math.random() * 156) + 100; // 100-255
// 确保至少有一个通道的值较高,使颜色更明亮
const brightChannel = Math.floor(Math.random() * 3);
switch (brightChannel) {
case 0:
red = Math.min(255, red + 50);
break;
case 1:
green = Math.min(255, green + 50);
break;
case 2:
blue = Math.min(255, blue + 50);
break;
}
} else {
// 亮色模式下生成正常的颜色
red = Math.floor(Math.random() * 256); // 0-255
green = Math.floor(Math.random() * 256); // 0-255
blue = Math.floor(Math.random() * 256); // 0-255
}
if (hasAlpha) { if (hasAlpha) {
const alpha = Math.floor(Math.random() * 100); const alpha = Math.floor(Math.random() * 100);
return `rgb(${red}, ${green}, ${blue}, 0.${alpha})`; return `rgba(${red}, ${green}, ${blue}, 0.${alpha})`;
} }
return `rgb(${red}, ${green}, ${blue})`; return `rgb(${red}, ${green}, ${blue})`;

View File

@@ -126,7 +126,7 @@ export function parseDataToTreeExclude(
} }
/** /**
* 解析树结构数据转出一维id数组 * 解析树结构数据转出所有一维id数组
* *
* @param data 数组数据 * @param data 数组数据
* @param fieldId 读取节点字段 默认 'id' * @param fieldId 读取节点字段 默认 'id'
@@ -158,7 +158,7 @@ export function parseTreeKeys(
} }
/** /**
* 解析树结构数据转出含子节点的一维id数组 * 解析树结构数据转出节点的一维id数组
* *
* @param data 数组数据 * @param data 数组数据
* @param fieldId 读取节点字段 默认 'id' * @param fieldId 读取节点字段 默认 'id'
@@ -221,3 +221,43 @@ export function parseDataToOptions(
return options; return options;
} }
/**
* 解析树结构数据转出子节点关联根节点的一维id数组
*
* @param data 数组数据
* @param checkedKeys 子节点数组数据
* @param fieldId 读取节点字段 默认 'id'
* @param fieldChildren 读取子节点字段 默认 'children'
* @returns 层级数组
*/
export function parseTreeNodeKeysByChecked(
data: Record<string, any>[],
checkedKeys: (string | number)[],
fieldId: string = 'id',
fieldChildren: string = 'children'
) {
// 节点id
let treeIds: (string | number)[] = [];
componet(data);
/**闭包递归函数 */
function componet(data: Record<string, any>[]) {
if (data.length <= 0) return false;
let hasKey = false;
for (const iterator of data) {
const key = iterator[fieldId];
if (checkedKeys.includes(key)) {
hasKey = true;
}
let nodes = iterator[fieldChildren];
if (Array.isArray(nodes) && nodes.length > 0) {
if (componet(nodes)) {
treeIds.push(key);
hasKey = true;
}
}
}
return hasKey;
}
return treeIds;
}

View File

@@ -171,8 +171,8 @@ export function parseSizeFromKbs(sizeByte: number, timeInterval: number): any {
} }
/** /**
* 字节数转换单位 * 位数据转换单位
* @param bits 字节Bit大小 64009540 = 512.08 MB * @param bits Bit大小 64009540 = 512.08 MB
* @returns xx B / KB / MB / GB / TB / PB / EB / ZB / YB * @returns xx B / KB / MB / GB / TB / PB / EB / ZB / YB
*/ */
export function parseSizeFromBits(bits: number | string): string { export function parseSizeFromBits(bits: number | string): string {
@@ -181,7 +181,42 @@ export function parseSizeFromBits(bits: number | string): string {
bits = bits * 8; bits = bits * 8;
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const unitIndex = Math.floor(Math.log2(bits) / 10); const unitIndex = Math.floor(Math.log2(bits) / 10);
const value = (bits / Math.pow(1000, unitIndex)).toFixed(2); const value = bits / Math.pow(1000, unitIndex);
const unti = units[unitIndex]; const unti = units[unitIndex];
if (unitIndex > 0) {
return `${value.toFixed(2)} ${unti}`;
}
return `${value} ${unti}`; return `${value} ${unti}`;
} }
/**
* 字节数转换单位
* @param byte 字节Byte大小 64009540 = 512.08 MB
* @param unit 指定单位 B / KB / MB / GB / TB / PB / EB / ZB / YB
* @returns xx B / KB / MB / GB / TB / PB / EB / ZB / YB
*/
export function parseSizeFromByte(
byte: number | string,
unit?: string
): string {
byte = Number(byte) || 0;
if (byte <= 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
let unitIndex = 0;
let unitStr = 'B';
if (unit) {
const index = units.indexOf(unit);
if (index > -1) {
unitIndex = index;
unitStr = unit;
}
} else {
unitIndex = Math.floor(Math.log2(byte) / 10);
unitStr = units[unitIndex];
}
const value = byte / Math.pow(1000, unitIndex);
if (unitIndex > 0) {
return `${value.toFixed(2)} ${unitStr}`;
}
return `${value} ${unitStr}`;
}

View File

@@ -1,714 +0,0 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, computed } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { ProModal } from 'antdv-pro-modal';
import { Form, message, Modal } from 'ant-design-vue/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/es/table';
import { parseDateToStr } from '@/utils/date-utils';
import useDictStore from '@/store/modules/dict';
import useNeInfoStore from '@/store/modules/neinfo';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useI18n from '@/hooks/useI18n';
import {
getPtNeConfigApplyList,
stuPtNeConfigApply,
updatePtNeConfigApply,
} from '@/api/pt/neConfigApply';
import { hasRoles } from '@/plugins/auth-user';
const { t } = useI18n();
const { getDict } = useDictStore();
const neInfoStore = useNeInfoStore();
/**字典数据 */
let dict: {
/**配置申请应用状态 */
ptConfigApplyStatus: DictType[];
} = reactive({
ptConfigApplyStatus: [],
});
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: '',
/**申请人 */
createBy: '',
/**状态 */
status: undefined,
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
neType: '',
createBy: '',
status: undefined,
pageNum: 1,
pageSize: 20,
});
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
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: 'center',
width: 100,
},
{
title: t('views.ne.common.neType'),
dataIndex: 'neType',
align: 'left',
width: 100,
},
{
title: '申请人',
dataIndex: 'createBy',
align: 'left',
width: 120,
},
{
title: '申请时间',
dataIndex: 'createTime',
align: 'left',
width: 150,
customRender(opt) {
if (+opt.value <= 0) return '';
return parseDateToStr(+opt.value);
},
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
align: 'left',
width: 100,
},
{
title: '处理人',
dataIndex: 'updateBy',
align: 'left',
width: 120,
},
{
title: '处理时间',
dataIndex: 'updateTime',
align: 'left',
width: 150,
customRender(opt) {
if (+opt.value <= 0) return '';
return parseDateToStr(+opt.value);
},
},
{
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: 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)[], infos: any) {
const arr = [];
for (const item of infos) {
if (item.status === '0') {
arr.push(item.id);
}
}
tableState.selectedRowKeys = arr;
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
getPtNeConfigApplyList(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 取消勾选
if (tableState.selectedRowKeys.length > 0) {
tableState.selectedRowKeys = [];
}
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 = {
/**详情框是否显示 */
openByView: boolean;
/**新增框或修改框是否显示 */
openByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
/**cron生成框是否显示 */
openByCron: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
openByView: false,
openByEdit: false,
title: '任务',
from: {
id: undefined,
createBy: '',
createTime: 0,
updateBy: '',
updateTime: 0,
neType: 'MME',
status: '0',
backInfo: '',
},
confirmLoading: false,
openByCron: false,
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
status: [
{
required: true,
message: t('common.selectPlease'),
},
],
})
);
/**
* 对话框弹出显示为 查看
* @param jobId 任务id
*/
function fnModalVisibleByVive(row: Record<string, any>) {
modalState.from = Object.assign(modalState.from, row);
modalState.title = '查看';
modalState.openByView = true;
}
/**
* 对话框弹出显示为 编辑
* @param jobId 任务id
*/
function fnModalVisibleByEdit(row: Record<string, any>) {
Object.assign(modalState.from, row);
modalState.from.status = '3';
modalState.title = '编辑状态';
modalState.openByEdit = true;
}
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
modalStateFrom
.validate()
.then(() => {
modalState.confirmLoading = true;
const from = toRaw(modalState.from);
updatePtNeConfigApply({
applyId: from.id,
neType: from.neType,
status: from.status,
backInfo: from.backInfo,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
modalState.openByEdit = false;
modalStateFrom.resetFields();
fnGetList();
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
modalState.confirmLoading = false;
});
})
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
});
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.openByEdit = false;
modalState.openByView = false;
modalState.from = {};
}
/**批量退回 */
function fnRecordBack(row?: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: row
? '确认要撤回配置应用申请吗?'
: '确认要批量退回学生的配置应用申请吗?',
onOk() {
let result: any;
if (row) {
result = stuPtNeConfigApply({ neType: row.neType, status: '1' });
} else {
result = updatePtNeConfigApply({
status: '3',
backId: tableState.selectedRowKeys.join(','),
backInfo: '请重新检查配置',
});
}
result.then((res: any) => {
fnGetList();
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
});
},
});
}
/**应用状态 */
const applyStatus = computed(() => {
if (hasRoles(['student'])) {
return dict.ptConfigApplyStatus.filter(s => ['0', '1'].includes(s.value));
}
let data = dict.ptConfigApplyStatus;
if (modalState.openByEdit && modalState.from.id) {
data = dict.ptConfigApplyStatus.filter(s => ['2', '3'].includes(s.value));
}
return data;
});
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('pt_config_apply_status')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.ptConfigApplyStatus = resArr[0].value;
}
});
// 获取网元列表
neInfoStore.fnNelist().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.configManage.license.neType')"
name="neType "
>
<a-auto-complete
v-model:value="queryParams.neType"
:options="neInfoStore.getNeSelectOtions"
allow-clear
:placeholder="t('views.configManage.license.neTypePlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="状态" name="status">
<a-select
v-model:value="queryParams.status"
allow-clear
:placeholder="t('common.selectPlease')"
:options="applyStatus"
>
</a-select>
</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>
<div class="button-container">
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
@click.prevent="fnRecordBack()"
v-roles:has="['admin', 'teacher']"
>
<template #icon><DeleteOutlined /></template>
批量退回
</a-button>
</div>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<div class="button-container">
<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>
</div>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:scroll="{ x: tableColumns.length * 120 }"
:pagination="tablePagination"
:row-selection="{
type: 'checkbox',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<DictTag
:options="dict.ptConfigApplyStatus"
:value="record.status"
/>
</template>
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.viewText') }}</template>
<a-button
type="link"
@click.prevent="fnModalVisibleByVive(record)"
>
<template #icon><ProfileOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip v-if="record.status === '0' && hasRoles(['student'])">
<template #title>撤回</template>
<a-button type="link" @click.prevent="fnRecordBack(record)">
<template #icon><RollbackOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip
v-if="record.status === '0' && hasRoles(['admin', 'teacher'])"
>
<template #title>{{ t('common.editText') }}</template>
<a-button
type="link"
@click.prevent="fnModalVisibleByEdit(record)"
>
<template #icon><FormOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
</a-table>
</a-card>
<!-- 详情框 -->
<ProModal
:drag="true"
:width="800"
:open="modalState.openByView"
:title="modalState.title"
@cancel="fnModalCancel"
>
<a-form layout="horizontal" :label-col="{ span: 6 }" :label-wrap="true">
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neType')" name="neType">
{{ modalState.from.neType }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="状态" name="status">
<DictTag
:options="dict.ptConfigApplyStatus"
:value="modalState.from.status"
/>
</a-form-item>
</a-col>
</a-row>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="申请人" name="createBy">
{{ modalState.from.createBy }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="申请时间" name="createTime">
{{ parseDateToStr(+modalState.from.createTime) }}
</a-form-item>
</a-col>
</a-row>
<a-row v-if="modalState.from.status !== '0'">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="处理人" name="updateBy">
{{ modalState.from.updateBy }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="处理时间" name="updateTime">
{{ parseDateToStr(+modalState.from.updateTime) }}
</a-form-item>
</a-col>
</a-row>
<a-form-item
v-if="modalState.from.status === '3'"
label="退回说明"
name="backInfo"
:label-col="{ span: 3 }"
:label-wrap="true"
>
{{ modalState.from.backInfo }}
</a-form-item>
</a-form>
<template #footer>
<a-button key="cancel" @click="fnModalCancel">
{{ t('common.close') }}
</a-button>
</template>
</ProModal>
<!-- 新增框或修改框 -->
<ProModal
:drag="true"
:width="800"
:open="modalState.openByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
:destroyOnClose="true"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="modalStateFrom"
layout="horizontal"
:label-col="{ span: 6 }"
:label-wrap="true"
>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neType')" name="neType">
{{ modalState.from.neType }}
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="申请人" name="createBy">
{{ modalState.from.createBy }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="申请时间" name="createTime">
{{ parseDateToStr(+modalState.from.createTime) }}
</a-form-item>
</a-col>
</a-row>
<a-form-item
label="状态"
name="status"
:label-col="{ span: 3 }"
:label-wrap="true"
v-bind="modalStateFrom.validateInfos.status"
>
<a-select
v-model:value="modalState.from.status"
:placeholder="t('common.selectPlease')"
:options="applyStatus"
>
</a-select>
</a-form-item>
<a-form-item
v-if="modalState.from.status === '3'"
label="退回说明"
name="backInfo"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-textarea
v-model:value="modalState.from.backInfo"
:auto-size="{ minRows: 2, maxRows: 6 }"
:maxlength="400"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
</a-form>
</ProModal>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -1,327 +0,0 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import CodemirrorEditeDiff from '@/components/CodemirrorEditeDiff/index.vue';
import { parseDateToStr } from '@/utils/date-utils';
import {
getPtNeConfigDataLogList,
restorePtNeConfigDataLog,
} from '@/api/pt/neConfigDataLog';
import useDictStore from '@/store/modules/dict';
import { message } from 'ant-design-vue';
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:open']);
const props = defineProps({
open: {
type: Boolean,
default: false,
},
/**网元类型 */
neType: {
type: String,
default: '',
},
/**参数名 */
paramName: {
type: String,
default: '',
},
/**学生用户账号 */
student: {
type: String,
default: '',
},
});
const { getDict } = useDictStore();
/**字典数据 */
let dict: {
/**业务类型 */
sysBusinessType: DictType[];
} = reactive({
sysBusinessType: [],
});
/**对话框对象信息状态类型 */
type StateType = {
/**新增框或修改框是否显示 */
openByList: boolean;
/**差异比较框是否显示 */
openByDiff: boolean;
/**标题 */
title: string;
/**加载状态 */
loading: boolean;
/**数据 */
data: Record<string, any>[];
/**差异数据 */
dataDiff: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let state: StateType = reactive({
openByList: false,
openByDiff: false,
title: '操作参数名称-学生账号',
loading: false,
data: [],
dataDiff: {},
confirmLoading: false,
});
function onClose() {
state.loading = false;
state.openByList = false;
state.openByDiff = false;
state.data = [];
state.dataDiff = {};
emit('cancel');
emit('update:open', false);
queryParams = {
neType: '',
paramName: '',
student: '',
pageNum: 1,
pageSize: 10,
};
}
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: '',
/**可用属性值 */
paramName: '',
/**学生账号 */
student: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 10,
});
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (state.loading) return;
state.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
if (pageNum === 1) state.data = [];
}
getPtNeConfigDataLogList(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// tablePagination.total = res.total;
state.data = state.data.concat(res.rows);
// 去首个做标题
if (queryParams.pageNum === 1 && state.data.length > 0) {
const item = state.data[0];
state.title = `${item.paramDisplay} - ${item.createBy}`;
}
if (state.data.length <= res.total && res.rows.length > 0) {
queryParams.pageNum++;
}
}
state.loading = false;
});
}
/**差异比较框打开 */
function fnMergeCellOpen(row: Record<string, any>) {
state.dataDiff = row;
state.dataDiff.paramJsonOld = JSON.stringify(
JSON.parse(state.dataDiff.paramJsonOld),
null,
2
);
state.dataDiff.paramJsonNew = JSON.stringify(
JSON.parse(state.dataDiff.paramJsonNew),
null,
2
);
state.openByDiff = true;
}
/**差异比较框关闭 */
function fnMergeCellClose() {
state.openByDiff = false;
state.dataDiff = {};
}
/**差异比较还原 */
function fnMergeCellRestore(value: 'old' | 'new') {
if (state.confirmLoading) return;
const id = state.dataDiff.id;
restorePtNeConfigDataLog({ id, value })
.then((res: any) => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
fnMergeCellClose();
fnGetList(1);
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
state.confirmLoading = false;
});
}
/**监听是否显示,初始数据 */
watch(
() => props.open,
val => {
if (val) {
if (props.neType && props.paramName) {
state.title = '';
state.openByList = true;
// 根据条件查询数据
queryParams.neType = props.neType;
queryParams.paramName = props.paramName;
if (props.student) {
queryParams.student = props.student;
}
fnGetList();
}
}
}
);
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('sys_oper_type')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.sysBusinessType = resArr[0].value;
}
});
});
</script>
<template>
<div>
<a-drawer
:width="500"
:title="state.title"
placement="right"
:open="state.openByList"
@close="onClose"
>
<a-list
class="demo-loadmore-list"
item-layout="horizontal"
:data-source="state.data"
>
<template #loadMore>
<div
:style="{
textAlign: 'center',
marginTop: '12px',
height: '32px',
lineHeight: '32px',
}"
>
<a-button @click="fnGetList()" :loading="state.loading">
{{ t('views.configManage.configParamForm.ptDiffLoad') }}
</a-button>
</div>
</template>
<template #renderItem="{ item }">
<a-list-item>
<template #actions>
<a-tooltip>
<template #title>
{{ t('views.configManage.configParamForm.ptDiffMerge') }}
</template>
<a-button type="primary" @click.prevent="fnMergeCellOpen(item)">
<template #icon><MergeCellsOutlined /></template>
</a-button>
</a-tooltip>
</template>
<a-list-item-meta>
<template #title>
<DictTag
:options="dict.sysBusinessType"
:value="item.operaType"
/>
</template>
<template #description>
{{ parseDateToStr(item.createTime) }}
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-drawer>
<a-modal
:width="800"
:destroyOnClose="true"
:mask-closable="false"
v-model:open="state.openByDiff"
:footer="null"
:body-style="{ padding: 0, maxHeight: '650px', 'overflow-y': 'auto' }"
@ok="fnMergeCellClose()"
@cancel="fnMergeCellClose()"
>
<template #title>
<DictTag
:options="dict.sysBusinessType"
:value="state.dataDiff.operaType"
/>
{{ parseDateToStr(state.dataDiff.createTime) }}
</template>
<div class="diffBack">
<div>
<a-button
type="text"
:loading="state.confirmLoading"
@click.prevent="fnMergeCellRestore('old')"
>
<template #icon><MergeCellsOutlined /></template>
{{ t('views.configManage.configParamForm.ptDiffRest') }}
</a-button>
</div>
<div>
<a-button
type="text"
:loading="state.confirmLoading"
@click.prevent="fnMergeCellRestore('new')"
>
<template #icon><MergeCellsOutlined /></template>
{{ t('views.configManage.configParamForm.ptDiffRest') }}
</a-button>
</div>
</div>
<CodemirrorEditeDiff
:old-area="state.dataDiff.paramJsonOld"
:new-area="state.dataDiff.paramJsonNew"
></CodemirrorEditeDiff>
</a-modal>
</div>
</template>
<style lang="less" scoped>
.diffBack {
display: flex;
flex-direction: row;
justify-content: space-between;
& > div:first-child {
flex: 1;
background: #fa9;
}
& > div:last-child {
flex: 1;
background: #8f8;
}
}
</style>

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