Compare commits

..
487 Commits
Author SHA1 Message Date
lai 5637af9798 取消隐藏分页 2025-09-15 16:20:45 +08:00
lai acdf580ba5 卡片加高 2025-08-08 10:24:31 +08:00
lai 886fb7a7c0 修改msdData解析方法 2025-08-08 09:51:33 +08:00
zhangsz 0ac4e1b7ff fix... 2025-07-25 11:42:30 +08:00
zhangsz 8137808d44 fix: router error display 403 if the first children menu unavalaible 2025-07-25 11:42:00 +08:00
zhangsz d4b0f9f343 fix: isovisModelyear zh description 2025-07-18 19:14:33 +08:00
lai b7ae3c0cdb 修改中英文 2025-07-18 18:37:18 +08:00
lai 5e21c8d53d 增加限制 2025-07-18 17:36:41 +08:00
lai 6598f7c67b 更改排版 2025-07-18 16:51:36 +08:00
lai c6b5d9861c Merge branch 'psap' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into psap 2025-07-18 15:03:46 +08:00
lai b1776f66a0 新增接口 2025-07-18 15:03:25 +08:00
lai 03082ba11f 修复PSAP需求 2025-07-18 15:02:39 +08:00
zhangsz 3e67ebbe8a feat: menu display follow NE installed 2025-07-17 17:43:06 +08:00
lai 3372d9929b Merge branch 'psap' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into psap 2025-07-15 18:44:44 +08:00
lai bcca03d983 新增字段 2025-07-15 18:44:24 +08:00
zhangsz 7b91ffa19d fix: oam issue 2025-07-15 16:25:21 +08:00
lai 772593e6da 新增修改接口 2025-07-14 11:41:06 +08:00
zhangsz 5a4f12687e fix: cbc put status url 2025-07-10 18:19:03 +08:00
zhangsz f5684d867f fix: update message json 2025-07-10 14:22:53 +08:00
lai 14df112884 新增中英文 2025-07-09 18:35:20 +08:00
lai 1a46b1948b 新增创建时间 2025-07-09 16:27:39 +08:00
lai db20ced6f3 更正中英文翻译 2025-07-09 16:11:05 +08:00
lai 50543a241c 完善cbc页面 2025-07-09 14:53:35 +08:00
lai eb809f4f3d 修复neID 2025-07-08 18:05:11 +08:00
lai 4cba38f0c0 Merge branch 'psap' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into psap 2025-07-08 17:03:27 +08:00
lai 34cbcb808a 新增cbc页面 2025-07-08 17:02:20 +08:00
zhangsz e53328176c feat: support ims and smsc 2025-06-20 18:43:26 +08:00
zhangsz c1fe8cd4c5 feat: add cbc NE type 2025-06-16 11:36:52 +08:00
lai 42c4e19f84 add search 2025-06-13 11:36:39 +08:00
lai 270f9915dc add psap version 2025-06-06 16:44:32 +08:00
lai af15418695 update 2025-05-21 15:55:42 +08:00
lai 331315ab84 更新 2025-05-21 15:24:13 +08:00
lai 43034fd082 Merge branch 'psap' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into psap 2025-04-17 16:01:11 +08:00
lai 7e790d90b8 add realData 2025-04-17 16:01:01 +08:00
zhangsz 0a19aaa8df fix: psap cdr field name 2025-04-16 15:58:24 +08:00
lai bfa80e10f5 add sysMem 2025-04-16 14:52:45 +08:00
lai edd399ac5c add cpu 2025-04-15 20:34:17 +08:00
lai 9a47e34dfc update 2025-04-15 16:59:58 +08:00
lai ab1532c2f5 Merge branch 'psap' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into psap 2025-04-15 16:46:32 +08:00
zhangsz eed90b24ef fix: ne type PSAP to MF 2025-04-15 16:40:35 +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
zhangsz e1343ce228 feat: refresh ne type 2025-04-15 11:05:24 +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
simonzhangsz 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
123 changed files with 16284 additions and 3767 deletions

No files matched your search

+2 -2
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.241115" VITE_APP_VERSION = "2.250412"
# 接口基础URL地址-不带/后缀 # 接口基础URL地址-不带/后缀
VITE_API_BASE_URL = "/omc-api" VITE_API_BASE_URL = "/omc-api"
+1 -1
View File
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
VITE_APP_CODE = "OMC" VITE_APP_CODE = "OMC"
# 应用版本 # 应用版本
VITE_APP_VERSION = "2.241115" VITE_APP_VERSION = "2.250412"
# 接口基础URL地址-不带/后缀 # 接口基础URL地址-不带/后缀
VITE_API_BASE_URL = "/omc-api" VITE_API_BASE_URL = "/omc-api"
+22 -22
View File
@@ -13,34 +13,34 @@
}, },
"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.2",
"@codemirror/lang-yaml": "^6.1.1", "@codemirror/lang-yaml": "^6.1.2",
"@codemirror/merge": "^6.7.2", "@codemirror/merge": "^6.8.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": "^12.5.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.11",
"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.0",
"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"
}, },
@@ -48,14 +48,14 @@
"@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.1",
"less": "^4.2.0", "less": "^4.2.1",
"typescript": "^5.6.3", "typescript": "~5.6.3",
"unplugin-vue-components": "^0.27.4", "unplugin-vue-components": "^0.28.0",
"vite": "5.4.10", "vite": "^6.1.0",
"vite-plugin-compression": "~0.5.1", "vite-plugin-compression": "~0.5.1",
"vue-tsc": "^2.1.8" "vue-tsc": "^2.2.0"
} }
} }
+12 -5
View File
@@ -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}`);
})(); })();
Binary file not shown.
Binary file not shown.
+28
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
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

+27
View File
@@ -0,0 +1,27 @@
import { request } from '@/plugins/http-fetch';
/**
* 查询工单列表
* @param query 查询参数
* @returns object
*/
export function listCallBack(query: Record<string, any>) {
return request({
url: '/psap/v1/mf/ticket/list',
method: 'get',
params: query,
});
}
/**
* 切换进行中状态
* @param
* @returns bolb
*/
export function updateStatus(data: Record<string, any>) {
return request({
url: `psap/v1/mf/ticket/${data.ticketId}/status`,
method: 'PATCH',
});
}
+16
View File
@@ -0,0 +1,16 @@
import { request } from '@/plugins/http-fetch';
/**
* 查询定时任务调度列表
* @param query 查询参数
* @returns object
*/
export function listCallings(query: Record<string, any>) {
return request({
url: '/psap/v1/mf/callings/list',
method: 'get',
params: query,
});
}
+75
View File
@@ -0,0 +1,75 @@
import { request } from '@/plugins/http-fetch';
/**
* CBC列表
* @param query 查询参数
* @returns object
*/
export function listCBC(query: Record<string, any>) {
return request({
url: `/psap/v1/cbc/${query.neId}/message/list`,
method: 'get',
params: query,
timeout: 30_000,
});
}
/**
* CBC签约用户新增
* @param data 签约对象
* @returns object
*/
export function addCBC(data: Record<string, any>) {
return request({
url: `/psap/v1/cbc/${data.neId}/message`,
method: 'post',
data: data,
timeout: 180_000,
});
}
export function updateCBCStatus(data:any) {
return request({
url: `/psap/v1/cbc/${data.neId}/message/${data.id}/${data.status}`,
method: 'put',
timeout: 180_000,
});
}
export function updateCBC(data:any) {
return request({
url: `/psap/v1/cbc/${data.neId}/message/${data.id}`,
method: 'put',
data,
timeout: 180_000,
});
}
/**
* CBC删除
* @param data 签约对象
* @returns object
*/
export function delCBC(neId: string, id: string) {
return request({
url: `/psap/v1/cbc/${neId}/message/${id}`,
method: 'delete',
timeout: 180_000,
});
}
+4 -4
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,
-2
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';
/** /**
* 查询列表 * 查询列表
+13 -6
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',
+3 -3
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',
}, },
+42
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 },
});
}
+4 -2
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',
}); });
} }
+4 -2
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,
}); });
} }
+88
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}`,
},
});
}
+1
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,
}); });
} }
+43
View File
@@ -0,0 +1,43 @@
import { request } from '@/plugins/http-fetch';
/**
* 查询IMS-CDR会话事件
* @param query 查询参数
* @returns object
*/
export function listMFDataCDR(query: Record<string, any>) {
return request({
url: '/neData/mf/cdr/list',
method: 'get',
params: query,
timeout: 60_000,
});
}
/**
* IMS-CDR会话删除
* @param id 信息ID
* @returns object
*/
export function delMFDataCDR(cdrIds: string | number) {
return request({
url: `/neData/mf/cdr/${cdrIds}`,
method: 'delete',
timeout: 60_000,
});
}
/**
* IMS-CDR会话列表导出
* @param data 查询列表条件
* @returns object
*/
export function exportMFDataCDR(data: Record<string, any>) {
return request({
url: '/neData/mf/cdr/export',
method: 'post',
data,
responseType: 'blob',
timeout: 60_000,
});
}
+88
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}`,
},
});
}
+30
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
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,
});
}
+16 -2
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,
}); });
+1
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,
}); });
} }
+51
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,
});
}
+1 -3
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;
} }
-3
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';
/** /**
* 查询自定义指标 * 查询自定义指标
-1
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';
/** /**
* 查询任务列表 * 查询任务列表
+2 -2
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,
}); });
} }
+1 -1
View File
@@ -76,7 +76,7 @@ export function filePullTask(traceId: string) {
method: 'get', method: 'get',
params: { traceId }, params: { traceId },
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 600_000,
}); });
} }
+1 -1
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,
}); });
} }
+103 -39
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>
+7 -6
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;
@@ -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>
+3
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';
+3 -17
View File
@@ -1,24 +1,10 @@
/**网元列表,默认顺序 */ /**网元列表,默认顺序 */
export const NE_TYPE_LIST = [ export const NE_TYPE_LIST = [
'OMC', 'OMC',
'IMS',
'AMF',
'AUSF',
'UDM',
'SMF',
'PCF',
'NSSF',
'NRF',
'UPF',
'LMF',
'NEF',
'MME',
'N3IWF',
'MOCNGW',
'SMSC',
'SMSF',
'CBC', 'CBC',
'CHF', 'MF',
'IMS',
'SMSC',
]; ];
/** /**
+154 -164
View File
@@ -222,12 +222,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',
@@ -351,168 +350,89 @@ export default {
description: "No data yet, try refreshing", description: "No data yet, try refreshing",
}, },
}, },
configManage: { agentManage:{
neManage: { callings:{
addNe:'Add Network Element', callerIdNumber:'Caller Number',
delSure:'Confirm deleting the data item with network element name {msg}', calleeIdNumber:'Callee Number',
editNe:'Edit Network Element', startTime:'Start Time',
exportSure:'Confirm exporting the configuration information with the network element name {msg}', answeredTime:'Answered Time(s)',
exportTip:'Export successful, please go to backup management for download', callDuration:'Call Duration',
getInfo:'Failed to get network element information', msdData:'MSD Info',
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: { callback:{
setBackupTask: 'Set automatic backup time', callerIdNumber:'Caller Number',
neTypePlease: 'Query network element type', calleeIdNumber:'Callee Number',
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', status:'Status',
letUpTime:'Activation time', ticketId:'Ticket ID',
createTime:'Creation time', startTime:'Created Time',
onlyAble:'Only upload file format {fileText} is supported', updateTime:'Update Time',
nullVersion:'There is no rollback version for the current network element.', msdData:'MSD Info',
}, agentName: 'Agent Name',
license: { comment: 'Comment',
neTypePlease: 'Select network element type', agentEmail: 'Agent Email',
neType: 'NE Type', agentMobile: 'Agent Mobile',
serialNum: 'Serial Num', title: ' Ticket List',
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",
}, },
cbc:{
cbe:{
neType:'CBC Object',
title: ' CBC Event',
delTip: 'Confirm deletion of the event data item numbered [{num}]?',
eventName: 'Event Name',
startTime: 'Start Time',
endTime: 'End Time',
repetitionPeriod: 'Repetition Period',
numOfBcast: 'Number of Broadcasts',
msgPWSType: 'Message Type',
messageId: 'Message ID',
displayMode: 'Display Mode',
geoScope:' Geographic Scope',
emergencyUserAlert: 'Emergency User Alert',
activatePopup: 'Activate Popup',
warningType: 'Warning Type',
language:' Language',
warningMessageText:' Broadcast Content',
status: 'Status',
warningAreaType: 'Warning Area Type',
taiListTip:'TAI List cannot be empty',
taiSonTip:'TAI List each item MCC, MNC, TAC cannot be empty',
eutraListTip:'EUTRA CellId List cannot be empty',
eutraSonTip:'EUTRA CellId List each item MCC, MNC, CellId cannot be empty',
nrTip:'NR CellId List cannot be empty',
nrSonTip:'NR CellId List each item MCC, MNC, CellId cannot be empty',
areaTip:'Area ID List cannot be empty',
areaSonTip:'Area ID List each item AreaID cannot be empty',
messageIdProfile:'Message ID Profile',
serialNumProfile:'Serial Num Profile',
warningTypeProfile:'Warning Type Profile',
warningMessageProfile:'Warning Message Profile',
etws:'Earthquake and Tsunami Warning System',
cmas:'Commercial Mobile Alert System',
createdAt:'Create Time',
eventNameHelp:'If it is CMAS, the recommended prefix is cmas_xxx. If it is ETWS, the recommended prefix is etws_xxx',
repetitionPeriodHelp:'Unit is seconds',
numOfBcastHelp:'Unit is seconds',
cellListTip:'CellId List cannot be empty',
cellListSonTip:'MCC、MNC、 Cannot be empty, and at least one EUTRA CellId/NR CellId must be filled in',
letupSure:'Do you confirm the status of the broadcast event with the modification number 【{id}】?',
tacHelp:'The TAC value is a decimal string, separated by ";" for multiple TAC values.',
cellIdHelp:'The CellId value is a hexadecimal string, separated by ";" for multiple CellId values.',
areaId:'The areaId value is a hexadecimal string.',
detail:'Detail',
}
}, },
dashboard: { dashboard: {
overview:{ overview:{
title: "Core Network Dashboard", title: "Core Network Dashboard",
fullscreen: "Click on the full-screen display", fullscreen: "Click on the full-screen display",
toRouter: "Click to jump to the detail page", toRouter: "Click to jump to the detail page",
psapTitle:'PSAP Dashboard',
onlineUser:'Online User',
totalUser:'Total User',
parallelUser:'Parallel User',
userTitle:'User Statistics',
sysTitle:'System Resources',
skim: { skim: {
users: "Users", users: "Users",
userTitle:'User Information', userTitle:'User Information',
@@ -579,14 +499,47 @@ 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',
recordPath:'Record Path',
msd:' MSD',
automaticActivation: 'Automatic Activation',
positionCanBeTrusted: 'Position Can Be Trusted',
testCall: 'Test Call',
vehicleType:'Vehicle Type',
messageIdentifier: 'Message Identifier',
numberOfOccupants: 'Number Of Occupants',
n1latitudeDelta:'Latitude Delta',
n1longitudeDelta: 'Longitude Delta',
n2latitudeDelta:'Latitude Delta',
n2longitudeDelta: 'Longitude Delta',
timestamp: 'Timestamp',
vehicleDirection: 'Vehicle Direction',
isovds: 'Vehicle Descriptor Section',
isovisModelyear:'Vehicle Identifier Section Year',
isovisSeqPlant: 'Vehicle Identifier Section Plant & Serial',
isowmi: 'World Manufacturer Identifier',
positionLatitude: 'Position Latitude',
positionLongitude: 'Position Longitude',
dieselTankPresent: 'Diesel Tank Present',
electricEnergyStorage: 'Electric Energy Storage',
gasolineTankPresent: 'Gasoline Tank Present',
control:'Control',
recentVehicleLocationN1:'Recent Vehicle Location N1',
recentVehicleLocationN2:'Recent Vehicle Location N2',
vehicleIdentificationNumber:'Vehicle Identification Number',
vehicleLocation:'Vehicle Location',
vehiclePropulsionStorageType:'Vehicle Propulsion Storage Type',
}, },
ue: { ue: {
eventType: "Event Type", eventType: "Event Type",
@@ -761,6 +714,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",
@@ -784,7 +738,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}.",
@@ -845,7 +801,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",
@@ -858,6 +814,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',
@@ -868,6 +848,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!!!!!.',
@@ -896,6 +877,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!!!!!.',
@@ -1090,6 +1072,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",
@@ -1108,8 +1091,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",
@@ -1320,12 +1309,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',
}, },
+154 -160
View File
@@ -1,3 +1,7 @@
import { language } from "@/plugins/http-fetch";
import { eventData } from "@/views/dashboard/overview/hooks/useUserActivity";
import { start, status } from "nprogress";
export default { export default {
// 语言 // 语言
i18n: '中文', i18n: '中文',
@@ -222,13 +226,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:'主要告警',
@@ -351,168 +354,89 @@ export default {
description: "暂无数据,尝试刷新看看", description: "暂无数据,尝试刷新看看",
}, },
}, },
configManage: { agentManage:{
neManage: { callings:{
addNe:'添加网元', callerIdNumber:'主叫号码',
delSure:'确认删除网元名称为{msg}的数据项 ', calleeIdNumber:'被叫号码',
editNe:'修改网元', startTime:'开始时间',
exportSure:'确认导出网元名称为 {msg} 的配置信息', answeredTime:'接听时间(s)',
exportTip:'导出成功,请到备份管理进行下载', callDuration:'通话时长',
getInfo:'获取网元信息失败', msdData:'MSD内容',
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: { callback:{
setBackupTask: '设置自动备份时间', callerIdNumber:'主叫号码',
neTypePlease: '查询网元类型', calleeIdNumber:'被叫号码',
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:'状态', status:'状态',
letUpTime:'激活时间', ticketId:'工单编号',
createTime:'创建时间', startTime:'创建时间',
onlyAble:'只支持上传文件格式 {fileText}', updateTime:'更新时间',
nullVersion:'当前网元无可回退版本', msdData:'MSD内容',
}, agentName: '座席名称',
license: { comment: '备注',
neTypePlease: '选择网元类型', agentEmail: '座席邮箱',
neType: '网元类型', agentMobile: '座席手机',
serialNum: '序列号', title: '工单列表',
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: "展开",
}, },
cbc:{
cbe:{
neType:'CBC网元对象',
title: '广播事件',
delTip: '确认删除编号为【{num}】的CBC事件吗?',
eventName: '事件名称',
startTime: '开始时间',
endTime: '结束时间',
repetitionPeriod: '广播周期',
numOfBcast: '广播次数',
msgPWSType: '消息类型',
messageId: '消息编号',
displayMode: '显示模式',
geoScope:'广播覆盖范围',
emergencyUserAlert: '紧急用户提示',
activatePopup: '弹窗提示',
warningType: '预警类型标识',
language:'语言',
warningMessageText:'广播内容',
status: '状态',
warningAreaType: '预警区域类型',
taiListTip:'TAI List 不能为空',
taiSonTip:'TAI List 每项的 MCC、MNC、TAC 都不能为空',
eutraListTip:'EUTRA CellId List 不能为空',
eutraSonTip:'EUTRA CellId List 每项的 MCC、MNC、CellId 都不能为空',
nrTip:'NR CellId List 不能为空',
nrSonTip:'NR CellId List 每项的 MCC、MNC、CellId 都不能为空',
areaTip:'Area ID List 不能为空',
areaSonTip:'Area ID List 每项的 AreaID 都不能为空',
messageIdProfile:'消息标识配置',
serialNumProfile:'序列号配置',
warningTypeProfile:'预警类型配置',
warningMessageProfile:'预警消息配置',
etws:'地震海啸预警',
cmas:'公共预警广播',
createdAt:'创建时间',
eventNameHelp:'如果为CMAS,推荐前缀为cmas_xxx,如果为ETWS,推荐前缀为etws_xxx',
repetitionPeriodHelp:'单位是秒',
numOfBcastHelp:'单位是秒',
cellListTip:'CellId List不能为空',
cellListSonTip:'MCC、MNC、不能为空以及EUTRA CellId/NR CellId至少填写一个',
letupSure:'确认修改编号为 【{id}】 的广播事件的状态嘛?',
tacHelp:'TAC值是十进制字符串,通过";"分隔多个TAC值',
cellIdHelp:'CellId值是十六进制字符串,通过";"分隔多个CellId值',
areaId:'areaId值是十六进制字符串',
detail:'详情',
}
}, },
dashboard: { dashboard: {
overview:{ overview:{
title: "核心网系统看板", title: "核心网系统看板",
fullscreen: "点击全屏显示", fullscreen: "点击全屏显示",
toRouter: "点击跳转详情页面", toRouter: "点击跳转详情页面",
psapTitle:'PSAP看板',
onlineUser:'在线座席数',
totalUser:'总座席数',
parallelUser:'并行通话数',
userTitle:'用户统计',
sysTitle:'系统资源',
skim: { skim: {
users: "用户数", users: "用户数",
userTitle:'用户信息', userTitle:'用户信息',
@@ -579,14 +503,47 @@ 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 下行链路',
recordPath:'录音文件路径',
msd:'最小数据集',
automaticActivation: '自动激活',
positionCanBeTrusted: '位置可信',
testCall: '测试呼叫',
vehicleType:'车辆类型',
messageIdentifier: '消息标识',
numberOfOccupants: '乘员数量',
n1latitudeDelta:'纬度增量',
n1longitudeDelta: '经度增量',
n2latitudeDelta:' 纬度增量',
n2longitudeDelta: '经度增量',
timestamp: '时间戳',
vehicleDirection: '车辆行驶方向',
isovds: '车辆描述段',
isovisModelyear:'车辆标识段年份',
isovisSeqPlant: '车辆标识段的工厂编码与生产序号',
isowmi: '世界制造商标识',
positionLatitude: '位置纬度',
positionLongitude: '位置经度',
dieselTankPresent: '柴油罐存在',
electricEnergyStorage: '电力储能',
gasolineTankPresent: '汽油罐存在',
control:'控制',
recentVehicleLocationN1:'最近车辆位置N1',
recentVehicleLocationN2:'最近车辆位置N2',
vehicleIdentificationNumber:'车辆识别号码',
vehicleLocation:'车辆位置',
vehiclePropulsionStorageType:'车辆推进存储类型',
}, },
ue: { ue: {
eventType: "事件类型", eventType: "事件类型",
@@ -761,6 +718,7 @@ export default {
upgradeDone: '更新完成,服务正在重载', upgradeDone: '更新完成,服务正在重载',
upgradeFail: '更新失败,请检查软件文件是否存在且服务终端环境是否可用!', upgradeFail: '更新失败,请检查软件文件是否存在且服务终端环境是否可用!',
upgradeModal: '网元版本更新', upgradeModal: '网元版本更新',
noPath: '软件包文件未发现',
}, },
neLicense: { neLicense: {
status: "许可证状态", status: "许可证状态",
@@ -784,7 +742,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} 记录成功",
@@ -858,6 +818,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:'鉴权信息',
@@ -868,6 +852,7 @@ export default {
checkExport : '勾选导出', checkExport : '勾选导出',
checkExportConfirm: '确认导出已勾选的鉴权用户数据吗?', checkExportConfirm: '确认导出已勾选的鉴权用户数据吗?',
import: '导入', import: '导入',
importFail: '失败记录',
loadDataConfirm: '确认要重新加载数据吗?', loadDataConfirm: '确认要重新加载数据吗?',
loadData: '加载数据', loadData: '加载数据',
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!', loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!',
@@ -896,6 +881,7 @@ export default {
checkExport : '勾选导出', checkExport : '勾选导出',
checkExportConfirm: '确认导出已勾选的签约用户数据吗?', checkExportConfirm: '确认导出已勾选的签约用户数据吗?',
import: '导入', import: '导入',
importFail: '失败记录',
loadDataConfirm: '确认要重新加载数据吗?', loadDataConfirm: '确认要重新加载数据吗?',
loadData: '加载数据', loadData: '加载数据',
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!', loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!',
@@ -1090,6 +1076,7 @@ export default {
expressionModal:'表达式模块', expressionModal:'表达式模块',
expressionErrorTip:'请检查表达式,错误的指标为{kpiId}', expressionErrorTip:'请检查表达式,错误的指标为{kpiId}',
expressionNoIdTip:'请检查表达式,没有找到任何有效的指标', expressionNoIdTip:'请检查表达式,没有找到任何有效的指标',
unitSelect:'为更好展示图需选择相同单位,当前单位为:',
}, },
kpiKeyTarget:{ kpiKeyTarget:{
"time":"时间", "time":"时间",
@@ -1109,7 +1096,13 @@ export default {
kpiOverView:{ kpiOverView:{
"kpiName":"指标名", "kpiName":"指标名",
"maxValue":"最大值", "maxValue":"最大值",
"maxValueTip":"用户筛选时间范围内度量数据的最大值。",
"minValue":"最小值", "minValue":"最小值",
"minValueTip":"用户筛选时间范围内度量数据的最小值。",
"avgValue":"平均值",
"avgValueTip":"用户筛选时间范围内度量数据的平均值。",
"totalValue":"总值",
"totalValueTip":"用户筛选时间范围内度量数据的总值。",
"kpiChartTitle":"网元指标概览", "kpiChartTitle":"网元指标概览",
"changeLine":"切换为折线图", "changeLine":"切换为折线图",
"changeBar":"切换为柱状图", "changeBar":"切换为柱状图",
@@ -1320,6 +1313,7 @@ export default {
size: "文件大小", size: "文件大小",
modifiedTime: "修改时间", modifiedTime: "修改时间",
fileName: "文件名称", fileName: "文件名称",
downTipZip: "确认将目录 【{fileName}】 下载为ZIP文件?",
downTip: "确认下载文件名为 【{fileName}】 文件?", downTip: "确认下载文件名为 【{fileName}】 文件?",
downTipErr: "文件获取失败", downTipErr: "文件获取失败",
dirCd: "进入目录", dirCd: "进入目录",
+10 -2
View File
@@ -29,12 +29,15 @@ import { MENU_PATH_INLINE } from '@/constants/menu-constants';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr } from '@/utils/date-utils';
import { parseUrlPath } from '@/plugins/file-static-url'; import { parseUrlPath } from '@/plugins/file-static-url';
import useNeInfoStore from '@/store/modules/neinfo';
const { proConfig, waterMarkContent } = useLayoutStore(); const { proConfig, waterMarkContent } = useLayoutStore();
const { t, currentLocale } = useI18n(); const { t, currentLocale } = useI18n();
const routerStore = useRouterStore(); const routerStore = useRouterStore();
const tabsStore = useTabsStore(); const tabsStore = useTabsStore();
const appStore = useAppStore(); const appStore = useAppStore();
const router = useRouter(); const router = useRouter();
const neListStore = useNeInfoStore();
/**菜单面板 */ /**菜单面板 */
const layoutState = reactive({ const layoutState = reactive({
@@ -65,6 +68,7 @@ watch(
); );
// 动态路由添加到菜单面板 // 动态路由添加到菜单面板
const menuData = computed(() => {
const rootRoute = router.getRoutes().find(r => r.name === 'Root'); const rootRoute = router.getRoutes().find(r => r.name === 'Root');
if (rootRoute) { if (rootRoute) {
const children = routerStore.setRootRouterData(rootRoute.children); const children = routerStore.setRootRouterData(rootRoute.children);
@@ -75,8 +79,12 @@ if (rootRoute) {
rootRoute.children = children; rootRoute.children = children;
} }
} }
const neTypes = neListStore.getNeSelectOtions.map(v => v.value);
const { menuData } = getMenuData(clearMenuItem(router.getRoutes())); let routes = clearMenuItem(router.getRoutes());
routes = routerStore.clearMenuItemByNeList(routes, neTypes);
const { menuData } = getMenuData(routes);
return menuData;
});
/**面包屑数据对象,排除根节点和首页不显示 */ /**面包屑数据对象,排除根节点和首页不显示 */
const breadcrumb = computed(() => { const breadcrumb = computed(() => {
+1 -1
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) {
+326 -14
View File
@@ -13,6 +13,7 @@ import { validHttp } from '@/utils/regular-utils';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import useRouterStore from '@/store/modules/router'; import useRouterStore from '@/store/modules/router';
import useNeInfoStore from '@/store/modules/neinfo';
// NProgress Configuration // NProgress Configuration
NProgress.configure({ showSpinner: false }); NProgress.configure({ showSpinner: false });
@@ -144,10 +145,17 @@ const router = createRouter({
}); });
/**全局路由-后置守卫 */ /**全局路由-后置守卫 */
// 在路由后置守卫中清理计数器
router.afterEach((to, from, failure) => { router.afterEach((to, from, failure) => {
NProgress.done(); NProgress.done();
// 清理成功导航的重定向计数
if (!failure) {
const redirectKey = `${from.path}->${to.path}`;
redirectCount.delete(redirectKey);
}
const title = to.meta?.title; const title = to.meta?.title;
// 设置标题
if (!failure && title) { if (!failure && title) {
useAppStore().setTitle(to.meta.title); useAppStore().setTitle(to.meta.title);
} }
@@ -163,10 +171,27 @@ const WHITE_LIST: string[] = [
'/trace-task-hlr', '/trace-task-hlr',
]; ];
const redirectCount = new Map<string, number>();
const MAX_REDIRECT_COUNT = 3; // 最大重定向次数
/**全局路由-前置守卫 */ /**全局路由-前置守卫 */
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
NProgress.start(); NProgress.start();
// 检查是否是 F5 刷新(from.name 为 null 且 to.path 不是根路径)
const isRefresh = !from.name && from.path === '/';
// 重定向计数检查
const redirectKey = `${from.path}->${to.path}`;
const currentCount = redirectCount.get(redirectKey) || 0;
if (currentCount > MAX_REDIRECT_COUNT) {
console.warn(`检测到重定向循环: ${redirectKey},强制跳转到首页`);
redirectCount.delete(redirectKey);
next({ name: 'Index', replace: true });
return;
}
// 获取系统配置信息 // 获取系统配置信息
const appStore = useAppStore(); const appStore = useAppStore();
if (!appStore.loginBackground) { if (!appStore.loginBackground) {
@@ -182,7 +207,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) {
@@ -197,39 +227,321 @@ router.beforeEach(async (to, from, next) => {
// 有Token // 有Token
if (token) { if (token) {
// 防止重复访问登录页面
if (to.path === '/login') { if (to.path === '/login') {
next({ name: 'Index' }); next({ name: 'Index' });
} else { } else {
// 判断当前用户是否有角色信息
const user = useUserStore(); const user = useUserStore();
if (user.roles && user.roles.length === 0) { if (user.roles && user.roles.length === 0) {
try { try {
// 获取用户信息 await useNeInfoStore().fnNelist();
await user.fnGetInfo(); await user.fnGetInfo();
// 获取路由信息
const accessRoutes = await useRouterStore().generateRoutes(); const accessRoutes = await useRouterStore().generateRoutes();
// 根据后台配置生成可访问的路由表
if (accessRoutes && accessRoutes.length !== 0) { if (accessRoutes && accessRoutes.length !== 0) {
for (const route of accessRoutes) { for (const route of accessRoutes) {
// 动态添加可访问路由表,http开头会异常
if (!validHttp(route.path)) { if (!validHttp(route.path)) {
router.addRoute(route); router.addRoute(route);
} }
} }
}
// 刷新替换原先路由,确保addRoutes已完成 // F5 刷新时,如果目标路由有效,直接跳转,不进行重定向
if (isRefresh && await isRouteAccessible(to, accessRoutes)) {
console.log(`F5 刷新,目标路由有效,直接跳转: ${to.path}`);
next({ ...to, replace: true }); next({ ...to, replace: true });
} catch (error: any) { return;
console.error(`[${to.path}]: ${error.message}`); }
await user.fnLogOut();
next({ name: 'Login' }); if (await isRouteAccessible(to, accessRoutes)) {
next({ ...to, replace: true });
} else {
// F5 刷新时,如果目标路由无效,优先跳转到首页
if (isRefresh) {
console.log(`F5 刷新,目标路由无效,跳转到首页: ${to.path}`);
next({ name: 'Index', replace: true });
return;
}
const validRedirect = findValidRedirect(to, accessRoutes);
if (validRedirect && validRedirect !== to.path) {
redirectCount.set(redirectKey, currentCount + 1);
console.log(`重定向到有效路由: ${to.path} -> ${validRedirect}`);
next({ path: validRedirect, replace: true });
} else {
next({ name: 'Index', replace: true });
}
} }
} else { } else {
next({ name: 'Index', replace: true });
}
} catch (error) {
console.error('Route guard error:', error);
next(`/login?redirect=${to.fullPath}`);
}
} else if (
to.meta.neType &&
to.meta.neType.length > 0 &&
!useNeInfoStore().fnHasNe(to.meta.neType)
) {
// 找到有效的替代路由
const validRedirect = findValidAlternative(to);
if (validRedirect && validRedirect !== to.path) {
redirectCount.set(redirectKey, currentCount + 1);
console.log(`403 重定向: ${to.path} -> ${validRedirect}`);
next({ path: validRedirect, replace: true });
} else {
console.log(`无有效替代路由,跳转到权限错误页面: ${to.path}`);
next({ name: 'NotPermission' });
}
} else {
// 清除重定向计数
redirectCount.clear();
next(); next();
} }
} }
} }
}); });
/**
* 检查路由是否可访问
*/
async function isRouteAccessible(to: any, accessRoutes: any[]): Promise<boolean> {
// 检查路由是否存在于 accessRoutes 中
const routeExists = findRouteInAccessRoutes(to.path, accessRoutes);
console.log(`检查路由可访问性: ${to.path}`, routeExists ? '找到' : '未找到');
if (!routeExists) return false;
// 检查网元类型
if (to.meta?.neType && to.meta.neType.length > 0) {
const hasNe = useNeInfoStore().fnHasNe(to.meta.neType);
console.log(`网元类型检查: ${to.meta.neType}`, hasNe ? '有效' : '无效');
return hasNe;
}
return true;
}
/**
* 在访问路由中查找指定路径
*/
function findRouteInAccessRoutes(targetPath: string, routes: any[], parentPath: string = ''): any {
for (const route of routes) {
// 构建完整路径
let fullPath = route.path;
// 如果不是绝对路径,需要拼接父路径
if (!fullPath.startsWith('/')) {
const cleanParentPath = parentPath.replace(/\/$/, ''); // 移除末尾斜杠
const cleanRoutePath = fullPath.replace(/^\//, ''); // 移除开头斜杠
fullPath = cleanParentPath + '/' + cleanRoutePath;
}
// 标准化路径,移除多余的斜杠
fullPath = fullPath.replace(/\/+/g, '/');
console.log(`匹配路径: ${targetPath} vs ${fullPath}`);
if (fullPath === targetPath) {
return route;
}
// 递归查找子路由
if (route.children && route.children.length > 0) {
const found = findRouteInAccessRoutes(targetPath, route.children, fullPath);
if (found) return found;
}
}
return null;
}
/**
* 查找有效的重定向目标
*/
function findValidRedirect(to: any, accessRoutes: any[]): string | null {
const neStore = useNeInfoStore();
console.log(`查找重定向目标: ${to.path}`);
// 1. 查找父路由的 redirect
const parentRoute = findParentRouteWithRedirect(to.path, accessRoutes);
if (parentRoute?.redirect) {
console.log(`找到父路由重定向: ${parentRoute.path} -> ${parentRoute.redirect}`);
// 验证 redirect 目标是否有效
const redirectTarget = findRouteInAccessRoutes(parentRoute.redirect, accessRoutes);
if (redirectTarget &&
(!redirectTarget.meta?.neType || neStore.fnHasNe(redirectTarget.meta.neType))) {
return parentRoute.redirect;
}
}
// 2. 查找同级的第一个有效路由
const siblingRoute = findFirstValidSibling(to.path, accessRoutes);
if (siblingRoute) {
console.log(`找到同级有效路由: ${siblingRoute}`);
return siblingRoute;
}
// 3. 查找根级别的第一个有效路由
const rootRoute = findFirstValidRootRoute(accessRoutes);
if (rootRoute) {
console.log(`找到根级有效路由: ${rootRoute}`);
return rootRoute;
}
return null;
}
/**
* 查找具有 redirect 的父路由
*/
function findParentRouteWithRedirect(targetPath: string, routes: any[], parentPath: string = ''): any {
for (const route of routes) {
let fullPath = route.path;
if (!fullPath.startsWith('/')) {
const cleanParentPath = parentPath.replace(/\/$/, '');
const cleanRoutePath = fullPath.replace(/^\//, '');
fullPath = cleanParentPath + '/' + cleanRoutePath;
}
fullPath = fullPath.replace(/\/+/g, '/');
// 检查是否是父路径且有 redirect
if (targetPath.startsWith(fullPath) &&
route.redirect &&
fullPath !== targetPath) {
return { ...route, path: fullPath };
}
// 递归查找
if (route.children && route.children.length > 0) {
const found = findParentRouteWithRedirect(targetPath, route.children, fullPath);
if (found) return found;
}
}
return null;
}
/**
* 查找同级的第一个有效路由
*/
function findFirstValidSibling(targetPath: string, routes: any[], parentPath: string = ''): string | null {
const neStore = useNeInfoStore();
// 获取父路径
const parentRouteResult = findParentOfTarget(targetPath, routes, parentPath);
if (!parentRouteResult) return null;
const { parentRoute, parentFullPath } = parentRouteResult;
if (parentRoute.children) {
for (const sibling of parentRoute.children) {
let siblingFullPath = sibling.path;
if (!siblingFullPath.startsWith('/')) {
const cleanParentPath = parentFullPath.replace(/\/$/, '');
const cleanSiblingPath = siblingFullPath.replace(/^\//, '');
siblingFullPath = cleanParentPath + '/' + cleanSiblingPath;
}
siblingFullPath = siblingFullPath.replace(/\/+/g, '/');
if (siblingFullPath !== targetPath &&
(!sibling.meta?.neType || neStore.fnHasNe(sibling.meta.neType))) {
return siblingFullPath;
}
}
}
return null;
}
/**
* 查找目标路径的父路由
*/
function findParentOfTarget(targetPath: string, routes: any[], parentPath: string = ''): { parentRoute: any, parentFullPath: string } | null {
for (const route of routes) {
let fullPath = route.path;
if (!fullPath.startsWith('/')) {
const cleanParentPath = parentPath.replace(/\/$/, '');
const cleanRoutePath = fullPath.replace(/^\//, '');
fullPath = cleanParentPath + '/' + cleanRoutePath;
}
fullPath = fullPath.replace(/\/+/g, '/');
// 检查子路由
if (route.children && route.children.length > 0) {
for (const child of route.children) {
let childFullPath = child.path;
if (!childFullPath.startsWith('/')) {
const cleanParentPath = fullPath.replace(/\/$/, '');
const cleanChildPath = childFullPath.replace(/^\//, '');
childFullPath = cleanParentPath + '/' + cleanChildPath;
}
childFullPath = childFullPath.replace(/\/+/g, '/');
if (childFullPath === targetPath) {
return { parentRoute: route, parentFullPath: fullPath };
}
}
// 递归查找
const found = findParentOfTarget(targetPath, route.children, fullPath);
if (found) return found;
}
}
return null;
}
/**
* 查找根级别的第一个有效路由
*/
function findFirstValidRootRoute(routes: any[]): string | null {
const neStore = useNeInfoStore();
for (const route of routes) {
if (!route.meta?.neType || neStore.fnHasNe(route.meta.neType)) {
if (route.children && route.children.length > 0) {
// 如果有子路由,返回第一个有效子路由的完整路径
for (const child of route.children) {
let childFullPath = child.path;
if (!childFullPath.startsWith('/')) {
const cleanParentPath = route.path.replace(/\/$/, '');
const cleanChildPath = childFullPath.replace(/^\//, '');
childFullPath = cleanParentPath + '/' + cleanChildPath;
}
childFullPath = childFullPath.replace(/\/+/g, '/');
if (!child.meta?.neType || neStore.fnHasNe(child.meta.neType)) {
return childFullPath;
}
}
} else {
return route.path;
}
}
}
return null;
}
/**
* 查找有效的替代路由(用于 403 情况)
*/
function findValidAlternative(to: any): string | null {
const routerStore = useRouterStore();
const buildRoutes = routerStore.buildRouterData;
return findValidRedirect(to, buildRoutes);
}
export default router; export default router;
+11 -1
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;
+26
View File
@@ -109,6 +109,32 @@ const useNeInfoStore = defineStore('neinfo', {
} }
return res; return res;
}, },
/**
* 含有网元
* @param metaNeType ['udm', 'ims', 'udm+ims', 'SGWC'] 支持大小写
* @returns boolean
*/
fnHasNe(metaNeType: string[]) {
if (this.neList.length > 0) {
const neTypes = this.neSelectOtions.map(item => item.value);
let match = false; // 匹配
for (const netype of metaNeType) {
if (netype.indexOf('+') > -1) {
metaNeType = netype.split('+');
match = true;
break;
}
}
if (match) {
// 同时匹配
return metaNeType.every(item => neTypes.includes(item.toUpperCase()));
}
// 有一种
return metaNeType.some(item => neTypes.includes(item.toUpperCase()));
}
return false;
},
}, },
}); });
+97 -5
View File
@@ -3,9 +3,11 @@ import type {
RouteComponent, RouteComponent,
RouteLocationRaw, RouteLocationRaw,
RouteMeta, RouteMeta,
RouteRecord,
RouteRecordRaw, RouteRecordRaw,
} from 'vue-router'; } from 'vue-router';
import { getRouters } from '@/api/router'; import { getRouters } from '@/api/router';
import useNeInfoStore from '@/store/modules/neinfo';
import BasicLayout from '@/layouts/BasicLayout.vue'; import BasicLayout from '@/layouts/BasicLayout.vue';
import BlankLayout from '@/layouts/BlankLayout.vue'; import BlankLayout from '@/layouts/BlankLayout.vue';
import LinkLayout from '@/layouts/LinkLayout.vue'; import LinkLayout from '@/layouts/LinkLayout.vue';
@@ -48,19 +50,95 @@ const useRouterStore = defineStore('router', {
async generateRoutes() { async generateRoutes() {
const res = await getRouters(); const res = await getRouters();
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
const buildRoutes = buildRouters(res.data.concat()); // 获取当前网元类型
const neTypes = useNeInfoStore().getNeSelectOtions.map(v => v.value);
// 先过滤
const filteredRoutes = this.clearMenuItemByNeList(res.data.concat(), neTypes);
// 再 build
const buildRoutes = buildRouters(filteredRoutes);
this.buildRouterData = buildRoutes; this.buildRouterData = buildRoutes;
return buildRoutes; return buildRoutes;
} }
return []; return [];
}, },
/**
* 根据网元类型过滤菜单
* @param routes 经过clearMenuItem(router.getRoutes())处理
* @param neTypes 网元类型
* @returns 过滤后的菜单
*/
clearMenuItemByNeList(
routes: RouteRecord[] | RouteRecordRaw[],
neTypes: string[]
): RouteRecordRaw[] {
return routes
.map((item: RouteRecord | RouteRecordRaw) => {
const finalItem = { ...item };
// 过滤网元类型
if (
Array.isArray(finalItem.meta?.neType) &&
finalItem.meta?.neType.length > 0
) {
let metaNeType: string[] = finalItem.meta.neType;
let match = false; // 匹配
for (const netype of metaNeType) {
if (netype.indexOf('+') > -1) {
metaNeType = netype.split('+');
match = true;
break;
}
}
if (match && !metaNeType.every(item => neTypes.includes(item))) {
// 同时匹配
return null;
} else if (!metaNeType.some(item => neTypes.includes(item))) {
// 有一种
return null;
}
}
// 有子菜单进行递归
if (finalItem.children && finalItem.children.length > 0) {
const children = this.clearMenuItemByNeList(
finalItem.children,
neTypes
);
// 如果子菜单都被过滤掉了,就不显示
if (children.length === 0) {
return null;
}
finalItem.children = children;
// 只重定向到第一个可用的子菜单
// finalItem.redirect = finalItem.children[0].path;
// console.log(`finalItem.redirect`, finalItem.redirect);
// 如果有子菜单,且没有重定向,则设置重定向到第一个子菜单
if (children.length > 0) {
let childPath = children[0].path;
if (!childPath.startsWith('/')) {
// 确保父路径以 / 结尾,子路径不以 / 开头
const parentPath = finalItem.path.replace(/\/$/, '');
childPath = parentPath + '/' + childPath.replace(/^\//, '');
}
finalItem.redirect = childPath;
}
return finalItem;
}
delete finalItem.children;
return finalItem;
})
.filter(item => item) as RouteRecordRaw[];
},
}, },
}); });
/**异步路由类型 */ /**异步路由类型 */
type RecordRaws = { type RecordRaws = {
path: string; path: string;
name: string; name?: string;
meta: RouteMeta; meta: RouteMeta;
redirect: RouteLocationRaw; redirect: RouteLocationRaw;
component: string; component: string;
@@ -75,17 +153,18 @@ type RecordRaws = {
* @param recordRaws 异步路由列表 * @param recordRaws 异步路由列表
* @returns 可添加的路由列表 * @returns 可添加的路由列表
*/ */
function buildRouters(recordRaws: RecordRaws[]): RouteRecordRaw[] { function buildRouters(recordRaws: RouteRecordRaw[]): RouteRecordRaw[] {
const routers: RouteRecordRaw[] = []; const routers: RouteRecordRaw[] = [];
for (const item of recordRaws) { for (const item of recordRaws) {
// 过滤旧前端菜单 是layui的菜单跳过 // 过滤旧前端菜单 是layui的菜单跳过
if (['', '/page"'].includes(item.path)) { if (['', '/page"'].includes(item.path)) {
continue; continue;
} }
// 路由页面组件 // 路由页面组件
let component: RouteComponent = {}; let component: RouteComponent = {};
if (item.component) { if (item.component) {
const comp = item.component; const comp = item.component as unknown as string; // 添加类型断言
if (comp === MENU_COMPONENT_LAYOUT_BASIC) { if (comp === MENU_COMPONENT_LAYOUT_BASIC) {
component = BasicLayout; component = BasicLayout;
} else if (comp === MENU_COMPONENT_LAYOUT_BLANK) { } else if (comp === MENU_COMPONENT_LAYOUT_BLANK) {
@@ -102,6 +181,17 @@ function buildRouters(recordRaws: RecordRaws[]): RouteRecordRaw[] {
let children: RouteRecordRaw[] = []; let children: RouteRecordRaw[] = [];
if (item.children && item.children.length > 0) { if (item.children && item.children.length > 0) {
children = buildRouters(item.children); children = buildRouters(item.children);
// 如果没有 redirect 但有子菜单,设置 redirect 到第一个子菜单
if (!item.redirect && children.length > 0) {
let childPath = children[0].path;
if (!childPath?.startsWith('/')) {
childPath = item.path.replace(/\/$/, '') + '/' + (childPath || '').replace(/^\//, '');
}
// 修改 item 的 redirect(需要类型断言)
(item as any).redirect = childPath;
}
} }
// 对元数据特殊参数进行处理 // 对元数据特殊参数进行处理
@@ -109,7 +199,9 @@ function buildRouters(recordRaws: RecordRaws[]): RouteRecordRaw[] {
if (!metaIcon.startsWith('icon-')) { if (!metaIcon.startsWith('icon-')) {
metaIcon = ''; metaIcon = '';
} }
item.meta = Object.assign(item.meta, {
// 更新 meta(需要类型断言)
(item as any).meta = Object.assign(item.meta || {}, {
icon: metaIcon, icon: metaIcon,
}); });
+2
View File
@@ -9,5 +9,7 @@ declare module 'vue-router' {
permissions?: string[]; permissions?: string[];
/**角色 */ /**角色 */
roles?: string[]; roles?: string[];
/**网元类型信息 */
neType?: string[];
} }
} }
+2
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;
+5 -2
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-DD HH: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);
} }
+1 -1
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(
+32 -4
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})`;
+42 -2
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;
}
+38 -3
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}`;
}
+647
View File
@@ -0,0 +1,647 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
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, parseStrToDate } from '@/utils/date-utils';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { listCallBack, updateStatus } from '@/api/agentManage/callback';
import useNeInfoStore from '@/store/modules/neinfo';
import useDictStore from '@/store/modules/dict';
import useI18n from '@/hooks/useI18n';
import { message } from 'ant-design-vue';
import { ProModal } from 'antdv-pro-modal';
import { create } from 'domain';
import { commentProps } from 'ant-design-vue/es/comment';
const { getDict } = useDictStore();
const { t } = useI18n();
let dictStatus = ref<DictType[]>([]);
/**网元参数 */
let neOtions = ref<Record<string, any>[]>([]);
/**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']);
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neId: '001',
/**记录时间 */
startTime: '',
endTime: '',
callerNumber: '',
agentName: '',
status: undefined,
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
neId: '001',
status: undefined,
beginTime: '',
endTime: '',
callerNumber: '',
agentName: '',
pageNum: 1,
pageSize: 20,
});
queryRangePicker.value = ['', ''];
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: object[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: true,
data: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('views.agentManage.callback.ticketId'),
dataIndex: 'ticketId',
align: 'center',
width: 5,
},
{
title: t('views.agentManage.callback.agentName'),
dataIndex: 'agentName',
align: 'center',
width: 5,
},
{
title: t('views.agentManage.callback.callerIdNumber'),
dataIndex: 'callerNumber',
align: 'center',
width: 5,
},
{
title: t('views.agentManage.callback.calleeIdNumber'),
dataIndex: 'calleeNumber',
align: 'center',
width: 5,
},
{
title: t('views.agentManage.callback.status'),
dataIndex: 'status',
key: 'status',
align: 'center',
width: 4,
},
{
title: t('views.agentManage.callback.startTime'),
dataIndex: 'createdAt',
align: 'center',
width: 6,
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value / 1000);
},
},
{
title: t('views.agentManage.callback.updateTime'),
dataIndex: 'updatedAt',
align: 'center',
width: 6,
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value / 1000);
},
},
// {
// title: t('views.agentManage.callback.msdData'),
// dataIndex: 'msdData',
// align: 'center',
// width: 6,
// },
{
title: t('common.operate'),
key: 'callbackId',
align: 'left',
width: 6,
},
];
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**查询备份信息列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
if (!queryRangePicker.value) {
queryRangePicker.value = ['', ''];
}
queryParams.startTime = queryRangePicker.value[0];
queryParams.endTime = queryRangePicker.value[1];
listCallBack(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
tablePagination.total = res.total;
tableState.data = res.data;
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
}
tableState.loading = false;
});
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**详情框是否显示 */
openByView: boolean;
/**新增框或修改框是否显示 */
openByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
/**更新加载数据按钮 loading */
loadDataLoading: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
openByView: false,
openByEdit: false,
title: 'callback',
from: {
id: undefined,
ticketId: '',
agentName: '',
callerNumber: '',
calleeNumber: '',
agentEmail: '',
agentMobile: '',
status: '',
createdAt: '',
comment: '',
},
confirmLoading: false,
loadDataLoading: false,
});
function fnModalVisibleByEdit(record: any) {
modalState.title = t('common.viewText') + t('views.agentManage.callback.title');
modalState.openByEdit = true;
modalState.from = Object.assign(modalState.from, record, {
createdAt: record.createdAt ? parseDateToStr(record.createdAt / 1000) : '',
});
}
function fnModalOk() {
const from = Object.assign({}, toRaw(modalState.from));
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
updateStatus(from)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.msgSuccess', { msg: modalState.title }),
duration: 3,
});
fnGetList(1);
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
fnModalCancel();
modalState.confirmLoading = false;
});
}
function fnModalCancel() {
modalState.openByEdit = false;
//modalState.openByView = false;
//modalStateFrom.resetFields();
}
onMounted(() => {
getDict('callback_status').then(res => {
dictStatus.value = res;
});
// 获取网元网元列表
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 === 'MF') {
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();
});
});
</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="PSAP对象" name="neId ">
<a-select
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
@change="fnGetList(1)"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.agentManage.callback.callerIdNumber')"
name="callerNumber"
>
<a-input
v-model:value="queryParams.callerNumber"
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.agentManage.callback.agentName')"
name="agentName"
>
<a-input
v-model:value="queryParams.agentName"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon>
<SearchOutlined />
</template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon>
<ClearOutlined />
</template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.agentManage.callback.status')"
name="status"
>
<a-select
v-model:value="queryParams.status"
:options="dictStatus"
:allow-clear="true"
>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-form-item
:label="t('views.agentManage.callback.startTime')"
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> </template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon>
<ReloadOutlined />
</template>
</a-button>
</a-tooltip>
<a-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: 1500, y: 400 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<DictTag :options="dictStatus" :value="record.status" />
</template>
<template v-if="column.key === 'callbackId'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.viewText') }}</template>
<a-button
type="link"
@click.prevent="fnModalVisibleByEdit(record)"
>
<template #icon><InfoCircleOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
</a-table>
</a-card>
<ProModal
:drag="true"
:width="800"
:destroyOnClose="true"
style="top: 0px"
:body-style="{ maxHeight: '600px', 'overflow-y': 'auto' }"
:keyboard="false"
:mask-closable="false"
:open="modalState.openByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@cancel="fnModalCancel"
@ok="fnModalOk"
:okText=" t('views.faultManage.activeAlarm.confirm') "
>
<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.agentManage.callback.ticketId')"
name="ticketId"
>
<a-input
:disabled="true"
v-model:value="modalState.from.ticketId"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.agentManage.callback.agentName')"
name="agentName"
>
<a-input
:disabled="true"
v-model:value="modalState.from.agentName"
></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.agentManage.callback.callerIdNumber')"
name="callerNumber"
>
<a-input
:disabled="true"
v-model:value="modalState.from.callerNumber"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.agentManage.callback.calleeIdNumber')"
name="calleeNumber"
>
<a-input
:disabled="true"
v-model:value="modalState.from.calleeNumber"
></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.agentManage.callback.agentEmail')"
name="agentEmail"
>
<a-input
:disabled="true"
v-model:value="modalState.from.agentEmail"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.agentManage.callback.agentMobile')"
name="agentMobile"
>
<a-input
:disabled="true"
v-model:value="modalState.from.agentMobile"
></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.agentManage.callback.status')"
name="status"
>
<a-input
:disabled="true"
v-model:value="modalState.from.status"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.agentManage.callback.startTime')"
name="createdAt"
>
<a-input
:disabled="true"
v-model:value="modalState.from.createdAt"
></a-input>
</a-form-item>
</a-col>
</a-row>
<a-form-item
:label="t('views.agentManage.callback.comment')"
:label-col="{ span: 3 }"
name="comment"
>
<a-input
:disabled="true"
v-model:value="modalState.from.comment"
></a-input>
</a-form-item>
</a-form>
</ProModal>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
.alarmTitleText {
max-width: 300px;
cursor: pointer;
}
</style>
+216
View File
@@ -0,0 +1,216 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
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 { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { listCallings } from '@/api/agentManage/callings';
import useNeInfoStore from '@/store/modules/neinfo';
import useDictStore from '@/store/modules/dict';
import useI18n from '@/hooks/useI18n';
const { getDict } = useDictStore();
const { t } = useI18n();
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neId: '001',
/**记录时间 */
beginTime: '',
endTime: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: object[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: true,
data: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('views.agentManage.callings.callerIdNumber'),
dataIndex: 'callerIdNumber',
align: 'center',
width: 5,
},
{
title: t('views.agentManage.callings.calleeIdNumber'),
dataIndex: 'calleeIdNumber',
align: 'center',
width: 5,
},
{
title: t('views.agentManage.callings.startTime'),
dataIndex: 'createdTime',
align: 'center',
width: 6,
},
{
title: t('views.agentManage.callings.answeredTime'),
dataIndex: 'answeredTime',
align: 'center',
width: 4,
},
{
title: t('views.agentManage.callings.callDuration'),
dataIndex: 'callDuration',
align: 'center',
width: 5,
},
];
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**查询备份信息列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if(pageNum){
queryParams.pageNum = pageNum;
}
listCallings(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
tablePagination.total = res.total;
tableState.data = res.data;
if (tablePagination.total <=(queryParams.pageNum - 1) * tablePagination.pageSize &&queryParams.pageNum !== 1) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
}
tableState.loading = false;
});
}
onMounted(() => {
// 获取列表数据
fnGetList();
});
</script>
<template>
<PageContainer>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title> </template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<a-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: 1500, y: 400 }"
>
</a-table>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
.alarmTitleText {
max-width: 300px;
cursor: pointer;
}
</style>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+157 -210
View File
@@ -2,7 +2,14 @@
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import { ColumnsType } from 'ant-design-vue/es/table'; import { ColumnsType } from 'ant-design-vue/es/table';
import { message } from 'ant-design-vue/es'; import { message } from 'ant-design-vue/es';
import { reactive, ref, onMounted, onBeforeUnmount, markRaw } from 'vue'; import {
reactive,
ref,
onMounted,
onBeforeUnmount,
markRaw,
useTemplateRef,
} from 'vue';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { TooltipComponent } from 'echarts/components'; import { TooltipComponent } from 'echarts/components';
import { GaugeChart } from 'echarts/charts'; import { GaugeChart } from 'echarts/charts';
@@ -32,24 +39,12 @@ echarts.use([
]); ]);
/**图DOM节点实例对象 */ /**图DOM节点实例对象 */
const statusBar = ref<HTMLElement | undefined>(undefined); const statusBar = useTemplateRef<HTMLDivElement>('statusBar');
/**图实例对象 */ /**图实例对象 */
const statusBarChart = ref<any>(null); 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 = [ let tableColumns: ColumnsType = [
{ {
title: t('views.index.object'), title: t('views.index.object'),
@@ -67,7 +62,9 @@ let tableColumns: ColumnsType = [
dataIndex: 'serverState', dataIndex: 'serverState',
align: 'left', align: 'left',
customRender(opt) { customRender(opt) {
if (opt.value?.refreshTime) return parseDateToStr(opt.value?.refreshTime); if (opt.value?.refreshTime) {
return parseDateToStr(opt.value?.refreshTime, 'HH:mm:ss');
}
return '-'; return '-';
}, },
}, },
@@ -104,12 +101,13 @@ let tableColumns: ColumnsType = [
}, },
}, },
]; ];
/**表格状态类型 */ /**表格状态类型 */
type TabeStateType = { type TabeStateType = {
/**加载等待 */ /**加载等待 */
loading: boolean; loading: boolean;
/**记录数据 */ /**记录数据 */
data: object[]; data: Record<string, any>[];
/**勾选记录 */ /**勾选记录 */
selectedRowKeys: (string | number)[]; selectedRowKeys: (string | number)[];
}; };
@@ -121,66 +119,63 @@ let tableState: TabeStateType = reactive({
selectedRowKeys: [], selectedRowKeys: [],
}); });
/**表格状态 */ /**状态 */
let nfInfo: any = reactive({ let serverState: any = ref({});
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) { async function fnGetList(reload: boolean = false) {
if (tableState.loading) return; tableState.loading = !reload;
one && (tableState.loading = true); try {
listAllNeInfo({ bandStatus: true }).then(res => { const res = await listAllNeInfo({ bandStatus: true });
tableState.data = res.data; tableState.data = res.data;
} catch (error) {
console.error(error);
tableState.data = [];
}
tableState.loading = false; tableState.loading = false;
if (tableState.data.length == 0) {
return;
}
var rightNum = 0; var rightNum = 0;
var errorNum = 0; var errorNum = 0;
res.data.forEach((item: any) => { for (const v of tableState.data) {
if (item.serverState.online) { if (v?.serverState?.online) {
rightNum++; rightNum++;
} else { } else {
errorNum++; errorNum++;
} }
}
// 初始
if (!reload) {
// 选择第一个
if (tableState.data.length > 0) {
const id = tableState.data[0].id;
fnTableSelectedRowKeys([id]);
} else {
fnTableSelectedRowKeys(tableState.selectedRowKeys);
}
if (statusBar.value) {
fnDesign(statusBar.value, rightNum, errorNum);
}
} else {
statusBarChart.value.setOption({
series: [
{
data: [
{ value: rightNum, name: t('views.index.normal') },
{ value: errorNum, name: t('views.index.abnormal') },
],
},
],
}); });
}
}
function fnDesign(container: HTMLElement, rightNum: number, errorNum: number) {
/// 图表数据
const optionData: any = { const optionData: any = {
title: { title: {
text: '', text: '',
@@ -194,10 +189,10 @@ function fnGetList(one: boolean) {
orient: 'vertical', orient: 'vertical',
left: 'left', left: 'left',
}, },
color: indexColor.value.map(item => item.tagClass), color: dict.indexStatus.map(item => item.tagClass),
series: [ series: [
{ {
name: t('views.index.realNeStatus'), name: t('views.index.runStatus'),
type: 'pie', type: 'pie',
radius: '70%', radius: '70%',
center: ['50%', '50%'], center: ['50%', '50%'],
@@ -205,30 +200,11 @@ function fnGetList(one: boolean) {
{ value: rightNum, name: t('views.index.normal') }, { value: rightNum, name: t('views.index.normal') },
{ value: errorNum, name: t('views.index.abnormal') }, { 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')); statusBarChart.value = markRaw(echarts.init(container, 'light'));
} statusBarChart.value.setOption(optionData);
option && statusBarChart.value.setOption(option);
// 创建 ResizeObserver 实例 // 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => { var observer = new ResizeObserver(entries => {
@@ -240,66 +216,43 @@ function fnDesign(container: HTMLElement | undefined, option: any) {
observer.observe(container); observer.observe(container);
} }
/**抽屉 网元详细信息 */ /**表格多选 */
const open = ref(false); function fnTableSelectedRowKeys(keys: (string | number)[]) {
const closeDrawer = () => { if (keys.length <= 0) return;
open.value = false; const id = keys[0];
}; const row: any = tableState.data.find((item: any) => item.id === id);
/**抽屉 网元详细信息 */ if (!row) {
/**监听表格行事件*/
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); message.error(t('views.index.neStatus'), 2);
return false; return;
} else { }
const totalMemInKB = pronData.mem?.totalMem; const neState = row.serverState;
const nfUsedMemInKB = pronData.mem?.nfUsedMem; if (!neState?.online) {
const sysMemUsageInKB = pronData.mem?.sysMemUsage; message.error(t('views.index.neStatus'), 2);
return;
// 将KB转换为MB }
tableState.selectedRowKeys = keys;
// Mem 将KB转换为MB
const totalMemInKB = neState.mem?.totalMem;
const nfUsedMemInKB = neState.mem?.nfUsedMem;
const sysMemUsageInKB = neState.mem?.sysMemUsage;
const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100; const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100;
const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100; const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
const sysMemUsageInMB = const sysMemUsageInMB = Math.round((sysMemUsageInKB / 1024) * 100) / 100;
Math.round((sysMemUsageInKB / 1024) * 100) / 100;
//渲染详细信息 // CPU
pronInfo = { const nfCpu = neState.cpu?.nfCpuUsage;
hostName: pronData.hostname, const sysCpu = neState.cpu?.sysCpuUsage;
osInfo: pronData.os, const nfCpuP = Math.round(nfCpu) / 100;
ipAddress: pronData.neIP, const sysCpuP = Math.round(sysCpu) / 100;
version: pronData.version,
cpuUse: serverState.value = Object.assign(
pronData.neName + {
':' + cpuUse: `NE:${nfCpuP}%; SYS:${sysCpuP}%`,
pronData.cpu?.nfCpuUsage / 100 + memoryUse: `Total: ${totalMemInMB}MB; NE: ${nfUsedMemInMB}MB; SYS: ${sysMemUsageInMB}MB`,
'%; ' +
'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;
}, },
}; neState
);
} }
let timer: any;
/** /**
* 国际化翻译转换 * 国际化翻译转换
@@ -312,63 +265,48 @@ function fnLocale() {
appStore.setTitle(title); appStore.setTitle(title);
} }
/**字典数据 */
let dict: {
/**网元信息状态 */
neInfoStatus: DictType[];
/**主页状态 */
indexStatus: DictType[];
} = reactive({
neInfoStatus: [],
indexStatus: [],
});
let timer: any;
onMounted(() => { onMounted(() => {
getDict('index_status') // 初始字典数据
.then(res => { Promise.allSettled([getDict('ne_info_status'), getDict('index_status')])
if (res.length > 0) { .then(resArr => {
indexColor.value = res; if (resArr[0].status === 'fulfilled') {
dict.neInfoStatus = resArr[0].value;
}
if (resArr[1].status === 'fulfilled') {
dict.indexStatus = resArr[1].value;
} }
}) })
.finally(() => { .finally(async () => {
fnLocale(); fnLocale();
await fnGetList(false);
timer = setInterval(() => {
if (!timer) return;
fnGetList(true); fnGetList(true);
timer = setInterval(() => fnGetList(false), 10000); // 每隔10秒执行一次 }, 10_000); // 每隔10秒执行一次
}); });
}); });
// 在组件卸载之前清除定时器 // 在组件卸载之前清除定时器
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearInterval(timer); clearInterval(timer);
timer = null;
}); });
</script> </script>
<template> <template>
<PageContainer :breadcrumb="{}"> <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-row :gutter="16">
<a-col :lg="14" :md="16" :xs="24"> <a-col :lg="14" :md="16" :xs="24">
<!-- 表格列表 --> <!-- 表格列表 -->
@@ -377,20 +315,20 @@ onBeforeUnmount(() => {
row-key="id" row-key="id"
size="small" size="small"
:columns="tableColumns" :columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data" :data-source="tableState.data"
:loading="tableState.loading"
:pagination="false" :pagination="false"
:scroll="{ x: true }" :scroll="{ x: true }"
:customRow="rowClick" :row-selection="{
type: 'radio',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
> >
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'"> <template v-if="column.key === 'status'">
<div v-if="record.serverState.online"> <DictTag :options="dict.neInfoStatus" :value="record.status" />
<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>
</template> </template>
</a-table> </a-table>
@@ -404,7 +342,8 @@ onBeforeUnmount(() => {
<div style="width: 100%; min-height: 200px" ref="statusBar"></div> <div style="width: 100%; min-height: 200px" ref="statusBar"></div>
</a-card> </a-card>
<a-card <a-card
:title="t('views.index.mark')" :loading="tableState.loading"
:title="`${t('views.index.mark')} - ${serverState.neName || 'OMC'}`"
style="margin-top: 16px" style="margin-top: 16px"
size="small" size="small"
> >
@@ -413,25 +352,33 @@ onBeforeUnmount(() => {
:column="1" :column="1"
:label-style="{ width: '160px' }" :label-style="{ width: '160px' }"
> >
<a-descriptions-item :label="t('views.index.object')">{{ <a-descriptions-item :label="t('views.index.hostName')">
nfInfo.obj {{ serverState.hostname }}
}}</a-descriptions-item> </a-descriptions-item>
<template v-if="nfInfo.obj === 'OMC'"> <a-descriptions-item :label="t('views.index.osInfo')">
<a-descriptions-item :label="t('views.index.versionNum')">{{ {{ serverState.os }}
nfInfo.version </a-descriptions-item>
}}</a-descriptions-item> <a-descriptions-item :label="t('views.index.ipAddress')">
<a-descriptions-item :label="t('views.index.systemStatus')">{{ {{ serverState.neIP }}
nfInfo.status </a-descriptions-item>
}}</a-descriptions-item> <a-descriptions-item :label="t('views.index.version')">
</template> {{ serverState.version }}
<template v-else> </a-descriptions-item>
<a-descriptions-item :label="t('views.index.serialNum')">{{ <a-descriptions-item :label="t('views.index.capability')">
nfInfo.serialNum {{ serverState.capability }}
}}</a-descriptions-item> </a-descriptions-item>
<a-descriptions-item :label="t('views.index.expiryDate')">{{ <a-descriptions-item :label="t('views.index.cpuUse')">
nfInfo.outTimeDate {{ serverState.cpuUse }}
}}</a-descriptions-item> </a-descriptions-item>
</template> <a-descriptions-item :label="t('views.index.memoryUse')">
{{ serverState.memoryUse }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.serialNum')">
{{ serverState.sn }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.expiryDate')">
{{ serverState.expire }}
</a-descriptions-item>
</a-descriptions> </a-descriptions>
</a-card> </a-card>
</a-col> </a-col>
+139 -81
View File
@@ -11,14 +11,21 @@ import {
RESULT_CODE_SUCCESS, RESULT_CODE_SUCCESS,
} from '@/constants/result-constants'; } from '@/constants/result-constants';
import useDictStore from '@/store/modules/dict'; import useDictStore from '@/store/modules/dict';
import useNeInfoStore from '@/store/modules/neinfo';
import { listAMFDataUE, delAMFDataUE, exportAMFDataUE } from '@/api/neData/amf'; import { listAMFDataUE, delAMFDataUE, exportAMFDataUE } from '@/api/neData/amf';
import { parseDateToStr } from '@/utils/date-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket'; import { OptionsType, WS } from '@/plugins/ws-websocket';
import dayjs, { Dayjs } from 'dayjs';
import saveAs from 'file-saver'; import saveAs from 'file-saver';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
import { useClipboard } from '@vueuse/core';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n(); const { t } = useI18n();
const { getDict } = useDictStore(); const { getDict } = useDictStore();
const ws = new WS(); const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true }); const queue = new PQueue({ concurrency: 1, autoStart: true });
/**网元可选 */
let neOtions = ref<Record<string, any>[]>([]);
/**字典数据 */ /**字典数据 */
let dict: { let dict: {
@@ -35,7 +42,10 @@ let dict: {
}); });
/**开始结束时间 */ /**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']); let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
dayjs().startOf('hour'),
dayjs().endOf('hour'),
]);
/**查询参数 */ /**查询参数 */
let queryParams = reactive({ let queryParams = reactive({
@@ -47,9 +57,9 @@ let queryParams = reactive({
sortField: 'timestamp', sortField: 'timestamp',
sortOrder: 'desc', sortOrder: 'desc',
/**开始时间 */ /**开始时间 */
startTime: '', startTime: undefined as undefined | number,
/**结束时间 */ /**结束时间 */
endTime: '', endTime: undefined as undefined | number,
/**当前页数 */ /**当前页数 */
pageNum: 1, pageNum: 1,
/**每页条数 */ /**每页条数 */
@@ -67,7 +77,7 @@ function fnQueryReset() {
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
}); });
queryRangePicker.value = ['', '']; queryRangePicker.value = [dayjs().startOf('hour'), dayjs().endOf('hour')];
tablePagination.current = 1; tablePagination.current = 1;
tablePagination.pageSize = 20; tablePagination.pageSize = 20;
fnGetList(); fnGetList();
@@ -141,9 +151,15 @@ let tableColumns: ColumnsType = [
{ {
title: t('views.dashboard.ue.time'), title: t('views.dashboard.ue.time'),
dataIndex: 'eventJSON', dataIndex: 'eventJSON',
key: 'time',
align: 'left', align: 'left',
width: 150, width: 150,
customRender(opt) {
const record = opt.value;
if (record?.time) {
return record.time;
}
return parseDateToStr(+record.timestamp * 1000);
},
}, },
{ {
title: t('common.operate'), title: t('common.operate'),
@@ -252,11 +268,19 @@ function fnGetList(pageNum?: number) {
if (pageNum) { if (pageNum) {
queryParams.pageNum = pageNum; queryParams.pageNum = pageNum;
} }
if (!queryRangePicker.value) {
queryRangePicker.value = ['', '']; // 时间范围
if (
Array.isArray(queryRangePicker.value) &&
queryRangePicker.value.length > 0
) {
queryParams.startTime = queryRangePicker.value[0].valueOf();
queryParams.endTime = queryRangePicker.value[1].valueOf();
} else {
queryParams.startTime = undefined;
queryParams.endTime = undefined;
} }
queryParams.startTime = queryRangePicker.value[0];
queryParams.endTime = queryRangePicker.value[1];
listAMFDataUE(toRaw(queryParams)).then(res => { listAMFDataUE(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选 // 取消勾选
@@ -324,6 +348,18 @@ function fnExportList() {
}); });
} }
/**
* 复制CDR
* @param jsonStr JSON字符串
*/
function fnRecordCopy(jsonStr: string) {
if (!jsonStr) return;
const text = JSON.stringify(jsonStr, null, 2);
copy(text).then(() => {
message.success(t('common.copyOk'), 3);
});
}
/**实时数据开关 */ /**实时数据开关 */
const realTimeData = ref<boolean>(false); const realTimeData = ref<boolean>(false);
@@ -333,31 +369,30 @@ const realTimeData = ref<boolean>(false);
function fnRealTime() { function fnRealTime() {
realTimeData.value = !realTimeData.value; realTimeData.value = !realTimeData.value;
if (realTimeData.value) { if (realTimeData.value) {
tableState.seached = false;
// 建立链接 // 建立链接
const options: OptionsType = { const options: OptionsType = {
url: '/ws', url: '/ws',
params: { params: {
/**订阅通道组 /**订阅通道组
* *
* AMF_UE会话事件(GroupID:1010) * AMF_UE会话事件(GroupID:1010_neId)
*/ */
subGroupID: '1010', subGroupID: `1010_${queryParams.neId}`,
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: wsError, onerror: (ev: any) => {
console.error(ev);
},
}; };
ws.connect(options); ws.connect(options);
} else { } else {
ws.close(); ws.close();
tableState.seached = true;
fnGetList(1);
} }
} }
/**接收数据后回调 */
function wsError(ev: any) {
// 接收数据后回调
console.error(ev);
}
/**接收数据后回调 */ /**接收数据后回调 */
function wsMessage(res: Record<string, any>) { function wsMessage(res: Record<string, any>) {
const { code, requestId, data } = res; const { code, requestId, data } = res;
@@ -371,7 +406,7 @@ function wsMessage(res: Record<string, any>) {
return; return;
} }
// ueEvent AMF_UE会话事件 // ueEvent AMF_UE会话事件
if (data.groupId === '1010') { if (data.groupId === `1010_${queryParams.neId}`) {
const ueEvent = data.data; const ueEvent = data.data;
queue.add(async () => { queue.add(async () => {
modalState.maxId += 1; modalState.maxId += 1;
@@ -399,8 +434,7 @@ onMounted(() => {
getDict('ue_auth_code'), getDict('ue_auth_code'),
getDict('ue_event_type'), getDict('ue_event_type'),
getDict('ue_event_cm_state'), getDict('ue_event_cm_state'),
]) ]).then(resArr => {
.then(resArr => {
if (resArr[0].status === 'fulfilled') { if (resArr[0].status === 'fulfilled') {
dict.ueAauthCode = resArr[0].value; dict.ueAauthCode = resArr[0].value;
} }
@@ -410,6 +444,31 @@ onMounted(() => {
if (resArr[2].status === 'fulfilled') { if (resArr[2].status === 'fulfilled') {
dict.ueEventCmState = resArr[2].value; dict.ueEventCmState = resArr[2].value;
} }
});
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
let arr: Record<string, any>[] = [];
res.data.forEach(i => {
if (i.neType === 'AMF') {
arr.push({ value: i.neId, label: i.neName });
}
});
neOtions.value = arr;
if (arr.length > 0) {
queryParams.neId = arr[0].value;
}
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
}) })
.finally(() => { .finally(() => {
// 获取列表数据 // 获取列表数据
@@ -434,6 +493,16 @@ onBeforeUnmount(() => {
<!-- 表格搜索栏 --> <!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal"> <a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="AMF" name="neId ">
<a-select
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
@change="fnGetList(1)"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.dashboard.ue.eventType')" :label="t('views.dashboard.ue.eventType')"
@@ -448,7 +517,7 @@ onBeforeUnmount(() => {
></a-select> ></a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="4" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item label="IMSI" name="imsi "> <a-form-item label="IMSI" name="imsi ">
<a-input <a-input
v-model:value="queryParams.imsi" v-model:value="queryParams.imsi"
@@ -457,6 +526,20 @@ onBeforeUnmount(() => {
></a-input> ></a-input>
</a-form-item> </a-form-item>
</a-col> </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-col :lg="8" :md="12" :xs="24"> <a-col :lg="8" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.dashboard.cdr.time')" :label="t('views.dashboard.cdr.time')"
@@ -473,20 +556,6 @@ onBeforeUnmount(() => {
></a-range-picker> ></a-range-picker>
</a-form-item> </a-form-item>
</a-col> </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-row>
</a-form> </a-form>
</a-card> </a-card>
@@ -522,6 +591,7 @@ onBeforeUnmount(() => {
:disabled="tableState.selectedRowKeys.length <= 0" :disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading" :loading="modalState.confirmLoading"
@click.prevent="fnRecordDelete('0')" @click.prevent="fnRecordDelete('0')"
v-perms:has="['cdr:ne:remove']"
> >
<template #icon><DeleteOutlined /></template> <template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }} {{ t('common.deleteText') }}
@@ -544,6 +614,7 @@ onBeforeUnmount(() => {
:checked-children="t('common.switch.show')" :checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')" :un-checked-children="t('common.switch.hide')"
size="small" size="small"
:disabled="realTimeData"
/> />
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
@@ -604,7 +675,7 @@ onBeforeUnmount(() => {
<span v-if="record.eventType === 'auth-result'"> <span v-if="record.eventType === 'auth-result'">
<DictTag <DictTag
:options="dict.ueAauthCode" :options="dict.ueAauthCode"
:value="record.eventJSON.authCode" :value="record.eventJSON.result"
/> />
</span> </span>
<span v-if="record.eventType === 'detach'"> <span v-if="record.eventType === 'detach'">
@@ -613,37 +684,29 @@ onBeforeUnmount(() => {
<span v-if="record.eventType === 'cm-state'"> <span v-if="record.eventType === 'cm-state'">
<DictTag <DictTag
:options="dict.ueEventCmState" :options="dict.ueEventCmState"
:value="record.eventJSON.status" :value="record.eventJSON.result"
/> />
</span> </span>
</template> </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'"> <template v-if="column.key === 'id'">
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.copyText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordCopy(record.eventJSON)"
>
<template #icon>
<CopyOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.deleteText') }}</template> <template #title>{{ t('common.deleteText') }}</template>
<a-button <a-button
type="link" type="link"
@click.prevent="fnRecordDelete(record.id)" @click.prevent="fnRecordDelete(record.id)"
v-perms:has="['cdr:ne:remove']"
> >
<template #icon> <template #icon>
<DeleteOutlined /> <DeleteOutlined />
@@ -654,7 +717,8 @@ onBeforeUnmount(() => {
</template> </template>
</template> </template>
<template #expandedRowRender="{ record }"> <template #expandedRowRender="{ record }">
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px"> <a-row :gutter="16">
<a-col :lg="8" :md="12" :xs="24" :offset="2">
<a-divider orientation="left"> <a-divider orientation="left">
{{ t('views.dashboard.ue.ueInfo') }} {{ t('views.dashboard.ue.ueInfo') }}
</a-divider> </a-divider>
@@ -666,40 +730,33 @@ onBeforeUnmount(() => {
<span>{{ t('views.ne.common.rmUid') }}: </span> <span>{{ t('views.ne.common.rmUid') }}: </span>
<span>{{ record.rmUID }}</span> <span>{{ record.rmUID }}</span>
</div> </div>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-divider orientation="left"> <a-divider orientation="left">
{{ t('views.dashboard.ue.rowInfo') }} {{ t('views.dashboard.ue.rowInfo') }}
</a-divider> </a-divider>
<div> <div>
<span>{{ t('views.dashboard.ue.time') }}: </span> <span>{{ t('views.dashboard.ue.time') }}: </span>
<span <template v-if="record.eventJSON?.time">
v-if="record.eventType === 'auth-result'" {{ record.eventJSON.time }}
:title="record.eventJSON.authTime" </template>
> <template v-else>
{{ record.eventJSON.authTime }} {{ parseDateToStr(record.eventJSON.timestamp * 1000) }}
</span> </template>
<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>
<div> <div>
<span>{{ t('views.dashboard.ue.eventType') }}: </span> <span>{{ t('views.dashboard.ue.eventType') }}: </span>
<DictTag :options="dict.ueEventType" :value="record.eventType" /> <DictTag
:options="dict.ueEventType"
:value="record.eventType"
/>
</div> </div>
<div> <div>
<span>{{ t('views.dashboard.ue.result') }}: </span> <span>{{ t('views.dashboard.ue.result') }}: </span>
<span v-if="record.eventType === 'auth-result'"> <span v-if="record.eventType === 'auth-result'">
<DictTag <DictTag
:options="dict.ueAauthCode" :options="dict.ueAauthCode"
:value="record.eventJSON.authCode" :value="record.eventJSON.result"
/> />
</span> </span>
<span v-if="record.eventType === 'detach'"> <span v-if="record.eventType === 'detach'">
@@ -708,11 +765,12 @@ onBeforeUnmount(() => {
<span v-if="record.eventType === 'cm-state'"> <span v-if="record.eventType === 'cm-state'">
<DictTag <DictTag
:options="dict.ueEventCmState" :options="dict.ueEventCmState"
:value="record.eventJSON.status" :value="record.eventJSON.result"
/> />
</span> </span>
</div> </div>
</div> </a-col>
</a-row>
</template> </template>
</a-table> </a-table>
</a-card> </a-card>
+72 -23
View File
@@ -21,6 +21,9 @@ import { parseDateToStr, parseDuration } from '@/utils/date-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket'; import { OptionsType, WS } from '@/plugins/ws-websocket';
import saveAs from 'file-saver'; import saveAs from 'file-saver';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
import { useClipboard } from '@vueuse/core';
import dayjs, { type Dayjs } from 'dayjs';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n(); const { t } = useI18n();
const { getDict } = useDictStore(); const { getDict } = useDictStore();
const ws = new WS(); const ws = new WS();
@@ -41,7 +44,25 @@ let dict: {
let neOtions = ref<Record<string, any>[]>([]); let neOtions = ref<Record<string, any>[]>([]);
/**开始结束时间 */ /**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']); let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
dayjs().startOf('hour'),
dayjs().endOf('hour'),
]);
/**时间范围 */
let rangePickerPresets = ref([
{
label: 'Now hour',
value: [dayjs().startOf('hour'), dayjs().endOf('hour')],
},
{ label: 'Today', value: [dayjs().startOf('day'), dayjs().endOf('day')] },
{
label: 'Yesterday',
value: [
dayjs().subtract(1, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'),
],
},
]);
/**查询参数 */ /**查询参数 */
let queryParams = reactive({ let queryParams = reactive({
@@ -54,9 +75,9 @@ let queryParams = reactive({
sortField: 'timestamp', sortField: 'timestamp',
sortOrder: 'desc', sortOrder: 'desc',
/**开始时间 */ /**开始时间 */
startTime: '', startTime: undefined as undefined | number,
/**结束时间 */ /**结束时间 */
endTime: '', endTime: undefined as undefined | number,
/**当前页数 */ /**当前页数 */
pageNum: 1, pageNum: 1,
/**每页条数 */ /**每页条数 */
@@ -70,12 +91,12 @@ function fnQueryReset() {
recordType: '', recordType: '',
callerParty: '', callerParty: '',
calledParty: '', calledParty: '',
startTime: '', startTime: undefined,
endTime: '', endTime: undefined,
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
}); });
queryRangePicker.value = ['', '']; queryRangePicker.value = [dayjs().startOf('hour'), dayjs().endOf('hour')];
tablePagination.current = 1; tablePagination.current = 1;
tablePagination.pageSize = 20; tablePagination.pageSize = 20;
fnGetList(); fnGetList();
@@ -307,6 +328,18 @@ function fnRecordDelete(id: string) {
}); });
} }
/**
* 复制CDR
* @param jsonStr JSON字符串
*/
function fnRecordCopy(jsonStr: string) {
if (!jsonStr) return;
const text = JSON.stringify(jsonStr, null, 2);
copy(text).then(() => {
message.success(t('common.copyOk'), 3);
});
}
/**查询列表, pageNum初始页数 */ /**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) { function fnGetList(pageNum?: number) {
if (tableState.loading) return; if (tableState.loading) return;
@@ -314,11 +347,17 @@ function fnGetList(pageNum?: number) {
if (pageNum) { if (pageNum) {
queryParams.pageNum = pageNum; queryParams.pageNum = pageNum;
} }
if (!queryRangePicker.value) { // 时间范围
queryRangePicker.value = ['', '']; if (
Array.isArray(queryRangePicker.value) &&
queryRangePicker.value.length > 0
) {
queryParams.startTime = queryRangePicker.value[0].valueOf();
queryParams.endTime = queryRangePicker.value[1].valueOf();
} else {
queryParams.startTime = undefined;
queryParams.endTime = undefined;
} }
queryParams.startTime = queryRangePicker.value[0];
queryParams.endTime = queryRangePicker.value[1];
listIMSDataCDR(toRaw(queryParams)).then(res => { listIMSDataCDR(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选 // 取消勾选
@@ -407,7 +446,9 @@ function fnRealTime() {
subGroupID: `1005_${queryParams.neId}`, subGroupID: `1005_${queryParams.neId}`,
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: wsError, onerror: (ev: any) => {
console.error(ev);
},
}; };
ws.connect(options); ws.connect(options);
} else { } else {
@@ -417,12 +458,6 @@ function fnRealTime() {
} }
} }
/**接收数据后回调 */
function wsError(ev: any) {
// 接收数据后回调
console.error(ev);
}
/**接收数据后回调 */ /**接收数据后回调 */
function wsMessage(res: Record<string, any>) { function wsMessage(res: Record<string, any>) {
const { code, requestId, data } = res; const { code, requestId, data } = res;
@@ -522,6 +557,7 @@ onBeforeUnmount(() => {
v-model:value="queryParams.neId" v-model:value="queryParams.neId"
:options="neOtions" :options="neOtions"
:placeholder="t('common.selectPlease')" :placeholder="t('common.selectPlease')"
@change="fnQueryReset()"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
@@ -584,12 +620,12 @@ onBeforeUnmount(() => {
> >
<a-range-picker <a-range-picker
v-model:value="queryRangePicker" v-model:value="queryRangePicker"
allow-clear :presets="rangePickerPresets"
bordered :bordered="true"
:allow-clear="false"
style="width: 100%"
:show-time="{ format: 'HH:mm:ss' }" :show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
value-format="x"
style="width: 100%"
></a-range-picker> ></a-range-picker>
</a-form-item> </a-form-item>
</a-col> </a-col>
@@ -628,6 +664,7 @@ onBeforeUnmount(() => {
:disabled="tableState.selectedRowKeys.length <= 0" :disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading" :loading="modalState.confirmLoading"
@click.prevent="fnRecordDelete('0')" @click.prevent="fnRecordDelete('0')"
v-perms:has="['cdr:ne:remove']"
> >
<template #icon><DeleteOutlined /></template> <template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }} {{ t('common.deleteText') }}
@@ -724,11 +761,23 @@ onBeforeUnmount(() => {
</template> </template>
<template v-if="column.key === 'id'"> <template v-if="column.key === 'id'">
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.copyText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordCopy(record.cdrJSON)"
>
<template #icon>
<CopyOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.deleteText') }}</template> <template #title>{{ t('common.deleteText') }}</template>
<a-button <a-button
type="link" type="link"
@click.prevent="fnRecordDelete(record.id)" @click.prevent="fnRecordDelete(record.id)"
v-perms:has="['cdr:ne:remove']"
> >
<template #icon> <template #icon>
<DeleteOutlined /> <DeleteOutlined />
@@ -740,7 +789,7 @@ onBeforeUnmount(() => {
</template> </template>
<template #expandedRowRender="{ record }"> <template #expandedRowRender="{ record }">
<a-row :gutter="16"> <a-row :gutter="16">
<a-col :lg="5" :md="12" :xs="24"> <a-col :lg="8" :md="12" :xs="24" :offset="2">
<a-divider orientation="left"> <a-divider orientation="left">
{{ t('views.dashboard.cdr.cdrInfo') }} {{ t('views.dashboard.cdr.cdrInfo') }}
</a-divider> </a-divider>
@@ -763,7 +812,7 @@ onBeforeUnmount(() => {
</span> </span>
</div> </div>
</a-col> </a-col>
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="8" :md="12" :xs="24">
<a-divider orientation="left"> <a-divider orientation="left">
{{ t('views.dashboard.cdr.rowInfo') }} {{ t('views.dashboard.cdr.rowInfo') }}
</a-divider> </a-divider>
File diff suppressed because it is too large. Load diff
+88 -37
View File
@@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import dayjs, { Dayjs } from 'dayjs';
import { reactive, onMounted, toRaw, ref, onBeforeUnmount } from 'vue'; import { reactive, onMounted, toRaw, ref, onBeforeUnmount } from 'vue';
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue/es'; import { message, Modal } from 'ant-design-vue/es';
@@ -17,6 +18,8 @@ import { parseDateToStr } from '@/utils/date-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket'; import { OptionsType, WS } from '@/plugins/ws-websocket';
import saveAs from 'file-saver'; import saveAs from 'file-saver';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
import { useClipboard } from '@vueuse/core';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n(); const { t } = useI18n();
const { getDict } = useDictStore(); const { getDict } = useDictStore();
const ws = new WS(); const ws = new WS();
@@ -40,7 +43,10 @@ let dict: {
}); });
/**开始结束时间 */ /**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']); let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
dayjs().startOf('hour'),
dayjs().endOf('hour'),
]);
/**查询参数 */ /**查询参数 */
let queryParams = reactive({ let queryParams = reactive({
@@ -52,9 +58,9 @@ let queryParams = reactive({
sortField: 'timestamp', sortField: 'timestamp',
sortOrder: 'desc', sortOrder: 'desc',
/**开始时间 */ /**开始时间 */
startTime: '', startTime: undefined as undefined | number,
/**结束时间 */ /**结束时间 */
endTime: '', endTime: undefined as undefined | number,
/**当前页数 */ /**当前页数 */
pageNum: 1, pageNum: 1,
/**每页条数 */ /**每页条数 */
@@ -72,7 +78,7 @@ function fnQueryReset() {
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
}); });
queryRangePicker.value = ['', '']; queryRangePicker.value = [dayjs().startOf('hour'), dayjs().endOf('hour')];
tablePagination.current = 1; tablePagination.current = 1;
tablePagination.pageSize = 20; tablePagination.pageSize = 20;
fnGetList(); fnGetList();
@@ -149,8 +155,11 @@ let tableColumns: ColumnsType = [
align: 'left', align: 'left',
width: 150, width: 150,
customRender(opt) { customRender(opt) {
const cdrJSON = opt.value; const record = opt.value;
return parseDateToStr(+cdrJSON.timestamp * 1000); if (record?.time) {
return record.time;
}
return parseDateToStr(+record.timestamp * 1000);
}, },
}, },
{ {
@@ -260,11 +269,19 @@ function fnGetList(pageNum?: number) {
if (pageNum) { if (pageNum) {
queryParams.pageNum = pageNum; queryParams.pageNum = pageNum;
} }
if (!queryRangePicker.value) {
queryRangePicker.value = ['', '']; // 时间范围
if (
Array.isArray(queryRangePicker.value) &&
queryRangePicker.value.length > 0
) {
queryParams.startTime = queryRangePicker.value[0].valueOf();
queryParams.endTime = queryRangePicker.value[1].valueOf();
} else {
queryParams.startTime = undefined;
queryParams.endTime = undefined;
} }
queryParams.startTime = queryRangePicker.value[0];
queryParams.endTime = queryRangePicker.value[1];
listMMEDataUE(toRaw(queryParams)).then(res => { listMMEDataUE(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选 // 取消勾选
@@ -332,6 +349,18 @@ function fnExportList() {
}); });
} }
/**
* 复制CDR
* @param jsonStr JSON字符串
*/
function fnRecordCopy(jsonStr: string) {
if (!jsonStr) return;
const text = JSON.stringify(jsonStr, null, 2);
copy(text).then(() => {
message.success(t('common.copyOk'), 3);
});
}
/**实时数据开关 */ /**实时数据开关 */
const realTimeData = ref<boolean>(false); const realTimeData = ref<boolean>(false);
@@ -348,12 +377,14 @@ function fnRealTime() {
params: { params: {
/**订阅通道组 /**订阅通道组
* *
* MME_UE会话事件(GroupID:1011) * MME_UE会话事件(GroupID:1011_neId)
*/ */
subGroupID: `1011_${queryParams.neId}`, subGroupID: `1011_${queryParams.neId}`,
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: wsError, onerror: (ev: any) => {
console.error(ev);
},
}; };
ws.connect(options); ws.connect(options);
} else { } else {
@@ -363,12 +394,6 @@ function fnRealTime() {
} }
} }
/**接收数据后回调 */
function wsError(ev: any) {
// 接收数据后回调
console.error(ev);
}
/**接收数据后回调 */ /**接收数据后回调 */
function wsMessage(res: Record<string, any>) { function wsMessage(res: Record<string, any>) {
const { code, requestId, data } = res; const { code, requestId, data } = res;
@@ -415,7 +440,7 @@ onMounted(() => {
dict.ueAauthCode = resArr[0].value; dict.ueAauthCode = resArr[0].value;
} }
if (resArr[1].status === 'fulfilled') { if (resArr[1].status === 'fulfilled') {
const ueEventType: any[] = JSON.parse(JSON.stringify(resArr[1])); const ueEventType: any[] = JSON.parse(JSON.stringify(resArr[1].value));
dict.ueEventType = ueEventType.map(item => { dict.ueEventType = ueEventType.map(item => {
if (item.value === 'cm-state') { if (item.value === 'cm-state') {
item.label = item.label.replace('CM', 'ECM'); item.label = item.label.replace('CM', 'ECM');
@@ -481,6 +506,7 @@ onBeforeUnmount(() => {
v-model:value="queryParams.neId" v-model:value="queryParams.neId"
:options="neOtions" :options="neOtions"
:placeholder="t('common.selectPlease')" :placeholder="t('common.selectPlease')"
@change="fnGetList(1)"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
@@ -498,7 +524,7 @@ onBeforeUnmount(() => {
></a-select> ></a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="4" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item label="IMSI" name="imsi "> <a-form-item label="IMSI" name="imsi ">
<a-input <a-input
v-model:value="queryParams.imsi" v-model:value="queryParams.imsi"
@@ -507,6 +533,20 @@ onBeforeUnmount(() => {
></a-input> ></a-input>
</a-form-item> </a-form-item>
</a-col> </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-col :lg="8" :md="12" :xs="24"> <a-col :lg="8" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.dashboard.cdr.time')" :label="t('views.dashboard.cdr.time')"
@@ -523,20 +563,6 @@ onBeforeUnmount(() => {
></a-range-picker> ></a-range-picker>
</a-form-item> </a-form-item>
</a-col> </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-row>
</a-form> </a-form>
</a-card> </a-card>
@@ -572,6 +598,7 @@ onBeforeUnmount(() => {
:disabled="tableState.selectedRowKeys.length <= 0" :disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading" :loading="modalState.confirmLoading"
@click.prevent="fnRecordDelete('0')" @click.prevent="fnRecordDelete('0')"
v-perms:has="['cdr:ne:remove']"
> >
<template #icon><DeleteOutlined /></template> <template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }} {{ t('common.deleteText') }}
@@ -670,11 +697,23 @@ onBeforeUnmount(() => {
</template> </template>
<template v-if="column.key === 'id'"> <template v-if="column.key === 'id'">
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.copyText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordCopy(record.eventJSON)"
>
<template #icon>
<CopyOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.deleteText') }}</template> <template #title>{{ t('common.deleteText') }}</template>
<a-button <a-button
type="link" type="link"
@click.prevent="fnRecordDelete(record.id)" @click.prevent="fnRecordDelete(record.id)"
v-perms:has="['cdr:ne:remove']"
> >
<template #icon> <template #icon>
<DeleteOutlined /> <DeleteOutlined />
@@ -685,7 +724,8 @@ onBeforeUnmount(() => {
</template> </template>
</template> </template>
<template #expandedRowRender="{ record }"> <template #expandedRowRender="{ record }">
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px"> <a-row :gutter="16">
<a-col :lg="8" :md="12" :xs="24" :offset="2">
<a-divider orientation="left"> <a-divider orientation="left">
{{ t('views.dashboard.ue.ueInfo') }} {{ t('views.dashboard.ue.ueInfo') }}
</a-divider> </a-divider>
@@ -697,16 +737,26 @@ onBeforeUnmount(() => {
<span>{{ t('views.ne.common.rmUid') }}: </span> <span>{{ t('views.ne.common.rmUid') }}: </span>
<span>{{ record.rmUID }}</span> <span>{{ record.rmUID }}</span>
</div> </div>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-divider orientation="left"> <a-divider orientation="left">
{{ t('views.dashboard.ue.rowInfo') }} {{ t('views.dashboard.ue.rowInfo') }}
</a-divider> </a-divider>
<div> <div>
<span>{{ t('views.dashboard.ue.time') }}: </span> <span>{{ t('views.dashboard.ue.time') }}: </span>
<template v-if="record.eventJSON?.time">
{{ record.eventJSON.time }}
</template>
<template v-else>
{{ parseDateToStr(record.eventJSON.timestamp * 1000) }} {{ parseDateToStr(record.eventJSON.timestamp * 1000) }}
</template>
</div> </div>
<div> <div>
<span>{{ t('views.dashboard.ue.eventType') }}: </span> <span>{{ t('views.dashboard.ue.eventType') }}: </span>
<DictTag :options="dict.ueEventType" :value="record.eventType" /> <DictTag
:options="dict.ueEventType"
:value="record.eventType"
/>
</div> </div>
<div> <div>
<span>{{ t('views.dashboard.ue.result') }}: </span> <span>{{ t('views.dashboard.ue.result') }}: </span>
@@ -726,7 +776,8 @@ onBeforeUnmount(() => {
/> />
</span> </span>
</div> </div>
</div> </a-col>
</a-row>
</template> </template>
</a-table> </a-table>
</a-card> </a-card>
@@ -77,8 +77,12 @@ onMounted(() => {
<div></div> <div></div>
<div> <div>
{{ t('views.dashboard.overview.userActivity.time') }}:&nbsp; {{ t('views.dashboard.overview.userActivity.time') }}:&nbsp;
<span :title="parseDateToStr(item.data.releaseTime * 1000)"> <span :title="item.data.releaseTime">
{{ parseDateToStr(item.data.releaseTime * 1000) }} {{
typeof item.data.releaseTime === 'number'
? parseDateToStr(+item.data.releaseTime * 1000)
: item.data.releaseTime
}}
</span> </span>
</div> </div>
</div> </div>
@@ -133,18 +137,12 @@ onMounted(() => {
</div> </div>
<div> <div>
{{ t('views.dashboard.overview.userActivity.time') }}: {{ t('views.dashboard.overview.userActivity.time') }}:
<span <template v-if="item.data?.time">
v-if="item.type === 'auth-result'" {{ item.data.time }}
:title="item.data.authTime" </template>
> <template v-else>
{{ item.data.authTime }} {{ parseDateToStr(+item.data.timestamp * 1000) }}
</span> </template>
<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> </div>
@@ -163,7 +161,7 @@ onMounted(() => {
<div v-if="item.type === 'auth-result'"> <div v-if="item.type === 'auth-result'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp; {{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span> <span>
<DictTag :options="dict.ueAauthCode" :value="item.data.authCode" /> <DictTag :options="dict.ueAauthCode" :value="item.data.result" />
</span> </span>
</div> </div>
<div v-if="item.type === 'detach'"> <div v-if="item.type === 'detach'">
@@ -173,7 +171,7 @@ onMounted(() => {
<div class="card-ue-w33" v-if="item.type === 'cm-state'"> <div class="card-ue-w33" v-if="item.type === 'cm-state'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp; {{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span> <span>
<DictTag :options="dict.ueEventCmState" :value="item.data.status" /> <DictTag :options="dict.ueEventCmState" :value="item.data.result" />
</span> </span>
</div> </div>
</div> </div>
@@ -198,12 +196,16 @@ onMounted(() => {
</span> </span>
</div> </div>
<div> <div>
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span> IMSI: <span :title="item.data?.imsi">{{ item.data?.imsi }}</span>
</div> </div>
<div> <div>
{{ t('views.dashboard.overview.userActivity.time') }}: {{ t('views.dashboard.overview.userActivity.time') }}:
<span :title="item.data.timestamp"> <span :title="item.data?.timestamp">
{{ parseDateToStr(+item.data.timestamp * 1000) }} {{
typeof item.data?.timestamp === 'number'
? parseDateToStr(+item.data?.timestamp * 1000)
: item.data?.timestamp
}}
</span> </span>
</div> </div>
</div> </div>
@@ -0,0 +1,561 @@
<script setup lang="ts">
import {
reactive,
onMounted,
onBeforeUnmount,
markRaw,
useTemplateRef, ref
} from 'vue';
import TrendChart from './TrendChart.vue';
import useI18n from '@/hooks/useI18n';
import { listNeInfo } from '@/api/ne/neInfo';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { getNeConfigData } from '@/api/ne/neConfig';
import { listCallings } from '@/api/agentManage/callings';
import { useRouter } from 'vue-router';
import useUserStore from '@/store/modules/user';
const { t } = useI18n();
const router = useRouter();
/**10s调度器 */
const interval10s = ref<any>(null);
// 模拟数据
const activeCallsData = ref([]);
const mosData = ref([]);
const failedCallsData = ref([]);
// 新增三个卡片的模拟数据
const networkCpuData = ref([]);
const systemCpuData = ref([]);
const systemStorageData = ref([]);
const systemMemData = ref([]);
// 是否是第一次加载资源数据
const isFirstLoad = ref(true);
// 更新图表数据的函数
function updateChartData(newValue: number, dataArray: any) {
// 如果是第一次加载,用当前值填充整个数组
if (isFirstLoad.value) {
dataArray.value = Array(7).fill(newValue);
} else {
// 非第一次加载,正常更新数据(移除第一个,添加新值)
const newData = [...dataArray.value];
newData.shift();
newData.push(newValue);
dataArray.value = newData;
}
}
// 是否是第一次加载用户数据
const isFirstLoadUser = ref(true);
function updateUserChartData(newValue: number, dataArray: any) {
// 如果是第一次加载,用当前值填充整个数组
if (isFirstLoadUser.value) {
dataArray.value = Array(7).fill(newValue);
} else {
// 非第一次加载,正常更新数据(移除第一个,添加新值)
const newData = [...dataArray.value];
newData.shift();
newData.push(newValue);
dataArray.value = newData;
}
}
// 是否是第一次加载并行用户数据
const isFirstLoadFailed = ref(true);
function updateFailedChartData(newValue: number, dataArray: any) {
// 如果是第一次加载,用当前值填充整个数组
if (isFirstLoadFailed.value) {
dataArray.value = Array(7).fill(newValue);
} else {
// 非第一次加载,正常更新数据(移除第一个,添加新值)
const newData = [...dataArray.value];
newData.shift();
newData.push(newValue);
dataArray.value = newData;
}
}
// 当前资源使用率
const currentNfCpuUsage = ref(0);
const currentSysCpuUsage = ref(0);
const currentSysDiskUsage = ref(0);
const currentSysMemUsage = ref(0);
// 上一次的资源使用率
const prevNfCpuUsage = ref(0);
const prevSysCpuUsage = ref(0);
const prevSysDiskUsage = ref(0);
const prevSysMemUsage = ref(0);
// 资源变化百分比
const nfCpuChange = ref(0);
const sysCpuChange = ref(0);
const sysDiskChange = ref(0);
const sysMemChange = ref(0);
// 当前资源使用率
const activeCalls = ref(0);
const onlineCount = ref(0);
const failedCallsCount = ref(0);
// 上一次的资源使用率
const prevActiveCalls = ref(0);
const prevOnlineCount = ref(0);
const prevFailedCallsCount = ref(0);
// 用户数变化百分比
const activeCallsChange = ref(0);
const onlineCountChange = ref(0);
const failedCallsCountChange = ref(0);
/**解析网元状态携带的资源利用率 */
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,
};
}
// 计算变化百分比
function calculateChange(current: number, previous: number): number {
if (previous === 0) return 0;
return Number(((current - previous) * 100 / previous).toFixed(1));
}
let tableState: any = [];
function fnGetList() {
listNeInfo({
neType: 'MF',
bandStatus: true,
pageNum: 1,
pageSize: 20,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows) && res.rows.length > 0) {
// 获取第一个网元的资源使用情况
const item = res.rows[0];
let resouresUsage = {
sysDiskUsage: 0,
sysMemUsage: 0,
sysCpuUsage: 0,
nfCpuUsage: 0,
};
const neState = item.serverState;
if (neState) {
resouresUsage = parseResouresUsage(neState);
}
// 保存上一次的值
prevNfCpuUsage.value = currentNfCpuUsage.value;
prevSysCpuUsage.value = currentSysCpuUsage.value;
prevSysDiskUsage.value = currentSysDiskUsage.value;
prevSysMemUsage.value = currentSysMemUsage.value;
// 更新当前值
currentNfCpuUsage.value = resouresUsage.nfCpuUsage;
currentSysCpuUsage.value = resouresUsage.sysCpuUsage;
currentSysDiskUsage.value = resouresUsage.sysDiskUsage;
currentSysMemUsage.value = resouresUsage.sysMemUsage;
// 计算变化百分比
nfCpuChange.value = calculateChange(currentNfCpuUsage.value, prevNfCpuUsage.value);
sysCpuChange.value = calculateChange(currentSysCpuUsage.value, prevSysCpuUsage.value);
sysDiskChange.value = calculateChange(currentSysDiskUsage.value, prevSysDiskUsage.value);
sysMemChange.value = calculateChange(currentSysMemUsage.value, prevSysMemUsage.value);
// 更新图表数据
updateChartData(currentNfCpuUsage.value, networkCpuData);
updateChartData(currentSysCpuUsage.value, systemCpuData);
updateChartData(currentSysDiskUsage.value, systemStorageData);
updateChartData(currentSysMemUsage.value, systemMemData);
// 第一次加载完成后,设置标志为false
if (isFirstLoad.value) {
isFirstLoad.value = false;
}
}
})
// 获取网元端的配置数据
getNeConfigData({
neType: 'MF',
neId: '001',
paramName: 'agents',
}).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
const neData = res.data;
prevActiveCalls.value = activeCalls.value;
prevOnlineCount.value = onlineCount.value;
// 更新 activeCallsData 和 mosData
activeCalls.value = neData.length; // 数组长度
onlineCount.value = neData.filter((item: any) => item.online).length; // online 为 true 的数量
activeCallsChange.value = prevActiveCalls.value ? prevActiveCalls.value - activeCalls.value : 0;
onlineCountChange.value = prevOnlineCount.value ? prevOnlineCount.value - onlineCount.value : 0;
// 更新图表数据
updateUserChartData(activeCalls.value ? activeCalls.value : 0, activeCallsData);
updateUserChartData(onlineCount.value ? onlineCount.value : 0, mosData);
// 第一次加载完成后,设置标志为false
if (isFirstLoadUser.value) {
isFirstLoadUser.value = false;
}
}
})
.catch(error => {
updateUserChartData(0, activeCallsData);
updateUserChartData(0, mosData);
// 第一次加载完成后,设置标志为false
if (isFirstLoadUser.value) {
isFirstLoadUser.value = false;
}
});
listCallings({ neId: '001' }).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
const callData: any = res.data;
prevFailedCallsCount.value = failedCallsCount.value;
failedCallsCount.value = res.total; // 数组长度
failedCallsCountChange.value = prevFailedCallsCount.value ? prevFailedCallsCount.value - failedCallsCount.value : 0;
// 更新图表数据
updateFailedChartData(failedCallsCount.value, failedCallsData);
// 第一次加载完成后,设置标志为false
if (isFirstLoadFailed.value) {
isFirstLoadFailed.value = false;
}
}
});
}
/**栏目信息跳转 */
function fnToRouter(name: string, query?: any) {
if (useUserStore().roles.includes('agent')) {
return;
}
router.push({ name, query });
}
onMounted(() => {
// 初始获取数据
fnGetList();
// 设置10秒定时器
interval10s.value = setInterval(() => {
fnGetList();
}, 10000);
});
// 组件销毁前清除定时器
onBeforeUnmount(() => {
if (interval10s.value) {
clearInterval(interval10s.value);
interval10s.value = null;
}
});
</script>
<template>
<a-card>
<div style="font-size:32px; font-weight: bold; margin-bottom: 20px; text-align: center;">
{{ t('views.dashboard.overview.psapTitle') }}</div>
<div class="row-title">{{ t('views.dashboard.overview.userTitle') }}</div>
<a-row :gutter="[48, 48]">
<a-col :xs="24" :sm="24" :lg="8">
<a-card :bordered="false" class="metric-card"
@click="fnToRouter('NeConfig_2146', { neType: 'MF', treeName: 'agents' })">
<div class="card-title">{{ t('views.dashboard.overview.totalUser') }}</div>
<div class="card-content">
<div class="trend-chart">
<TrendChart :data="activeCallsData" color="#4CAF50" />
</div>
<div class="metric-info">
<div class="metric-value">
{{ activeCalls }}
<a-icon :class="['trend-icon', activeCallsChange >= 0 ? 'up' : 'down']"
:type="activeCallsChange >= 0 ? 'arrow-up' : 'arrow-down'" />
</div>
<div class="metric-change">{{ activeCallsChange >= 0 ? '+' : '' }}{{ activeCallsChange }}% last 10s</div>
</div>
</div>
</a-card>
</a-col>
<a-col :xs="24" :sm="24" :lg="8">
<a-card :bordered="false" class="metric-card"
@click="fnToRouter('NeConfig_2146', { neType: 'MF', treeName: 'agents' })">
<div class="card-title">{{ t('views.dashboard.overview.onlineUser') }}</div>
<div class="card-content">
<div class="trend-chart">
<TrendChart :data="mosData" color="#2196F3" />
</div>
<div class="metric-info">
<div class="metric-value">
{{ onlineCount }}
<a-icon :class="['trend-icon', onlineCountChange >= 0 ? 'up' : 'down']"
:type="onlineCountChange >= 0 ? 'arrow-up' : 'arrow-down'" />
</div>
<div class="metric-change">{{ onlineCountChange >= 0 ? '+' : '' }}{{ onlineCountChange }} last 10s</div>
</div>
</div>
</a-card>
</a-col>
<a-col :xs="24" :sm="24" :lg="8">
<a-card :bordered="false" class="metric-card" @click="fnToRouter('Callings_20001')">
<div class="card-title">{{ t('views.dashboard.overview.parallelUser') }}</div>
<div class="card-content">
<div class="trend-chart">
<TrendChart :data="failedCallsData" color="#F44336" />
</div>
<div class="metric-info">
<div class="metric-value">
{{ failedCallsCount }}
<a-icon :class="['trend-icon', failedCallsCountChange >= 0 ? 'up' : 'down']"
:type="failedCallsCountChange >= 0 ? 'arrow-up' : 'arrow-down'" />
</div>
<div class="metric-change">{{ failedCallsCountChange >= 0 ? '+' : '' }}{{ failedCallsCountChange }} last
10s</div>
</div>
</div>
</a-card>
</a-col>
</a-row>
<div class="row-title" style="margin-top: 100px;"> {{ t('views.dashboard.overview.sysTitle') }} </div>
<a-row :gutter="[48, 48]">
<a-col :xs="24" :sm="24" :lg="6">
<a-card :bordered="false" class="metric-card">
<div class="card-title">{{ t('views.dashboard.overview.resources.neCpu') }}</div>
<div class="card-content">
<div class="trend-chart">
<TrendChart :data="networkCpuData" color="#FF9800" />
</div>
<div class="metric-info">
<div class="metric-value">
{{ currentNfCpuUsage }}%
<a-icon :class="['trend-icon', nfCpuChange >= 0 ? 'up' : 'down']"
:type="nfCpuChange >= 0 ? 'arrow-up' : 'arrow-down'" />
</div>
<div class="metric-change">{{ nfCpuChange >= 0 ? '+' : '' }}{{ nfCpuChange }}% last 10s</div>
</div>
</div>
</a-card>
</a-col>
<a-col :xs="24" :sm="24" :lg="6">
<a-card :bordered="false" class="metric-card">
<div class="card-title">{{ t('views.dashboard.overview.resources.sysCpu') }}</div>
<div class="card-content">
<div class="trend-chart">
<TrendChart :data="systemCpuData" color="#9C27B0" />
</div>
<div class="metric-info">
<div class="metric-value">
{{ currentSysCpuUsage }}%
<a-icon :class="['trend-icon', sysCpuChange >= 0 ? 'up' : 'down']"
:type="sysCpuChange >= 0 ? 'arrow-up' : 'arrow-down'" />
</div>
<div class="metric-change">{{ sysCpuChange >= 0 ? '+' : '' }}{{ sysCpuChange }}% last 10s</div>
</div>
</div>
</a-card>
</a-col>
<a-col :xs="24" :sm="24" :lg="6">
<a-card :bordered="false" class="metric-card">
<div class="card-title">{{ t('views.dashboard.overview.resources.sysMem') }}</div>
<div class="card-content">
<div class="trend-chart">
<TrendChart :data="systemMemData" color="#CDDC39" />
</div>
<div class="metric-info">
<div class="metric-value">
{{ currentSysMemUsage }}%
<a-icon :class="['trend-icon', sysMemChange >= 0 ? 'up' : 'down']"
:type="sysMemChange >= 0 ? 'arrow-up' : 'arrow-down'" />
</div>
<div class="metric-change">{{ sysMemChange >= 0 ? '+' : '' }}{{ sysMemChange }}% last 10s</div>
</div>
</div>
</a-card>
</a-col>
<a-col :xs="24" :sm="24" :lg="6">
<a-card :bordered="false" class="metric-card">
<div class="card-title">{{ t('views.dashboard.overview.resources.sysDisk') }}</div>
<div class="card-content">
<div class="trend-chart">
<TrendChart :data="systemStorageData" color="#00BCD4" />
</div>
<div class="metric-info">
<div class="metric-value">
{{ currentSysDiskUsage }}%
<a-icon :class="['trend-icon', sysDiskChange >= 0 ? 'up' : 'down']"
:type="sysDiskChange >= 0 ? 'arrow-up' : 'arrow-down'" />
</div>
<div class="metric-change">{{ sysDiskChange >= 0 ? '+' : '' }}{{ sysDiskChange }}% last 10s</div>
</div>
</div>
</a-card>
</a-col>
</a-row>
</a-card>
</template>
<style scoped>
.row-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
padding-left: 8px;
border-left: 3px solid #1890ff;
}
.dashboard-cards {
padding: 16px;
}
.metric-card {
border-radius: 8px;
height: 100%;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
}
.card-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
}
.card-content {
display: flex;
align-items: center;
}
.trend-chart {
flex: 1;
height: 50px;
}
.metric-info {
margin-left: 16px;
text-align: right;
min-width: 90px;
}
.full-width {
width: 100%;
text-align: center;
}
.metric-value {
font-size: 24px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: flex-end;
}
.metric-change {
font-size: 12px;
color: #999;
margin-top: 4px;
}
.trend-icon {
margin-left: 8px;
font-size: 16px;
}
.up {
color: #4CAF50;
}
.down {
color: #F44336;
}
.right {
color: #2196F3;
}
</style>
@@ -0,0 +1,107 @@
<template>
<div class="trend-chart-container" ref="chartContainer"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch, PropType } from 'vue';
import * as echarts from 'echarts/core';
import { LineChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { SVGRenderer } from 'echarts/renderers';
echarts.use([LineChart, GridComponent, SVGRenderer]);
// 定义props
const props = defineProps({
data: {
type: Array as PropType<number[]>,
required: true
},
color: {
type: String,
default: '#4CAF50'
}
});
const chartContainer = ref<HTMLElement | null>(null);
let chart: echarts.ECharts | null = null;
const initChart = () => {
if (!chartContainer.value) return;
chart = echarts.init(chartContainer.value);
const option = {
grid: {
left: 0,
right: 0,
top: 0,
bottom: 0
},
xAxis: {
type: 'category',
show: false,
data: ['40m', '', '', '', '', '', 'now']
},
yAxis: {
type: 'value',
show: false
},
series: [
{
data: props.data,
type: 'line',
smooth: true,
symbol: 'none',
lineStyle: {
color: props.color,
width: 2
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: props.color + '40' // 40% 透明度
},
{
offset: 1,
color: props.color + '00' // 0% 透明度
}
]
}
}
}
],
animation: false
};
chart.setOption(option);
};
onMounted(() => {
initChart();
window.addEventListener('resize', () => {
chart?.resize();
});
});
watch(() => props.data, () => {
chart?.setOption({
series: [{ data: props.data }]
});
});
</script>
<style scoped>
.trend-chart-container {
width: 100%;
height: 100%;
}
</style>
+6 -1
View File
@@ -16,6 +16,8 @@
.column { .column {
flex: 3; flex: 3;
position: relative; position: relative;
display: flex;
flex-direction: column;
} }
/* 边框 */ /* 边框 */
@@ -79,13 +81,14 @@
.upfFlow .inner .chart { .upfFlow .inner .chart {
width: 100%; width: 100%;
height: 100%; height: 100%;
margin-top: 1rem; margin-top: 0rem;
} }
/* 网络拓扑 */ /* 网络拓扑 */
.topology { .topology {
/* min-height: 27.8rem; */ /* min-height: 27.8rem; */
height: 56.4%; height: 56.4%;
flex: 1;
} }
.topology .inner h3 { .topology .inner h3 {
display: flex; display: flex;
@@ -179,6 +182,7 @@
.userActivity { .userActivity {
/* min-height: 35.8rem; */ /* min-height: 35.8rem; */
height: 54.6%; height: 54.6%;
flex: 1;
} }
.userActivity .inner .chart { .userActivity .inner .chart {
width: 100%; width: 100%;
@@ -259,6 +263,7 @@
.alarmType { .alarmType {
/* min-height: 25rem; */ /* min-height: 25rem; */
height: 46%; height: 46%;
flex: 1;
} }
.alarmType .inner .chart { .alarmType .inner .chart {
width: 100%; width: 100%;
@@ -36,11 +36,6 @@ export function upfFlowParse(data: Record<string, string>) {
upfFlowData.value.lineYDown.shift(); upfFlowData.value.lineYDown.shift();
upfFlowData.value.cap -= 1; 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 = { type TFType = {
+29 -13
View File
@@ -1,6 +1,6 @@
import { RESULT_CODE_ERROR } from '@/constants/result-constants'; import { RESULT_CODE_ERROR } from '@/constants/result-constants';
import { OptionsType, WS } from '@/plugins/ws-websocket'; import { OptionsType, WS } from '@/plugins/ws-websocket';
import { onBeforeUnmount, onMounted } from 'vue'; import { onBeforeUnmount, ref } from 'vue';
import { import {
eventListParse, eventListParse,
eventItemParseAndPush, eventItemParseAndPush,
@@ -12,9 +12,15 @@ import {
upfFlowParse, upfFlowParse,
upfTotalFlowReset, upfTotalFlowReset,
} from './useUPFTotalFlow'; } from './useUPFTotalFlow';
import { topologyReset, neStateParse } from './useTopology'; import { topologyReset, neStateParse, neStateRequestMap } from './useTopology';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
/**UPF-的Id */
export const upfWhoId = ref<any>('');
/**UPF-的RmUid */
export const upfWhoRmUid = ref<any>('');
/**websocket连接 */ /**websocket连接 */
export default function useWS() { export default function useWS() {
const ws = new WS(); const ws = new WS();
@@ -33,7 +39,6 @@ export default function useWS() {
console.warn(res.msg); console.warn(res.msg);
return; return;
} }
// 网元状态 // 网元状态
if (requestId && requestId.startsWith('neState')) { if (requestId && requestId.startsWith('neState')) {
const neType = requestId.split('_')[1]; const neType = requestId.split('_')[1];
@@ -44,7 +49,7 @@ export default function useWS() {
// 普通信息 // 普通信息
switch (requestId) { switch (requestId) {
// AMF_UE会话事件 // AMF_UE会话事件
case 'amf_1010': case 'amf_1010_001':
if (Array.isArray(data.rows)) { if (Array.isArray(data.rows)) {
eventListParse('amf_ue', data); eventListParse('amf_ue', data);
} }
@@ -72,20 +77,19 @@ export default function useWS() {
upfTFParse('30', data); upfTFParse('30', data);
break; break;
} }
// 订阅组信息 // 订阅组信息
if (!data?.groupId) { if (!data?.groupId) {
return; return;
} }
switch (data.groupId) { switch (data.groupId) {
// kpiEvent 指标UPF // kpiEvent 指标UPF
case '12_001': case '10_UPF_' + upfWhoId.value:
if (data.data) { if (data.data) {
upfFlowParse(data.data); upfFlowParse(data.data);
} }
break; break;
// AMF_UE会话事件 // AMF_UE会话事件
case '1010': case '1010_001':
if (data.data) { if (data.data) {
queue.add(() => eventItemParseAndPush('amf_ue', data.data)); queue.add(() => eventItemParseAndPush('amf_ue', data.data));
} }
@@ -128,7 +132,7 @@ export default function useWS() {
function userActivitySend() { function userActivitySend() {
// AMF_UE会话事件 // AMF_UE会话事件
ws.send({ ws.send({
requestId: 'amf_1010', requestId: 'amf_1010_001',
type: 'amf_ue', type: 'amf_ue',
data: { data: {
neType: 'AMF', neType: 'AMF',
@@ -168,18 +172,28 @@ export default function useWS() {
}); });
} }
onMounted(() => { /**重新发送至UPF 10_UPF_neId */
function reSendUPF(neId: string) {
upfWhoId.value = neId;
//初始时时无需还原全部属性以及关闭
if (ws.state() === WebSocket.OPEN) {
ws.close();
// userActivityReset();
upfTotalFlowReset();
neStateRequestMap.value = new Map();
//topologyReset();
}
const options: OptionsType = { const options: OptionsType = {
url: '/ws', url: '/ws',
params: { params: {
/**订阅通道组 /**订阅通道组
* *
* 指标UPF (GroupID:12_neId) * 指标UPF (GroupID:10_neType_neId)
* AMF_UE会话事件(GroupID:1010) * AMF_UE会话事件(GroupID:1010_neId)
* MME_UE会话事件(GroupID:1011_neId) * MME_UE会话事件(GroupID:1011_neId)
* IMS_CDR会话事件(GroupID:1005_neId) * IMS_CDR会话事件(GroupID:1005_neId)
*/ */
subGroupID: '12_001,1010,1011_001,1005_001', subGroupID: '10_UPF_' + neId + ',1010_001,1011_001,1005_001',
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: (ev: any) => { onerror: (ev: any) => {
@@ -187,18 +201,20 @@ export default function useWS() {
}, },
}; };
ws.connect(options); ws.connect(options);
}); }
onBeforeUnmount(() => { onBeforeUnmount(() => {
ws.close(); ws.close();
userActivityReset(); userActivityReset();
upfTotalFlowReset(); upfTotalFlowReset();
topologyReset(); topologyReset();
upfWhoId.value = '';
}); });
return { return {
wsSend, wsSend,
userActivitySend, userActivitySend,
upfTFSend, upfTFSend,
reSendUPF,
}; };
} }
+10 -471
View File
@@ -1,491 +1,30 @@
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue'; 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';
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,
});
/**总览节点 */ import DashboardCards from './components/mfOverview/DashboardCards.vue';
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) {
router.push({ name, query });
}
onMounted(() => { onMounted(() => {
fnGetSkim().then(() => {
loadData();
});
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearInterval(interval10s.value);
interval10s.value = null;
clearInterval(interval5s.value);
interval5s.value = null;
}); });
</script> </script>
<template> <template>
<div class="viewport" ref="viewportDom"> <div class="dashboard-page">
<div class="brand"> <DashboardCards />
<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].upFrom }}</h4>
</div>
<div class="item">
<span>
<ArrowDownOutlined style="color: #52c41a" />
{{ t('views.dashboard.overview.upfFlowTotal.down') }}
</span>
<h4>{{ upfTotalFlow[upfTFActive].downFrom }}</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> </div>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.cardClass {
height: 100%;
width: 100%;
border-radius: 10px;
}
@import url('./css/index.css'); @import url('./css/index.css');
</style> </style>
+850
View File
@@ -0,0 +1,850 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, onBeforeUnmount, ref } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { Modal, message } 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 useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n';
import {
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import {
delSGWCDataCDR,
exportSGWCDataCDR,
listSGWCDataCDR,
} from '@/api/neData/sgwc';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import PQueue from 'p-queue';
import saveAs from 'file-saver';
import { useClipboard } from '@vueuse/core';
import dayjs, { type Dayjs } from 'dayjs';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n();
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
/**网元可选 */
let neOtions = ref<Record<string, any>[]>([]);
/**开始结束时间 */
let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
dayjs().startOf('hour'),
dayjs().endOf('hour'),
]);
/**时间范围 */
let rangePickerPresets = ref([
{
label: 'Now hour',
value: [dayjs().startOf('hour'), dayjs().endOf('hour')],
},
{ label: 'Today', value: [dayjs().startOf('day'), dayjs().endOf('day')] },
{
label: 'Yesterday',
value: [
dayjs().subtract(1, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'),
],
},
]);
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: 'SGWC',
neId: '001',
imsi: '',
msisdn: '',
sortField: 'timestamp',
sortOrder: 'desc',
/**开始时间 */
startTime: undefined as undefined | number,
/**结束时间 */
endTime: undefined as undefined | number,
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
imsi: '',
msisdn: '',
startTime: undefined,
endTime: undefined,
pageNum: 1,
pageSize: 20,
});
queryRangePicker.value = [dayjs().startOf('hour'), dayjs().endOf('hour')];
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: 'left',
width: 100,
},
{
title: t('views.dashboard.cdr.chargingID'), // 计费ID
dataIndex: 'cdrJSON',
align: 'left',
width: 100,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.chargingID;
},
},
{
title: t('views.dashboard.cdr.sgwcServedIMSI'), // IMSI
dataIndex: 'cdrJSON',
align: 'left',
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.servedIMSI;
},
},
{
title: t('views.dashboard.cdr.sgwcServedMSISDN'), // MSISDN
dataIndex: 'cdrJSON',
align: 'left',
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.servedMSISDN;
},
},
{
title: t('views.dashboard.cdr.sgwcVolumeGPRSUplink'), // GPRS 上行链路
dataIndex: 'cdrJSON',
align: 'left',
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
const listOfTrafficVolumes = cdrJSON.listOfTrafficVolumes;
if (
!Array.isArray(listOfTrafficVolumes) ||
listOfTrafficVolumes.length < 1
) {
return 0;
}
let dataVolumeGPRSUplink = 0;
for (const used of listOfTrafficVolumes) {
const v = +used.dataVolumeGPRSUplink;
dataVolumeGPRSUplink += isNaN(v) ? 0 : v;
}
return dataVolumeGPRSUplink;
},
},
{
title: t('views.dashboard.cdr.sgwcVolumeGPRSDownlink'), // GPRS 下行链路
dataIndex: 'cdrJSON',
align: 'left',
width: 180,
customRender(opt) {
const cdrJSON = opt.value;
const listOfTrafficVolumes = cdrJSON.listOfTrafficVolumes;
if (
!Array.isArray(listOfTrafficVolumes) ||
listOfTrafficVolumes.length < 1
) {
return 0;
}
let dataVolumeGPRSDownlink = 0;
for (const used of listOfTrafficVolumes) {
const v = +used.dataVolumeGPRSDownlink;
dataVolumeGPRSDownlink += isNaN(v) ? 0 : v;
}
return dataVolumeGPRSDownlink;
},
},
{
title: t('views.dashboard.cdr.durationTime'), // 持续时间
dataIndex: 'cdrJSON',
align: 'left',
width: 100,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.duration;
},
},
{
title: t('views.dashboard.cdr.invocationTime'), // 操作时间
dataIndex: 'cdrJSON',
align: 'left',
width: 200,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.recordOpeningTime;
},
},
{
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);
delSGWCDataCDR(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;
});
},
});
}
/**
* 复制CDR
* @param jsonStr JSON字符串
*/
function fnRecordCopy(jsonStr: string) {
if (!jsonStr) return;
const text = JSON.stringify(jsonStr, null, 2);
copy(text).then(() => {
message.success(t('common.copyOk'), 3);
});
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
// 时间范围
if (
Array.isArray(queryRangePicker.value) &&
queryRangePicker.value.length > 0
) {
queryParams.startTime = queryRangePicker.value[0].valueOf();
queryParams.endTime = queryRangePicker.value[1].valueOf();
} else {
queryParams.startTime = undefined;
queryParams.endTime = undefined;
}
listSGWCDataCDR(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;
exportSGWCDataCDR(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
saveAs(res.data, `sgwc_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: {
/**订阅通道组
*
* CDR会话事件-SGWC (GroupID:1008)
*/
subGroupID: `1008_${queryParams.neId}`,
},
onmessage: wsMessage,
onerror: (ev: any) => {
console.error(ev);
},
};
ws.connect(options);
} else {
ws.close();
tableState.seached = true;
fnGetList(1);
}
}
/**接收数据后回调 */
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 === `1008_${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(() => {
// 获取网元网元列表
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 === 'SGWC') {
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="SGWC" name="neId ">
<a-select
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
@change="fnQueryReset()"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.sgwcServedIMSI')"
name="imsi"
>
<a-input
v-model:value="queryParams.imsi"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="40"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.sgwcServedMSISDN')"
name="msisdn"
>
<a-input
v-model:value="queryParams.msisdn"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="40"
></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.time')"
name="queryRangePicker"
>
<a-range-picker
v-model:value="queryRangePicker"
:presets="rangePickerPresets"
:bordered="true"
:allow-clear="false"
style="width: 100%"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
></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-perms:has="['cdr:ne:remove']"
>
<template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }}
</a-button>
<a-button type="dashed" @click.prevent="fnExportList()">
<template #icon><ExportOutlined /></template>
{{ t('common.export') }}
</a-button>
</a-space>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
:disabled="realTimeData"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click" placement="bottomRight">
<a-button type="text">
<template #icon><ColumnHeightOutlined /></template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
<a-menu-item key="middle">
{{ t('common.size.middle') }}
</a-menu-item>
<a-menu-item key="small">
{{ t('common.size.small') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.copyText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordCopy(record.cdrJSON)"
>
<template #icon>
<CopyOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.deleteText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordDelete(record.id)"
v-perms:has="['cdr:ne:remove']"
>
<template #icon>
<DeleteOutlined />
</template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
<template #expandedRowRender="{ record }">
<a-row :gutter="16">
<a-col :lg="8" :md="12" :xs="24" :offset="2">
<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>{{ record.cdrJSON.recordOpeningTime }}</span>
</div>
<a-divider orientation="left">
{{ t('views.dashboard.cdr.rowInfo') }}
</a-divider>
<div>
<span>Record Type: </span>
<span>{{ record.cdrJSON.recordType }}</span>
</div>
<div>
<span>Record Opening Time: </span>
<span>{{ record.cdrJSON.recordOpeningTime }}</span>
</div>
<div>
<span>Charging ID: </span>
<span>{{ record.cdrJSON.chargingID }}</span>
</div>
<div>
<span>Duration: </span>
<span>{{ record.cdrJSON.duration }}</span>
</div>
<div>
<span>Record Access Point Name NI: </span>
<span>{{ record.cdrJSON.accessPointNameNI }}</span>
</div>
<div>
<span>Record Cause For Rec Closing: </span>
<span>{{ record.cdrJSON.causeForRecClosing }}</span>
</div>
<div>
<span>Record Sequence Number: </span>
<span>{{ record.cdrJSON.recordSequenceNumber }}</span>
</div>
<div>
<span>Local Record Sequence Number: </span>
<span>{{ record.cdrJSON.localRecordSequenceNumber }}</span>
</div>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-divider orientation="left"> Server Information </a-divider>
<div>
<span>IMSI: </span>
<span> {{ record.cdrJSON.servedIMSI }} </span>
</div>
<div>
<span>MSISDN: </span>
<span> {{ record.cdrJSON.servedMSISDN }} </span>
</div>
<div v-if="record.cdrJSON.pGWAddressUsed">
<span>PGW Address: </span>
<span> {{ record.cdrJSON.pGWAddressUsed }} </span>
</div>
<div v-if="record.cdrJSON.GGSNAddress">
<span>GGSN Address: </span>
<span> {{ record.cdrJSON.GGSNAddress }} </span>
</div>
<div v-if="record.cdrJSON.sGWAddress">
<span>SGW Address: </span>
<span> {{ record.cdrJSON.sGWAddress }} </span>
</div>
<div v-if="record.cdrJSON.SGSNAddress">
<span>SGSN Address: </span>
<span> {{ record.cdrJSON.SGSNAddress }} </span>
</div>
<div>
<span>RAT Type: </span>
<span> {{ record.cdrJSON.rATType }} </span>
</div>
<a-divider orientation="left"> PDPPD Information </a-divider>
<div>
<span>PDPPDN Type: </span>
<span> {{ record.cdrJSON.pdpPDNType }} </span>
</div>
<div>
<span>PDPPDN Address: </span>
<span> {{ record.cdrJSON.servedPDPPDNAddress }} </span>
</div>
<div>
<span>Node Address: </span>
<span>
{{ record.cdrJSON.servingNodeAddress?.join(', ') }}
</span>
</div>
<div>
<span>Node Type: </span>
<span>
<template v-for="item in record.cdrJSON.servingNodeType">
{{ item.servingNodeType }}
</template>
</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>
+112 -41
View File
@@ -19,6 +19,10 @@ import {
import { OptionsType, WS } from '@/plugins/ws-websocket'; import { OptionsType, WS } from '@/plugins/ws-websocket';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
import saveAs from 'file-saver'; import saveAs from 'file-saver';
import dayjs, { Dayjs } from 'dayjs';
import { useClipboard } from '@vueuse/core';
import { parseSizeFromByte } from '@/utils/parse-utils';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n(); const { t } = useI18n();
const ws = new WS(); const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true }); const queue = new PQueue({ concurrency: 1, autoStart: true });
@@ -27,7 +31,25 @@ const queue = new PQueue({ concurrency: 1, autoStart: true });
let neOtions = ref<Record<string, any>[]>([]); let neOtions = ref<Record<string, any>[]>([]);
/**开始结束时间 */ /**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']); let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
dayjs().startOf('hour'),
dayjs().endOf('hour'),
]);
/**时间范围 */
let rangePickerPresets = ref([
{
label: 'Now hour',
value: [dayjs().startOf('hour'), dayjs().endOf('hour')],
},
{ label: 'Today', value: [dayjs().startOf('day'), dayjs().endOf('day')] },
{
label: 'Yesterday',
value: [
dayjs().subtract(1, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'),
],
},
]);
/**查询参数 */ /**查询参数 */
let queryParams = reactive({ let queryParams = reactive({
@@ -38,9 +60,9 @@ let queryParams = reactive({
sortField: 'timestamp', sortField: 'timestamp',
sortOrder: 'desc', sortOrder: 'desc',
/**开始时间 */ /**开始时间 */
startTime: '', startTime: undefined as undefined | number,
/**结束时间 */ /**结束时间 */
endTime: '', endTime: undefined as undefined | number,
/**当前页数 */ /**当前页数 */
pageNum: 1, pageNum: 1,
/**每页条数 */ /**每页条数 */
@@ -51,12 +73,10 @@ let queryParams = reactive({
function fnQueryReset() { function fnQueryReset() {
queryParams = Object.assign(queryParams, { queryParams = Object.assign(queryParams, {
subscriberID: '', subscriberID: '',
startTime: '',
endTime: '',
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
}); });
queryRangePicker.value = ['', '']; queryRangePicker.value = [dayjs().startOf('hour'), dayjs().endOf('hour')];
tablePagination.current = 1; tablePagination.current = 1;
tablePagination.pageSize = 20; tablePagination.pageSize = 20;
fnGetList(); fnGetList();
@@ -86,7 +106,7 @@ let tableState: TabeStateType = reactive({
}); });
/**表格字段列 */ /**表格字段列 */
let tableColumns: ColumnsType = [ let tableColumns = ref<ColumnsType>([
{ {
title: t('common.rowId'), title: t('common.rowId'),
dataIndex: 'id', dataIndex: 'id',
@@ -94,7 +114,7 @@ let tableColumns: ColumnsType = [
width: 100, width: 100,
}, },
{ {
title: t('views.dashboard.cdr.smfChargingID'), // 计费ID title: t('views.dashboard.cdr.chargingID'), // 计费ID
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
align: 'left', align: 'left',
width: 100, width: 100,
@@ -137,11 +157,15 @@ let tableColumns: ColumnsType = [
) { ) {
return 0; return 0;
} }
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer; let dataVolumeUplink = 0;
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) { for (const v of listOfMultipleUnitUsage) {
return 0; if (Array.isArray(v.usedUnitContainer)) {
for (const used of v.usedUnitContainer) {
dataVolumeUplink += +used.dataVolumeUplink;
} }
return usedUnitContainer[0].dataVolumeUplink; }
}
return parseSizeFromByte(dataVolumeUplink, 'MB');
}, },
}, },
{ {
@@ -158,11 +182,15 @@ let tableColumns: ColumnsType = [
) { ) {
return 0; return 0;
} }
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer; let dataVolumeDownlink = 0;
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) { for (const v of listOfMultipleUnitUsage) {
return 0; if (Array.isArray(v.usedUnitContainer)) {
for (const used of v.usedUnitContainer) {
dataVolumeDownlink += +used.dataVolumeDownlink;
} }
return usedUnitContainer[0].dataVolumeDownlink; }
}
return parseSizeFromByte(dataVolumeDownlink, 'MB');
}, },
}, },
{ {
@@ -179,15 +207,19 @@ let tableColumns: ColumnsType = [
) { ) {
return 0; return 0;
} }
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer; let dataTotalVolume = 0;
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) { for (const v of listOfMultipleUnitUsage) {
return 0; if (Array.isArray(v.usedUnitContainer)) {
for (const used of v.usedUnitContainer) {
dataTotalVolume += +used.dataTotalVolume;
} }
return usedUnitContainer[0].dataTotalVolume; }
}
return parseSizeFromByte(dataTotalVolume, 'MB');
}, },
}, },
{ {
title: t('views.dashboard.cdr.smfDuration'), // 持续时间 title: t('views.dashboard.cdr.durationTime'), // 持续时间
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
align: 'left', align: 'left',
width: 100, width: 100,
@@ -197,7 +229,7 @@ let tableColumns: ColumnsType = [
}, },
}, },
{ {
title: t('views.dashboard.cdr.smfInvocationTime'), // 调用时间 title: t('views.dashboard.cdr.invocationTime'), // 调用时间
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
align: 'left', align: 'left',
width: 200, width: 200,
@@ -211,7 +243,7 @@ let tableColumns: ColumnsType = [
key: 'id', key: 'id',
align: 'left', align: 'left',
}, },
]; ]);
/**表格分页器参数 */ /**表格分页器参数 */
let tablePagination = reactive({ let tablePagination = reactive({
@@ -306,6 +338,18 @@ function fnRecordDelete(id: string) {
}); });
} }
/**
* 复制CDR
* @param jsonStr JSON字符串
*/
function fnRecordCopy(jsonStr: string) {
if (!jsonStr) return;
const text = JSON.stringify(jsonStr, null, 2);
copy(text).then(() => {
message.success(t('common.copyOk'), 3);
});
}
/**查询列表, pageNum初始页数 */ /**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) { function fnGetList(pageNum?: number) {
if (tableState.loading) return; if (tableState.loading) return;
@@ -313,11 +357,19 @@ function fnGetList(pageNum?: number) {
if (pageNum) { if (pageNum) {
queryParams.pageNum = pageNum; queryParams.pageNum = pageNum;
} }
if (!queryRangePicker.value) {
queryRangePicker.value = ['', '']; // 时间范围
if (
Array.isArray(queryRangePicker.value) &&
queryRangePicker.value.length > 0
) {
queryParams.startTime = queryRangePicker.value[0].valueOf();
queryParams.endTime = queryRangePicker.value[1].valueOf();
} else {
queryParams.startTime = undefined;
queryParams.endTime = undefined;
} }
queryParams.startTime = queryRangePicker.value[0];
queryParams.endTime = queryRangePicker.value[1];
listSMFDataCDR(toRaw(queryParams)).then(res => { listSMFDataCDR(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选 // 取消勾选
@@ -406,7 +458,9 @@ function fnRealTime() {
subGroupID: `1006_${queryParams.neId}`, subGroupID: `1006_${queryParams.neId}`,
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: wsError, onerror: (ev: any) => {
console.error(ev);
},
}; };
ws.connect(options); ws.connect(options);
} else { } else {
@@ -416,12 +470,6 @@ function fnRealTime() {
} }
} }
/**接收数据后回调 */
function wsError(ev: any) {
// 接收数据后回调
console.error(ev);
}
/**接收数据后回调 */ /**接收数据后回调 */
function wsMessage(res: Record<string, any>) { function wsMessage(res: Record<string, any>) {
const { code, requestId, data } = res; const { code, requestId, data } = res;
@@ -510,6 +558,7 @@ onBeforeUnmount(() => {
v-model:value="queryParams.neId" v-model:value="queryParams.neId"
:options="neOtions" :options="neOtions"
:placeholder="t('common.selectPlease')" :placeholder="t('common.selectPlease')"
@change="fnQueryReset()"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
@@ -533,12 +582,12 @@ onBeforeUnmount(() => {
> >
<a-range-picker <a-range-picker
v-model:value="queryRangePicker" v-model:value="queryRangePicker"
allow-clear :presets="rangePickerPresets"
bordered :bordered="true"
:allow-clear="false"
style="width: 100%"
:show-time="{ format: 'HH:mm:ss' }" :show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
value-format="x"
style="width: 100%"
></a-range-picker> ></a-range-picker>
</a-form-item> </a-form-item>
</a-col> </a-col>
@@ -591,6 +640,7 @@ onBeforeUnmount(() => {
:disabled="tableState.selectedRowKeys.length <= 0" :disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading" :loading="modalState.confirmLoading"
@click.prevent="fnRecordDelete('0')" @click.prevent="fnRecordDelete('0')"
v-perms:has="['cdr:ne:remove']"
> >
<template #icon><DeleteOutlined /></template> <template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }} {{ t('common.deleteText') }}
@@ -669,11 +719,23 @@ onBeforeUnmount(() => {
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'id'"> <template v-if="column.key === 'id'">
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.copyText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordCopy(record.cdrJSON)"
>
<template #icon>
<CopyOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.deleteText') }}</template> <template #title>{{ t('common.deleteText') }}</template>
<a-button <a-button
type="link" type="link"
@click.prevent="fnRecordDelete(record.id)" @click.prevent="fnRecordDelete(record.id)"
v-perms:has="['cdr:ne:remove']"
> >
<template #icon> <template #icon>
<DeleteOutlined /> <DeleteOutlined />
@@ -742,9 +804,17 @@ onBeforeUnmount(() => {
<a-divider orientation="left"> <a-divider orientation="left">
List Of Multiple Unit Usage List Of Multiple Unit Usage
</a-divider> </a-divider>
<div v-for="u in record.cdrJSON.listOfMultipleUnitUsage"> <div v-for="u in record.cdrJSON.listOfMultipleUnitUsage">
<div>RatingGroup: {{ u.ratingGroup }}</div> <!-- <div>RatingGroup: {{ u.ratingGroup }}</div> -->
<div v-for="udata in u.usedUnitContainer"> <div
v-for="(udata, i) in u.usedUnitContainer"
style="display: flex"
>
<!-- <strong style="margin-right: 12px">
{{ i }}
</strong> -->
<div>
<div> <div>
<span>Data Total Volume: </span> <span>Data Total Volume: </span>
<span>{{ udata.dataTotalVolume }}</span> <span>{{ udata.dataTotalVolume }}</span>
@@ -757,9 +827,10 @@ onBeforeUnmount(() => {
<span>Data Volume Uplink: </span> <span>Data Volume Uplink: </span>
<span>{{ udata.dataVolumeUplink }}</span> <span>{{ udata.dataVolumeUplink }}</span>
</div> </div>
<div> <!-- <div>
<span>Time: </span> <span>Time: </span>
<span>{{ udata.time }}</span> <span>{{ udata.time }}</span>
</div> -->
</div> </div>
</div> </div>
</div> </div>
+699
View File
@@ -0,0 +1,699 @@
<script setup lang="ts">
import * as echarts from 'echarts/core';
import {
TitleComponent,
ToolboxComponent,
TooltipComponent,
GridComponent,
LegendComponent,
DataZoomComponent,
} from 'echarts/components';
import { LineChart } from 'echarts/charts';
import { UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([
TitleComponent,
ToolboxComponent,
TooltipComponent,
GridComponent,
LegendComponent,
DataZoomComponent,
LineChart,
CanvasRenderer,
UniversalTransition,
]);
import { reactive, onMounted, toRaw, onBeforeUnmount, ref } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import useI18n from '@/hooks/useI18n';
import { listSMFDataCDR } from '@/api/neData/smf';
import {
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import { parseSizeFromByte } from '@/utils/parse-utils';
import { message } from 'ant-design-vue';
import useNeInfoStore from '@/store/modules/neinfo';
import dayjs, { Dayjs } from 'dayjs';
const { t, currentLocale } = useI18n();
const ws = new WS();
/**图DOM节点实例对象 */
const cdrChartDom = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
let cdrChart: echarts.ECharts | null = null;
/**图表配置 */
const option = {
title: {
text: 'Data Usage Report',
left: 'left',
},
tooltip: {
trigger: 'axis',
axisPointer: {
animation: true,
},
formatter: (params: any) => {
const title = params[0].name;
let uplinkValue = 0;
let downlinkValue = 0;
if (params[0].seriesName === 'Uplink') {
uplinkValue = params[0].value;
} else {
downlinkValue = params[0].value;
}
if (params[1].seriesName === 'Uplink') {
uplinkValue = params[1].value;
} else {
downlinkValue = params[1].value;
}
const uplinkValueF = parseSizeFromByte(uplinkValue, 'MB');
const downlinkValueF = parseSizeFromByte(downlinkValue, 'MB');
return `
<div style="font-weight: bold;">${title}</div>
<div>Downlink: ${downlinkValueF}</div>
<div>Uplink: ${uplinkValueF}</div>
`;
},
},
toolbox: {
feature: {
dataZoom: {
yAxisIndex: 'none',
},
saveAsImage: {},
},
},
axisPointer: {
link: [
{
xAxisIndex: 'all',
},
],
},
dataZoom: [
{
show: false,
realtime: true,
start: 0,
end: 100,
xAxisIndex: [0, 1],
},
{
type: 'inside',
realtime: true,
start: 0,
end: 100,
xAxisIndex: [0, 1],
},
],
grid: [
{
left: '10%',
right: 50,
height: '40%',
},
{
left: '10%',
right: 50,
top: '50%',
height: '40%',
},
],
xAxis: [
{
type: 'category',
boundaryGap: false,
axisLine: { onZero: true },
data: [], // x轴初始数据
axisLabel: {
show: false, // 显示标签
rotate: 15, // 设置倾斜角度(如15度)
},
},
{
gridIndex: 1,
type: 'category',
boundaryGap: false,
axisLine: { onZero: true },
data: [], // x轴初始数据
axisLabel: {
show: false, // 隐藏第二个 x 轴的标签
},
position: 'top',
},
],
yAxis: [
{
name: 'Downlink (Byte)',
type: 'value',
},
{
gridIndex: 1,
name: 'Uplink (Byte)',
type: 'value',
inverse: true,
},
],
series: [
{
name: 'Downlink',
type: 'line',
data: [], // y轴初始数据
symbol: 'circle', // 数据点形状
symbolSize: 6, // 数据点大小
smooth: true, // 平滑曲线
color: 'rgb(0, 190, 99)',
areaStyle: {
color: {
colorStops: [
{ offset: 0, color: 'rgba(0, 190, 99, .5)' },
{ offset: 1, color: 'rgba(0, 190, 99, 0.5)' },
],
x: 0,
y: 0,
x2: 0,
y2: 1,
type: 'linear',
global: false,
},
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10,
},
},
{
name: 'Uplink',
type: 'line',
xAxisIndex: 1,
yAxisIndex: 1,
data: [], // y轴初始数据
symbol: 'circle', // 数据点形状
symbolSize: 6, // 数据点大小
smooth: true, // 平滑曲线
color: 'rgb(17, 178, 255)',
areaStyle: {
color: {
colorStops: [
{ offset: 0, color: 'rgba(17, 178, 255, .5)' },
{ offset: 1, color: 'rgba(17, 178, 255, 0.5)' },
],
x: 0,
y: 0,
x2: 0,
y2: 1,
type: 'linear',
global: false,
},
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10,
},
},
],
};
/**绘制图表 */
function fnRanderChart() {
const container: HTMLElement | undefined = cdrChartDom.value;
if (!container) return;
const locale = currentLocale.value.split('_')[0];
cdrChart = echarts.init(container, 'light', {
// https://github.com/apache/echarts/tree/release/src/i18n 取值langEN.ts ==> EN
locale: locale.toUpperCase(),
});
cdrChart.setOption(option);
// cdrChart.showLoading('default', {
// text: 'Please enter IMSI to query user traffic',
// fontSize: 16, // 字体大小
// });
// 创建 ResizeObserver 实例 监听图表容器大小变化,并在变化时调整图表大小
var observer = new ResizeObserver(entries => {
if (cdrChart && !cdrChart.isDisposed) {
cdrChart.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
/**网元可选 */
let neOtions = ref<Record<string, any>[]>([]);
/**开始结束时间 */
let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
dayjs().startOf('hour'),
dayjs().endOf('hour'),
]);
/**时间范围 */
let rangePickerPresets = ref([
{
label: 'Now hour',
value: [dayjs().startOf('hour'), dayjs().endOf('hour')],
},
{ label: 'Today', value: [dayjs().startOf('day'), dayjs().endOf('day')] },
{
label: 'Yesterday',
value: [
dayjs().subtract(1, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'),
],
},
]);
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: 'SMF',
neId: '001',
subscriberID: '',
dnn: '',
sortField: 'timestamp',
sortOrder: 'desc',
/**开始时间 */
startTime: undefined as undefined | number,
/**结束时间 */
endTime: undefined as undefined | number,
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 1000,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams.subscriberID = '';
queryParams.dnn = '';
queryRangePicker.value = [dayjs().startOf('hour'), dayjs().endOf('hour')];
fnGetList(1);
}
let state = reactive({
/**表格数据 */
data: [] as any[],
/**表格总数 */
total: 0,
/**表格加载状态 */
loading: false,
/**数据总量 up,down */
dataUsage: ['0 B', '0 B'],
});
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (state.loading) return;
state.loading = true;
if (!queryParams.subscriberID) {
message.warning('Please enter IMSI to query user traffic');
state.loading = false;
return;
}
if (pageNum) {
queryParams.pageNum = pageNum;
}
if (cdrChart) {
cdrChart.showLoading('default', {
text: 'Loading...',
fontSize: 16, // 字体大小
});
}
// 时间范围
if (
Array.isArray(queryRangePicker.value) &&
queryRangePicker.value.length > 0
) {
queryParams.startTime = queryRangePicker.value[0].valueOf();
queryParams.endTime = queryRangePicker.value[1].valueOf();
} else {
queryParams.startTime = undefined;
queryParams.endTime = undefined;
}
listSMFDataCDR(toRaw(queryParams))
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
state.total = res.total;
// 遍历处理cdr字符串数据
state.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;
})
.reverse();
}
})
.finally(() => {
state.loading = false;
fnRanderChartDataLoad();
});
}
/**图表配置数据x轴 */
let dataTimeXAxisData: string[] = [];
/**图表配置数据y轴 */
let dataVolumeUplinkYSeriesData: number[] = [];
let dataVolumeDownlinkYSeriesData: number[] = [];
/**图表数据渲染 */
function fnRanderChartDataLoad() {
if (!cdrChart) return;
dataTimeXAxisData = [];
dataVolumeUplinkYSeriesData = [];
dataVolumeDownlinkYSeriesData = [];
if (state.data.length > 0) {
// 处理数据渲染图表
for (const item of state.data) {
if (!item.cdrJSON.invocationTimestamp) {
break;
}
// 时间
const dataTime = item.cdrJSON.invocationTimestamp;
const listOfMultipleUnitUsage = item.cdrJSON.listOfMultipleUnitUsage;
if (
!Array.isArray(listOfMultipleUnitUsage) ||
listOfMultipleUnitUsage.length < 1
) {
return 0;
}
// 数据
let dataVolumeUplink = 0;
let dataVolumeDownlink = 0;
for (const v of listOfMultipleUnitUsage) {
if (Array.isArray(v.usedUnitContainer)) {
for (const used of v.usedUnitContainer) {
dataVolumeUplink += +used.dataVolumeUplink;
dataVolumeDownlink += +used.dataVolumeDownlink;
}
}
}
dataTimeXAxisData.push(dataTime);
dataVolumeUplinkYSeriesData.push(dataVolumeUplink);
dataVolumeDownlinkYSeriesData.push(dataVolumeDownlink);
}
// 绘制图数据
fnRanderChartDataUpdate();
} else {
message.warning('No Data');
cdrChart.hideLoading();
cdrChart.setOption({
title: {
text: `Data Volume Uplink / Downlink By IMSI ${queryParams.subscriberID}`,
},
xAxis: [
{
data: dataTimeXAxisData,
},
{
data: dataTimeXAxisData,
},
],
series: [
{
data: dataVolumeUplinkYSeriesData,
},
{
data: dataVolumeDownlinkYSeriesData,
},
],
});
}
}
/**图表数据渲染 */
function fnRanderChartDataUpdate() {
if (cdrChart == null) return;
// 绘制图数据
cdrChart.setOption({
title: {
text: `Data Usage Report of IMSI ${queryParams.subscriberID}`,
},
xAxis: [
{
data: dataTimeXAxisData,
},
{
data: dataTimeXAxisData,
},
],
series: [
{
data: dataVolumeUplinkYSeriesData,
},
{
data: dataVolumeDownlinkYSeriesData,
},
],
});
cdrChart.hideLoading();
// 累加总量
let uplinkTotal = 0;
let downlinkTotal = 0;
for (let index = 0; index < dataVolumeUplinkYSeriesData.length; index++) {
uplinkTotal += dataVolumeUplinkYSeriesData[index];
downlinkTotal += dataVolumeDownlinkYSeriesData[index];
}
state.dataUsage = [
parseSizeFromByte(uplinkTotal, 'MB'),
parseSizeFromByte(downlinkTotal, 'MB'),
];
}
/**
* 实时数据
*/
function fnRealTime() {
if (ws.state() === WebSocket.OPEN) {
ws.close();
}
// 建立链接
const options: OptionsType = {
url: '/ws',
params: {
/**订阅通道组
*
* CDR会话事件-SMF (GroupID:1006)
*/
subGroupID: `1006_${queryParams.neId}`,
},
onmessage: (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 === `1006_${queryParams.neId}`) {
const cdrEvent = data.data;
// 对应结束时间内
if (queryParams.endTime) {
const endTime = Math.round(queryParams.endTime / 1000);
if (cdrEvent.timestamp > endTime) {
return;
}
}
const cdrJSON = cdrEvent.CDR;
if (!cdrJSON.invocationTimestamp) {
return;
}
// 对应IMSI
if (
cdrJSON.subscriberIdentifier.subscriptionIDData !==
queryParams.subscriberID
) {
return;
}
// 时间
const dataTime = cdrJSON.invocationTimestamp;
const listOfMultipleUnitUsage = cdrJSON.listOfMultipleUnitUsage;
if (
!Array.isArray(listOfMultipleUnitUsage) ||
listOfMultipleUnitUsage.length < 1
) {
return 0;
}
// 数据
let dataVolumeUplink = 0;
let dataVolumeDownlink = 0;
for (const v of listOfMultipleUnitUsage) {
if (Array.isArray(v.usedUnitContainer)) {
for (const used of v.usedUnitContainer) {
dataVolumeUplink += +used.dataVolumeUplink;
dataVolumeDownlink += +used.dataVolumeDownlink;
}
}
}
// 添加数据
dataTimeXAxisData.push(dataTime);
dataVolumeUplinkYSeriesData.push(dataVolumeUplink);
dataVolumeDownlinkYSeriesData.push(dataVolumeDownlink);
fnRanderChartDataUpdate();
}
},
onerror: (ev: any) => {
console.error(ev);
},
};
ws.connect(options);
}
onMounted(() => {
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
let arr: Record<string, any>[] = [];
res.data.forEach((v: any) => {
if (v.neType === 'SMF') {
arr.push({ value: v.neId, label: v.neName });
}
});
neOtions.value = arr;
if (arr.length > 0) {
queryParams.neId = arr[0].value;
}
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
fnRanderChart();
fnRealTime();
});
});
onBeforeUnmount(() => {
ws.close();
if (cdrChart) {
cdrChart.clear();
cdrChart.dispose();
}
});
</script>
<template>
<PageContainer>
<a-card
: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="SMF" name="neId ">
<a-select
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
@change="fnRealTime()"
:disabled="state.loading"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="IMSI" name="subscriberID" :required="true">
<a-input
v-model:value="queryParams.subscriberID"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="40"
:disabled="state.loading"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="DNN" name="dnn">
<a-input
v-model:value="queryParams.dnn"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="40"
:disabled="state.loading"
></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)"
:loading="state.loading"
>
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button
type="default"
@click.prevent="fnQueryReset"
:disabled="state.loading"
>
<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.time')"
name="queryRangePicker"
>
<a-range-picker
v-model:value="queryRangePicker"
:presets="rangePickerPresets"
:bordered="true"
:allow-clear="false"
style="width: 100%"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
:disabled="state.loading"
></a-range-picker>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false">
<!-- 图数据 -->
<div ref="cdrChartDom" style="height: 600px; width: 100%"></div>
<a-descriptions title="Data Usage" bordered :column="2">
<a-descriptions-item label="Total Uplink">
{{ state.dataUsage[0] }}
</a-descriptions-item>
<a-descriptions-item label="Total Downlink">
{{ state.dataUsage[1] }}
</a-descriptions-item>
</a-descriptions>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped></style>
+100 -27
View File
@@ -21,6 +21,10 @@ import { parseDateToStr } from '@/utils/date-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket'; import { OptionsType, WS } from '@/plugins/ws-websocket';
import saveAs from 'file-saver'; import saveAs from 'file-saver';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
import { useClipboard } from '@vueuse/core';
import { hasPermissions } from '@/plugins/auth-user';
import dayjs, { type Dayjs } from 'dayjs';
const { copy } = useClipboard({ legacy: true });
const { getDict } = useDictStore(); const { getDict } = useDictStore();
const { t } = useI18n(); const { t } = useI18n();
const ws = new WS(); const ws = new WS();
@@ -38,7 +42,25 @@ let dict: {
let neOtions = ref<Record<string, any>[]>([]); let neOtions = ref<Record<string, any>[]>([]);
/**开始结束时间 */ /**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']); let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
dayjs().startOf('hour'),
dayjs().endOf('hour'),
]);
/**时间范围 */
let rangePickerPresets = ref([
{
label: 'Now hour',
value: [dayjs().startOf('hour'), dayjs().endOf('hour')],
},
{ label: 'Today', value: [dayjs().startOf('day'), dayjs().endOf('day')] },
{
label: 'Yesterday',
value: [
dayjs().subtract(1, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'),
],
},
]);
/**查询参数 */ /**查询参数 */
let queryParams = reactive({ let queryParams = reactive({
@@ -51,9 +73,9 @@ let queryParams = reactive({
sortField: 'timestamp', sortField: 'timestamp',
sortOrder: 'desc', sortOrder: 'desc',
/**开始时间 */ /**开始时间 */
startTime: '', startTime: undefined as undefined | number,
/**结束时间 */ /**结束时间 */
endTime: '', endTime: undefined as undefined | number,
/**当前页数 */ /**当前页数 */
pageNum: 1, pageNum: 1,
/**每页条数 */ /**每页条数 */
@@ -67,12 +89,12 @@ function fnQueryReset() {
recordType: '', recordType: '',
callerParty: '', callerParty: '',
calledParty: '', calledParty: '',
startTime: '', startTime: undefined,
endTime: '', endTime: undefined,
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
}); });
queryRangePicker.value = ['', '']; queryRangePicker.value = [dayjs().startOf('hour'), dayjs().endOf('hour')];
tablePagination.current = 1; tablePagination.current = 1;
tablePagination.pageSize = 20; tablePagination.pageSize = 20;
fnGetList(); fnGetList();
@@ -144,7 +166,7 @@ let tableColumns: ColumnsType = [
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
key: 'callerParty', key: 'callerParty',
align: 'left', align: 'left',
width: 120, width: 150,
customRender(opt) { customRender(opt) {
const cdrJSON = opt.value; const cdrJSON = opt.value;
return cdrJSON.callerParty; return cdrJSON.callerParty;
@@ -155,7 +177,7 @@ let tableColumns: ColumnsType = [
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
key: 'calledParty', key: 'calledParty',
align: 'left', align: 'left',
width: 120, width: 150,
customRender(opt) { customRender(opt) {
const cdrJSON = opt.value; const cdrJSON = opt.value;
return cdrJSON.calledParty; return cdrJSON.calledParty;
@@ -173,7 +195,7 @@ let tableColumns: ColumnsType = [
title: t('views.dashboard.cdr.time'), title: t('views.dashboard.cdr.time'),
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
align: 'left', align: 'left',
width: 150, width: 200,
customRender(opt) { customRender(opt) {
const cdrJSON = opt.value; const cdrJSON = opt.value;
if (typeof cdrJSON.updateTime === 'number') { if (typeof cdrJSON.updateTime === 'number') {
@@ -282,6 +304,18 @@ function fnRecordDelete(id: string) {
}); });
} }
/**
* 复制CDR
* @param jsonStr JSON字符串
*/
function fnRecordCopy(jsonStr: string) {
if (!jsonStr) return;
const text = JSON.stringify(jsonStr, null, 2);
copy(text).then(() => {
message.success(t('common.copyOk'), 3);
});
}
/**查询列表, pageNum初始页数 */ /**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) { function fnGetList(pageNum?: number) {
if (tableState.loading) return; if (tableState.loading) return;
@@ -289,11 +323,19 @@ function fnGetList(pageNum?: number) {
if (pageNum) { if (pageNum) {
queryParams.pageNum = pageNum; queryParams.pageNum = pageNum;
} }
if (!queryRangePicker.value) {
queryRangePicker.value = ['', '']; // 时间范围
if (
Array.isArray(queryRangePicker.value) &&
queryRangePicker.value.length > 0
) {
queryParams.startTime = queryRangePicker.value[0].valueOf();
queryParams.endTime = queryRangePicker.value[1].valueOf();
} else {
queryParams.startTime = undefined;
queryParams.endTime = undefined;
} }
queryParams.startTime = queryRangePicker.value[0];
queryParams.endTime = queryRangePicker.value[1];
listSMSCDataCDR(toRaw(queryParams)).then(res => { listSMSCDataCDR(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选 // 取消勾选
@@ -382,7 +424,9 @@ function fnRealTime() {
subGroupID: `1007_${queryParams.neId}`, subGroupID: `1007_${queryParams.neId}`,
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: wsError, onerror: (ev: any) => {
console.error(ev);
},
}; };
ws.connect(options); ws.connect(options);
} else { } else {
@@ -392,12 +436,6 @@ function fnRealTime() {
} }
} }
/**接收数据后回调 */
function wsError(ev: any) {
// 接收数据后回调
console.error(ev);
}
/**接收数据后回调 */ /**接收数据后回调 */
function wsMessage(res: Record<string, any>) { function wsMessage(res: Record<string, any>) {
const { code, requestId, data } = res; const { code, requestId, data } = res;
@@ -492,6 +530,7 @@ onBeforeUnmount(() => {
v-model:value="queryParams.neId" v-model:value="queryParams.neId"
:options="neOtions" :options="neOtions"
:placeholder="t('common.selectPlease')" :placeholder="t('common.selectPlease')"
@change="fnQueryReset()"
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
@@ -554,12 +593,12 @@ onBeforeUnmount(() => {
> >
<a-range-picker <a-range-picker
v-model:value="queryRangePicker" v-model:value="queryRangePicker"
allow-clear :presets="rangePickerPresets"
bordered :bordered="true"
:allow-clear="false"
style="width: 100%"
:show-time="{ format: 'HH:mm:ss' }" :show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
value-format="x"
style="width: 100%"
></a-range-picker> ></a-range-picker>
</a-form-item> </a-form-item>
</a-col> </a-col>
@@ -598,6 +637,7 @@ onBeforeUnmount(() => {
:disabled="tableState.selectedRowKeys.length <= 0" :disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading" :loading="modalState.confirmLoading"
@click.prevent="fnRecordDelete('0')" @click.prevent="fnRecordDelete('0')"
v-perms:has="['cdr:ne:remove']"
> >
<template #icon><DeleteOutlined /></template> <template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }} {{ t('common.deleteText') }}
@@ -665,7 +705,7 @@ onBeforeUnmount(() => {
:data-source="tableState.data" :data-source="tableState.data"
:size="tableState.size" :size="tableState.size"
:pagination="tablePagination" :pagination="tablePagination"
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }" :scroll="{ x: tableColumns.length * 180, y: 'calc(100vh - 480px)' }"
:row-selection="{ :row-selection="{
type: 'checkbox', type: 'checkbox',
columnWidth: '48px', columnWidth: '48px',
@@ -689,11 +729,23 @@ onBeforeUnmount(() => {
</template> </template>
<template v-if="column.key === 'id'"> <template v-if="column.key === 'id'">
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.copyText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordCopy(record.cdrJSON)"
>
<template #icon>
<CopyOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.deleteText') }}</template> <template #title>{{ t('common.deleteText') }}</template>
<a-button <a-button
type="link" type="link"
@click.prevent="fnRecordDelete(record.id)" @click.prevent="fnRecordDelete(record.id)"
v-perms:has="['cdr:ne:remove']"
> >
<template #icon> <template #icon>
<DeleteOutlined /> <DeleteOutlined />
@@ -704,7 +756,8 @@ onBeforeUnmount(() => {
</template> </template>
</template> </template>
<template #expandedRowRender="{ record }"> <template #expandedRowRender="{ record }">
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px"> <a-row :gutter="16">
<a-col :lg="8" :md="12" :xs="22" :offset="2">
<a-divider orientation="left"> <a-divider orientation="left">
{{ t('views.dashboard.cdr.cdrInfo') }} {{ t('views.dashboard.cdr.cdrInfo') }}
</a-divider> </a-divider>
@@ -726,6 +779,8 @@ onBeforeUnmount(() => {
}} }}
</span> </span>
</div> </div>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-divider orientation="left"> <a-divider orientation="left">
{{ t('views.dashboard.cdr.rowInfo') }} {{ t('views.dashboard.cdr.rowInfo') }}
</a-divider> </a-divider>
@@ -755,7 +810,25 @@ onBeforeUnmount(() => {
{{ t('views.dashboard.cdr.resultOk') }} {{ t('views.dashboard.cdr.resultOk') }}
</span> </span>
</div> </div>
</div> </a-col>
<a-col
:lg="16"
:md="16"
:xs="22"
:offset="2"
v-if="
record.cdrJSON?.smsContent &&
hasPermissions(['cdr:smsc:content'])
"
>
<a-divider orientation="left"> Content </a-divider>
<a-typography-paragraph
copyable
:content="record.cdrJSON.smsContent"
>
</a-typography-paragraph>
</a-col>
</a-row>
</template> </template>
</a-table> </a-table>
</a-card> </a-card>
+21 -51
View File
@@ -145,66 +145,54 @@ let alarmTableState: TabeStateType = reactive({
let tableColumns: ColumnsType = [ let tableColumns: ColumnsType = [
{ {
title: t('views.faultManage.activeAlarm.origLevel'), title: t('views.faultManage.activeAlarm.origLevel'),
align: 'center', align: 'left',
key: 'origSeverity', key: 'origSeverity',
dataIndex: 'origSeverity', dataIndex: 'origSeverity',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.alarmTitle'), title: t('views.faultManage.activeAlarm.alarmTitle'),
dataIndex: 'alarmTitle', dataIndex: 'alarmTitle',
align: 'left', align: 'left',
width: 5, width: 200,
}, },
{ {
title: t('views.faultManage.activeAlarm.neType'), title: t('views.faultManage.activeAlarm.neType'),
dataIndex: 'neType', dataIndex: 'neType',
align: 'center', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.eventTime'), title: t('views.faultManage.activeAlarm.eventTime'),
dataIndex: 'eventTime', dataIndex: 'eventTime',
align: 'center', align: 'left',
sorter: (a: any, b: any) => 1, sorter: (a: any, b: any) => 1,
width: 5, width: 150,
}, },
{ {
title: t('views.faultManage.activeAlarm.alarmCode'), title: t('views.faultManage.activeAlarm.alarmCode'),
dataIndex: 'alarmCode', dataIndex: 'alarmCode',
align: 'center', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.alarmType'), title: t('views.faultManage.activeAlarm.alarmType'),
dataIndex: 'alarmType', dataIndex: 'alarmType',
key: 'alarmType', key: 'alarmType',
align: 'left', align: 'left',
width: 5, width: 120,
}, },
{ {
title: t('views.faultManage.activeAlarm.neName'), title: t('views.faultManage.activeAlarm.neName'),
dataIndex: 'neName', dataIndex: 'neName',
align: 'center', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.neId'), title: t('views.faultManage.activeAlarm.neId'),
dataIndex: 'neId', dataIndex: 'neId',
align: 'center', align: 'left',
width: 5, width: 100,
},
{
title: t('views.faultManage.activeAlarm.pvFlag'),
dataIndex: 'pvFlag',
align: 'center',
width: 5,
},
{
title: t('views.faultManage.activeAlarm.alarmId'),
dataIndex: 'alarmId',
align: 'center',
width: 5,
}, },
{ {
@@ -212,20 +200,20 @@ let tableColumns: ColumnsType = [
dataIndex: 'ackState', dataIndex: 'ackState',
key: 'ackState', key: 'ackState',
align: 'left', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.ackUser'), title: t('views.faultManage.activeAlarm.ackUser'),
dataIndex: 'ackUser', dataIndex: 'ackUser',
align: 'left', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('common.operate'), title: t('common.operate'),
key: 'alarm_id', key: 'alarm_id',
align: 'center', align: 'left',
fixed: 'right', fixed: 'right',
width: 5, width: 100,
}, },
]; ];
@@ -1022,12 +1010,12 @@ onMounted(() => {
:data-source="tableState.data" :data-source="tableState.data"
:size="tableState.size" :size="tableState.size"
:row-selection="{ :row-selection="{
columnWidth: 2, columnWidth: 48,
selectedRowKeys: state.selectedRowKeys, selectedRowKeys: state.selectedRowKeys,
onChange: onSelectChange, onChange: onSelectChange,
}" }"
:pagination="tablePagination" :pagination="tablePagination"
:scroll="{ x: 2500, y: 400 }" :scroll="{ x: tableColumns.length * 150, y: 400 }"
> >
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'origSeverity'"> <template v-if="column.key === 'origSeverity'">
@@ -1213,14 +1201,6 @@ onMounted(() => {
{{ modalState.from.alarmType }} {{ modalState.from.alarmType }}
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.pvFlag')"
name="pvFlag"
>
{{ modalState.from.pvFlag }}
</a-form-item>
</a-col>
</a-row> </a-row>
<a-form-item <a-form-item
@@ -1231,17 +1211,7 @@ onMounted(() => {
{{ modalState.from.locationInfo }} {{ modalState.from.locationInfo }}
</a-form-item> </a-form-item>
<a-row> </a-row>
<a-row> <a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.province')"
name="province"
>
{{ modalState.from.province }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.faultManage.activeAlarm.origLevel')" :label="t('views.faultManage.activeAlarm.origLevel')"
@@ -1290,7 +1260,7 @@ onMounted(() => {
:label="t('views.faultManage.activeAlarm.ackState')" :label="t('views.faultManage.activeAlarm.ackState')"
name="ackState" name="ackState"
> >
{{ modalState.from.ackState }} <DictTag :options="dict.activeAckState" :value="modalState.from.ackState" />
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
+14 -28
View File
@@ -118,61 +118,47 @@ let tableState: TabeStateType = reactive({
/**表格字段列 */ /**表格字段列 */
let tableColumns: ColumnsType = [ let tableColumns: ColumnsType = [
{
title: t('views.faultManage.activeAlarm.alarmId'),
dataIndex: 'alarmId',
align: 'center',
width: 5,
},
{ {
title: t('views.faultManage.activeAlarm.neId'), title: t('views.faultManage.activeAlarm.neId'),
dataIndex: 'neId', dataIndex: 'neId',
align: 'center', align: 'left',
width: 5, width: 150,
}, },
{ {
title: t('views.faultManage.activeAlarm.neName'), title: t('views.faultManage.activeAlarm.neName'),
dataIndex: 'neName', dataIndex: 'neName',
align: 'center', align: 'left',
width: 5, width: 150,
}, },
{ {
title: t('views.faultManage.activeAlarm.neType'), title: t('views.faultManage.activeAlarm.neType'),
dataIndex: 'neType', dataIndex: 'neType',
align: 'center', align: 'left',
width: 5, width: 150,
}, },
{ {
title: t('views.faultManage.activeAlarm.alarmCode'), title: t('views.faultManage.activeAlarm.alarmCode'),
dataIndex: 'alarmCode', dataIndex: 'alarmCode',
align: 'center', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.alarmTitle'), title: t('views.faultManage.activeAlarm.alarmTitle'),
dataIndex: 'alarmTitle', dataIndex: 'alarmTitle',
align: 'left', align: 'left',
width: 5, width: 200,
}, },
{ {
title: t('views.faultManage.activeAlarm.eventTime'), title: t('views.faultManage.activeAlarm.eventTime'),
dataIndex: 'eventTime', dataIndex: 'eventTime',
align: 'center',
sorter: (a: any, b: any) => 1, sorter: (a: any, b: any) => 1,
width: 5, align: 'left',
}, width: 200,
{
title: t('views.faultManage.activeAlarm.pvFlag'),
dataIndex: 'pvFlag',
align: 'center',
width: 5,
}, },
{ {
title: t('common.operate'), title: t('common.operate'),
key: 'alarm_id', key: 'alarm_id',
align: 'center', align: 'left',
fixed: 'right',
width: 5,
}, },
]; ];
@@ -316,7 +302,7 @@ function mapKeysWithReduce(data: any, titleMapping: any) {
const title = titleMapping[key] || key; const title = titleMapping[key] || key;
newItem[title] = item[key]; newItem[title] = item[key];
return newItem; return newItem;
}); }, {});
}); });
} }
@@ -626,7 +612,7 @@ onMounted(() => {
</a-dropdown> </a-dropdown>
</a-tooltip> </a-tooltip>
<TableColumnsDnd <TableColumnsDnd
cache-id="alarmActive" cache-id="alarmEvent"
:columns="tableColumns" :columns="tableColumns"
v-model:columns-dnd="tableColumnsDnd" v-model:columns-dnd="tableColumnsDnd"
></TableColumnsDnd> ></TableColumnsDnd>
+33 -70
View File
@@ -122,108 +122,96 @@ let tableState: TabeStateType = reactive({
/**表格字段列 */ /**表格字段列 */
let tableColumns: ColumnsType = [ let tableColumns: ColumnsType = [
{
title: t('views.faultManage.activeAlarm.alarmId'),
dataIndex: 'alarmId',
align: 'center',
width: 5,
},
{ {
title: t('views.faultManage.activeAlarm.neId'), title: t('views.faultManage.activeAlarm.neId'),
dataIndex: 'neId', dataIndex: 'neId',
align: 'center', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.neName'), title: t('views.faultManage.activeAlarm.neName'),
dataIndex: 'neName', dataIndex: 'neName',
align: 'center', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.neType'), title: t('views.faultManage.activeAlarm.neType'),
dataIndex: 'neType', dataIndex: 'neType',
align: 'center', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.origLevel'), title: t('views.faultManage.activeAlarm.origLevel'),
align: 'left', align: 'left',
dataIndex: 'origSeverity', dataIndex: 'origSeverity',
key: 'origSeverity', key: 'origSeverity',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.alarmCode'), title: t('views.faultManage.activeAlarm.alarmCode'),
dataIndex: 'alarmCode', dataIndex: 'alarmCode',
align: 'center', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.alarmTitle'), title: t('views.faultManage.activeAlarm.alarmTitle'),
dataIndex: 'alarmTitle', dataIndex: 'alarmTitle',
align: 'left', align: 'left',
width: 5, width: 200,
}, },
{ {
title: t('views.faultManage.activeAlarm.eventTime'), title: t('views.faultManage.activeAlarm.eventTime'),
dataIndex: 'eventTime', dataIndex: 'eventTime',
align: 'center', align: 'left',
sorter: (a: any, b: any) => 1, sorter: (a: any, b: any) => 1,
width: 5, width: 150,
}, },
{ {
title: t('views.faultManage.activeAlarm.alarmType'), title: t('views.faultManage.activeAlarm.alarmType'),
dataIndex: 'alarmType', dataIndex: 'alarmType',
key: 'alarmType', key: 'alarmType',
align: 'left', align: 'left',
width: 5, width: 120,
},
{
title: t('views.faultManage.activeAlarm.pvFlag'),
dataIndex: 'pvFlag',
align: 'center',
width: 5,
}, },
{ {
title: t('views.faultManage.activeAlarm.clearUser'), title: t('views.faultManage.activeAlarm.clearUser'),
dataIndex: 'clearUser', dataIndex: 'clearUser',
align: 'left', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.clearType'), title: t('views.faultManage.activeAlarm.clearType'),
dataIndex: 'clearType', dataIndex: 'clearType',
key: 'clearType', key: 'clearType',
align: 'left', align: 'left',
width: 5, width: 120,
}, },
{ {
title: t('views.faultManage.activeAlarm.clearTime'), title: t('views.faultManage.activeAlarm.clearTime'),
dataIndex: 'clearTime', dataIndex: 'clearTime',
align: 'left', align: 'left',
sorter: (a: any, b: any) => 1, sorter: (a: any, b: any) => 1,
width: 5, width: 150,
}, },
{ {
title: t('views.faultManage.activeAlarm.ackState'), title: t('views.faultManage.activeAlarm.ackState'),
dataIndex: 'ackState', dataIndex: 'ackState',
key: 'ackState', key: 'ackState',
align: 'left', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('views.faultManage.activeAlarm.ackUser'), title: t('views.faultManage.activeAlarm.ackUser'),
dataIndex: 'ackUser', dataIndex: 'ackUser',
align: 'left', align: 'left',
width: 5, width: 100,
}, },
{ {
title: t('common.operate'), title: t('common.operate'),
key: 'alarm_id', key: 'alarm_id',
align: 'center', align: 'left',
fixed: 'right', fixed: 'right',
width: 5, width: 120,
}, },
]; ];
@@ -648,7 +636,7 @@ onMounted(() => {
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<a-row> <a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.faultManage.activeAlarm.alarmCode')" :label="t('views.faultManage.activeAlarm.alarmCode')"
@@ -688,22 +676,7 @@ onMounted(() => {
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
<a-row> <a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.pvFlag')"
name="pv_flag"
>
<a-select
v-model:value="queryParams.pv_flag"
:placeholder="t('common.selectPlease')"
:options="[
{ label: 'PNF', value: 'PNF' },
{ label: 'VNF', value: 'VNF' },
]"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="6" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.faultManage.activeAlarm.alarmType')" :label="t('views.faultManage.activeAlarm.alarmType')"
@@ -804,12 +777,12 @@ onMounted(() => {
:data-source="tableState.data" :data-source="tableState.data"
:size="tableState.size" :size="tableState.size"
:row-selection="{ :row-selection="{
columnWidth: 3, columnWidth: 48,
selectedRowKeys: state.selectedRowKeys, selectedRowKeys: state.selectedRowKeys,
onChange: onSelectChange, onChange: onSelectChange,
}" }"
:pagination="tablePagination" :pagination="tablePagination"
:scroll="{ x: tableColumns.length * 120, y: 400 }" :scroll="{ x: tableColumns.length * 150, y: 400 }"
> >
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'origSeverity'"> <template v-if="column.key === 'origSeverity'">
@@ -863,6 +836,7 @@ onMounted(() => {
@ok="fnModalOk" @ok="fnModalOk"
:ok-text="t('views.faultManage.activeAlarm.confirm')" :ok-text="t('views.faultManage.activeAlarm.confirm')"
@cancel="fnModalCancel" @cancel="fnModalCancel"
:footer="null"
> >
<a-form <a-form
name="modalStateFrom" name="modalStateFrom"
@@ -946,15 +920,6 @@ onMounted(() => {
</a-row> </a-row>
<a-row> <a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.pvFlag')"
name="pvFlag"
>
{{ modalState.from.pvFlag }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.faultManage.activeAlarm.alarmType')" :label="t('views.faultManage.activeAlarm.alarmType')"
@@ -973,14 +938,6 @@ onMounted(() => {
{{ modalState.from.locationInfo }} {{ modalState.from.locationInfo }}
</a-form-item> </a-form-item>
<a-row> <a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.province')"
name="province"
>
{{ modalState.from.province }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.faultManage.activeAlarm.origLevel')" :label="t('views.faultManage.activeAlarm.origLevel')"
@@ -1019,7 +976,10 @@ onMounted(() => {
:label="t('views.faultManage.activeAlarm.clearType')" :label="t('views.faultManage.activeAlarm.clearType')"
name="clearType" name="clearType"
> >
{{ modalState.from.clearType }} <DictTag
:options="dict.activeClearType"
:value="modalState.from.clearType"
/>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
@@ -1046,7 +1006,10 @@ onMounted(() => {
:label="t('views.faultManage.activeAlarm.ackState')" :label="t('views.faultManage.activeAlarm.ackState')"
name="ackState" name="ackState"
> >
{{ modalState.from.ackState }} <DictTag
:options="dict.activeAckState"
:value="modalState.from.ackState"
/>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
-1
View File
@@ -43,7 +43,6 @@ onMounted(() => {
getConfigKey('sys.homePage').then(res => { getConfigKey('sys.homePage').then(res => {
spinning.value = false; spinning.value = false;
if (res.code === RESULT_CODE_SUCCESS && res.data) { if (res.code === RESULT_CODE_SUCCESS && res.data) {
console.log(spinning);
if (res.data) { if (res.data) {
const asyncComponent = findView(`${res.data}`); const asyncComponent = findView(`${res.data}`);
currentComponent.value = defineAsyncComponent(asyncComponent); currentComponent.value = defineAsyncComponent(asyncComponent);
+245 -1
View File
@@ -1,19 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, onMounted, toRaw } from 'vue'; import { reactive, ref, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout'; import { PageContainer } from 'antdv-pro-layout';
import { ProModal } from 'antdv-pro-modal';
import { SizeType } from 'ant-design-vue/es/config-provider'; import { SizeType } from 'ant-design-vue/es/config-provider';
import { ColumnsType } from 'ant-design-vue/es/table'; import { ColumnsType } from 'ant-design-vue/es/table';
import { Modal, message } from 'ant-design-vue/es'; import { Form, Modal, message } from 'ant-design-vue/es';
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr } from '@/utils/date-utils';
import { import {
getBakFile, getBakFile,
getBakFileList, getBakFileList,
downFile, downFile,
delFile, delFile,
updateFTPInfo,
getFTPInfo,
putFTPInfo,
} from '@/api/logManage/exportFile'; } from '@/api/logManage/exportFile';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import saveAs from 'file-saver'; import saveAs from 'file-saver';
import { regExpIPv4 } from '@/utils/regular-utils';
const { t } = useI18n(); const { t } = useI18n();
/**网元参数 */ /**网元参数 */
@@ -269,6 +274,131 @@ onMounted(() => {
// fnGetList(); // fnGetList();
// }); // });
}); });
/**对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
openByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**FTP日志对象信息状态 */
let modalState: ModalStateType = reactive({
openByEdit: false,
title: '设置远程备份配置',
from: {
username: '',
password: '',
toIp: '',
toPort: 22,
enable: false,
dir: '',
},
confirmLoading: false,
});
/**FTP日志对象信息内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
toIp: [
{
required: true,
pattern: regExpIPv4,
message: 'Please enter the service login IP',
},
],
username: [
{
required: true,
trigger: 'blur',
message: 'Please enter the service login user name',
},
],
dir: [
{
required: true,
trigger: 'blur',
message: 'Please enter the service address target file directory',
},
],
})
);
/**
* 对话框弹出显示为 新增或者修改
* @param configId 参数编号id, 不传为新增
*/
function fnModalVisibleByEdit() {
if (modalState.confirmLoading) return;
const hide = message.loading(t('common.loading'), 0);
modalState.confirmLoading = true;
getFTPInfo().then(res => {
modalState.confirmLoading = false;
hide();
if (res.code === RESULT_CODE_SUCCESS && res.data) {
modalState.from = Object.assign(modalState.from, res.data);
modalState.title = 'Setting Remote Backup';
modalState.openByEdit = true;
} else {
message.error(res.msg, 3);
modalState.title = 'Setting Remote Backup';
modalState.openByEdit = false;
}
});
}
/**FTP对象保存 */
function fnModalOk() {
modalStateFrom.validate().then(() => {
modalState.confirmLoading = true;
const from = toRaw(modalState.from);
updateFTPInfo(from)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(`Configuration saved successfully`, 3);
fnModalCancel();
} else {
message.warning(`Configuration save exception`, 3);
}
})
.finally(() => {
modalState.confirmLoading = false;
});
});
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.openByEdit = false;
modalStateFrom.resetFields();
}
/**
* 同步文件到FTP
* @param row
*/
function fnSyncFileToFTP(row: Record<string, any>) {
putFTPInfo(row.filePath, row.fileName)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
} else {
message.warning(res.msg, 3);
}
})
.finally(() => {
modalState.confirmLoading = false;
});
}
</script> </script>
<template> <template>
@@ -305,6 +435,12 @@ onMounted(() => {
<!-- 插槽-卡片右侧 --> <!-- 插槽-卡片右侧 -->
<template #extra> <template #extra>
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-tooltip>
<template #title>Setting Remote Backup</template>
<a-button type="text" @click.prevent="fnModalVisibleByEdit()">
<template #icon><DeliveredProcedureOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.reloadText') }}</template> <template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()"> <a-button type="text" @click.prevent="fnGetList()">
@@ -328,6 +464,14 @@ onMounted(() => {
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'fileName'"> <template v-if="column.key === 'fileName'">
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-button
type="link"
:loading="downLoading"
@click.prevent="fnSyncFileToFTP(record)"
v-if="record.fileType === 'file'"
>
<template #icon><CloudServerOutlined /></template>
</a-button>
<a-button <a-button
type="link" type="link"
:loading="downLoading" :loading="downLoading"
@@ -349,6 +493,106 @@ onMounted(() => {
</template> </template>
</a-table> </a-table>
</a-card> </a-card>
<!-- 新增框或修改框 -->
<ProModal
:drag="true"
:width="800"
:destroyOnClose="true"
: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 }"
:label-wrap="true"
>
<a-form-item label="Enable" name="enable" :label-col="{ span: 3 }">
<a-switch
v-model:checked="modalState.from.enable"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
/>
</a-form-item>
<template v-if="modalState.from.enable">
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="Service IP"
name="toIp"
v-bind="modalStateFrom.validateInfos.toIp"
>
<a-input
v-model:value="modalState.from.toIp"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="Service Port"
name="toPort"
v-bind="modalStateFrom.validateInfos.toPort"
>
<a-input-number
v-model:value="modalState.from.toPort"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="UserName"
name="username"
v-bind="modalStateFrom.validateInfos.username"
>
<a-input
v-model:value="modalState.from.username"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="Password"
name="password"
v-bind="modalStateFrom.validateInfos.password"
>
<a-input-password
v-model:value="modalState.from.password"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input-password>
</a-form-item>
</a-col>
</a-row>
<a-form-item
label="Save Dir"
name="dir"
v-bind="modalStateFrom.validateInfos.dir"
:label-col="{ span: 3 }"
>
<a-input
v-model:value="modalState.from.dir"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</template>
</a-form>
</ProModal>
</PageContainer> </PageContainer>
</template> </template>
+1
View File
@@ -50,6 +50,7 @@ async function fnGetState() {
graphG6.value.setItemState(neShape, 'neState', ne.serverState.online); graphG6.value.setItemState(neShape, 'neState', ne.serverState.online);
} }
} }
await new Promise(resolve => setTimeout(resolve, 15_000));
} }
/**查询全部网元数据列表 */ /**查询全部网元数据列表 */
@@ -92,13 +92,13 @@ const graphNodeMenu = new Menu({
${neState.neName ?? '--'} ${neState.neName ?? '--'}
</h3> </h3>
<div id="restart" style="cursor: pointer; margin-bottom: 4px"> <div id="restart" style="cursor: pointer; margin-bottom: 4px">
> ${t('views.configManage.neManage.restart')} > ${t('views.ne.common.restart')}
</div> </div>
<div id="stop" style="cursor: pointer; margin-bottom: 4px;"> <div id="stop" style="cursor: pointer; margin-bottom: 4px;">
> ${t('views.configManage.neManage.stop')} > ${t('views.ne.common.stop')}
</div> </div>
<div id="log" style="cursor: pointer; margin-bottom: 4px;"> <div id="log" style="cursor: pointer; margin-bottom: 4px;">
> ${t('views.configManage.neManage.log')} > ${t('views.ne.common.log')}
</div> </div>
</div> </div>
`; `;
@@ -130,26 +130,17 @@ export function edgeLineAnimateState() {
// circle-move 圆点沿边运动 // circle-move 圆点沿边运动
if (name === 'circle-move') { if (name === 'circle-move') {
let back1 = group.find( const backArr = group.findAll((ele: any) =>
(ele: any) => ele.get('name') === 'circle-stroke1' ele.get('name').startsWith('circle-stroke')
); );
if (back1) {
back1.remove();
back1.destroy();
}
let back2 = group.find(
(ele: any) => ele.get('name') === 'circle-stroke2'
);
if (back2) {
back2.remove();
back2.destroy();
}
if (value) { if (value) {
if (backArr.length > 0) return;
// 第一个矩形边 // 第一个矩形边
const fillColor = typeof value === 'string' ? value : '#1890ff'; const fillColor = typeof value === 'string' ? value : '#1890ff';
// 边缘路径的起始位置 // 边缘路径的起始位置
const startPoint = keyShape.getPoint(0); const startPoint = keyShape.getPoint(0);
back1 = group.addShape('circle', { const back1 = group.addShape('circle', {
attrs: { attrs: {
x: startPoint.x, x: startPoint.x,
y: startPoint.y, y: startPoint.y,
@@ -159,6 +150,7 @@ export function edgeLineAnimateState() {
// 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性
name: 'circle-stroke1', name: 'circle-stroke1',
}); });
back1.show();
back1.animate( back1.animate(
(ratio: any) => { (ratio: any) => {
// 每帧中的操作。比率范围从 0 到 1,表示动画的进度。返回修改后的配置 // 每帧中的操作。比率范围从 0 到 1,表示动画的进度。返回修改后的配置
@@ -177,7 +169,7 @@ export function edgeLineAnimateState() {
); );
// 第二个矩形边 // 第二个矩形边
const endPoint = keyShape.getPoint(1); const endPoint = keyShape.getPoint(1);
back2 = group.addShape('circle', { const back2 = group.addShape('circle', {
zIndex: -2, zIndex: -2,
attrs: { attrs: {
x: endPoint.x, x: endPoint.x,
@@ -188,7 +180,7 @@ export function edgeLineAnimateState() {
// 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性 // 在 G6 3.3 及之后的版本中,必须指定 name,可以是任意字符串,但需要在同一个自定义元素类型中保持唯一性
name: 'circle-stroke2', name: 'circle-stroke2',
}); });
back2.show();
back2.animate( back2.animate(
(ratio: any) => { (ratio: any) => {
// 每帧中的操作。比率范围从 0 到 1,表示动画的进度。返回修改后的配置 // 每帧中的操作。比率范围从 0 到 1,表示动画的进度。返回修改后的配置
@@ -205,6 +197,14 @@ export function edgeLineAnimateState() {
duration: 2 * 1000, // 执行一次的持续时间 duration: 2 * 1000, // 执行一次的持续时间
} }
); );
} else {
if (backArr.length <= 0) return;
backArr.forEach((ele: any) => {
ele.hide();
ele.remove();
ele.destroy();
});
backArr.length = 0;
} }
return; return;
} }
@@ -212,11 +212,7 @@ export function edgeLineAnimateState() {
// line-dash 虚线运动 // line-dash 虚线运动
if (name === 'line-dash') { if (name === 'line-dash') {
if (value) { if (value) {
keyShape.stopAnimate(); if (keyShape.cfg.animating) return;
keyShape.attr({
lineDash: null,
lineDashOffset: null,
});
let index = 0; let index = 0;
keyShape.animate( keyShape.animate(
() => { () => {
@@ -234,6 +230,12 @@ export function edgeLineAnimateState() {
duration: 3000, // 执行一次的持续时间 duration: 3000, // 执行一次的持续时间
} }
); );
} else {
keyShape.stopAnimate();
keyShape.attr({
lineDash: null,
lineDashOffset: null,
});
} }
return; return;
} }
@@ -246,6 +248,7 @@ export function edgeLineAnimateState() {
back.remove(); back.remove();
back.destroy(); back.destroy();
} }
const { path, stroke, lineWidth } = keyShape.attr(); const { path, stroke, lineWidth } = keyShape.attr();
back = group.addShape('path', { back = group.addShape('path', {
attrs: { attrs: {
@@ -300,12 +300,23 @@ export function nodeImageAnimateState() {
ele.get('name').startsWith('circle-shape') ele.get('name').startsWith('circle-shape')
); );
if (value) { if (value) {
if (Array.isArray(backArr) && backArr.length === 3) return; const fillColor = typeof value === 'string' ? value : '#f5222d';
// 移除
if (Array.isArray(backArr) && backArr.length >= 3) {
for (const back of backArr) {
back.stopAnimate();
back.hide();
back.remove();
back.destroy();
}
backArr.length = 0;
}
// 根据大小确定半径
const size = Array.isArray(model?.size) ? model?.size : [40, 40]; const size = Array.isArray(model?.size) ? model?.size : [40, 40];
const x = size[0] / 2; const x = size[0] / 2;
const y = -size[1] / 2; const y = -size[1] / 2;
const r = 3; const r = 3;
const fillColor = typeof value === 'string' ? value : '#f5222d';
// 第一个背景圆 // 第一个背景圆
const back1 = group.addShape('circle', { const back1 = group.addShape('circle', {
@@ -0,0 +1,383 @@
<script setup lang="ts">
import { reactive, ref, toRaw, watch } from 'vue';
import { ProModal } from 'antdv-pro-modal';
import { message, Modal } from 'ant-design-vue/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { ColumnsType } from 'ant-design-vue/es/table';
import { saveAs } from 'file-saver';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useI18n from '@/hooks/useI18n';
import { exportNBState, listNBState } from '@/api/neData/nb-state';
import { Dayjs } from 'dayjs';
const { t } = useI18n();
const emit = defineEmits(['cancel', 'update:open']);
const props = defineProps({
title: {
type: String,
default: '标题',
},
open: {
type: Boolean,
default: false,
},
/**网元ID */
neId: {
type: String,
default: false,
},
neType: {
type: String,
default: false,
},
});
/**开始结束时间 */
let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>(undefined);
/**状态字典 */
const nbStateOptions = ref<DictType[]>([
{
value: 'ON',
label: t('views.neData.baseStation.online'),
tagType: 'green',
tagClass: '',
},
{
value: 'OFF',
label: t('views.neData.baseStation.offline'),
tagType: 'red',
tagClass: '',
},
]);
/**查询参数 */
let queryParams = reactive({
/**网元 */
neType: '',
neId: '',
/**排序字段 */
sortField: 'id',
sortOrder: 'desc',
/**状态 */
status: undefined as undefined | string,
/**开始时间 */
startTime: undefined as undefined | number,
/**结束时间 */
endTime: undefined as undefined | number,
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
status: undefined,
startTime: undefined,
endTime: undefined,
pageNum: 1,
pageSize: 20,
});
queryRangePicker.value = undefined;
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 = ref<ColumnsType>([
{
title: t('common.rowId'),
dataIndex: 'id',
align: 'left',
width: 80,
},
{
title: t('views.neData.baseStation.name'),
dataIndex: 'name',
align: 'left',
resizable: true,
width: 120,
minWidth: 100,
maxWidth: 250,
ellipsis: true,
},
{
title: t('views.neData.baseStation.position'),
dataIndex: 'position',
align: 'left',
resizable: true,
width: 150,
minWidth: 100,
maxWidth: 400,
ellipsis: true,
},
{
title: t('views.neData.baseStation.address'),
dataIndex: 'address',
align: 'left',
width: 100,
},
{
title: t('views.neData.baseStation.nbName'),
dataIndex: 'nbName',
align: 'left',
resizable: true,
width: 100,
minWidth: 100,
maxWidth: 200,
},
{
title: t('views.neData.baseStation.state'),
dataIndex: 'state',
key: 'state',
align: 'left',
width: 80,
},
{
title: t('views.neData.baseStation.time'),
dataIndex: 'time',
align: 'left',
width: 150,
},
]);
/**表格分页器参数 */
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 fnExportList() {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.neData.baseStation.exportTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
const querys = toRaw(queryParams);
exportNBState(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
saveAs(res.data, `nb_state_history_records_export_${Date.now()}.xlsx`);
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
});
},
});
}
/**查询字典数据列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
// 时间范围
if (
Array.isArray(queryRangePicker.value) &&
queryRangePicker.value.length > 0
) {
queryParams.startTime = queryRangePicker.value[0].valueOf();
queryParams.endTime = queryRangePicker.value[1].valueOf();
} else {
queryParams.startTime = undefined;
queryParams.endTime = undefined;
}
listNBState(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;
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
}
tableState.loading = false;
});
}
/**弹框取消按钮事件 */
function fnModalCancel() {
emit('update:open', false);
emit('cancel');
}
/**监听是否显示,初始数据 */
watch(
() => props.open,
val => {
if (val) {
queryParams.neType = props.neType;
queryParams.neId = props.neId;
// 获取列表数据
fnGetList();
}
}
);
</script>
<template>
<ProModal
:drag="true"
:destroyOnClose="true"
:width="1200"
:title="props.title"
:open="props.open"
:keyboard="false"
:mask-closable="false"
@cancel="fnModalCancel"
:footer="null"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16" style="margin-left: 0; margin-right: 0">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.neData.baseStation.state')"
name="status"
>
<a-select
v-model:value="queryParams.status"
allow-clear
:placeholder="t('common.selectPlease')"
:options="nbStateOptions"
>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="16" :md="12" :xs="24">
<a-form-item
:label="t('views.neData.baseStation.time')"
name="queryRangePicker"
>
<a-range-picker
v-model:value="queryRangePicker"
:bordered="true"
:allow-clear="true"
style="width: 100%"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
></a-range-picker>
</a-form-item>
</a-col>
<a-col :lg="24" :md="24" :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-button type="dashed" @click.prevent="fnExportList()">
<template #icon><ExportOutlined /></template>
{{ t('common.export') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
<!-- 表格列表 -->
<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, y: 'calc(100vh - 480px)' }"
@resizeColumn="(w:number, col:any) => (col.width = w)"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'state'">
<DictTag
:options="nbStateOptions"
:value="record.state"
value-default="OFF"
/>
</template>
</template>
</a-table>
</ProModal>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>
+33
View File
@@ -0,0 +1,33 @@
<script setup lang="ts">
import { ref } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import ListComponent from './list.vue';
import TopologyComponent from './topology.vue';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
const value = ref<string>('list');
</script>
<template>
<PageContainer>
<template #extra>
<a-radio-group v-model:value="value" button-style="solid">
<a-radio-button value="list">
{{ t('views.neData.baseStation.list') }}
</a-radio-button>
<a-radio-button value="topology">
{{ t('views.neData.baseStation.topology') }}
</a-radio-button>
</a-radio-group>
</template>
<div v-show="value === 'list'">
<ListComponent></ListComponent>
</div>
<div v-if="value === 'topology'">
<TopologyComponent></TopologyComponent>
</div>
</PageContainer>
</template>
<style scoped></style>
+958
View File
@@ -0,0 +1,958 @@
<script setup lang="ts">
import {
reactive,
ref,
onMounted,
toRaw,
computed,
defineAsyncComponent,
} from 'vue';
import { Form, message, Modal } from 'ant-design-vue';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { ColumnsType } from 'ant-design-vue/es/table';
import { ProModal } from 'antdv-pro-modal';
import UploadModal from '@/components/UploadModal/index.vue';
import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n';
const { t, currentLocale } = useI18n();
import {
addAMFNbState,
delAMFNbState,
editAMFNbState,
listAMFNbStatelist,
} from '@/api/neData/amf';
import {
addMMENbState,
delMMENbState,
editMMENbState,
listMMENbStatelist,
} from '@/api/neData/mme';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import saveAs from 'file-saver';
import { readSheet, writeSheet } from '@/utils/execl-utils';
import { useRoute } from 'vue-router';
const route = useRoute();
// 异步加载组件
const HistoryModal = defineAsyncComponent(
() => import('./components/history.vue')
);
const nbState = ref<DictType[]>([
{
value: 'ON',
label: t('views.neData.baseStation.online'),
tagType: 'green',
tagClass: '',
},
{
value: 'OFF',
label: t('views.neData.baseStation.offline'),
tagType: 'red',
tagClass: '',
},
]);
/**网元参数 */
let neCascaderOptions = ref<Record<string, any>[]>([]);
/**网元数据 */
let neTypeAndId = ref<string[]>([]);
/**查询参数 */
let queryParams = reactive({
/**网元ID */
neId: '',
/**IMSI */
state: undefined,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
state: undefined,
});
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**记录数据 */
data: Record<string, any>[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'small',
data: [],
selectedRowKeys: [],
});
/**表格字段列 */
let tableColumns = ref<ColumnsType>([
// {
// title: t('common.rowId'),
// dataIndex: 'index',
// align: 'left',
// width: 80,
// },
{
title: t('views.neData.baseStation.name'),
dataIndex: 'name',
align: 'left',
resizable: true,
width: 120,
minWidth: 100,
maxWidth: 250,
ellipsis: true,
},
{
title: t('views.neData.baseStation.position'),
dataIndex: 'position',
align: 'left',
resizable: true,
width: 150,
minWidth: 100,
maxWidth: 400,
ellipsis: true,
},
{
title: t('views.neData.baseStation.address'),
dataIndex: 'address',
align: 'left',
resizable: true,
width: 100,
minWidth: 100,
maxWidth: 200,
},
{
title: t('views.neData.baseStation.nbName'),
dataIndex: 'nbName',
align: 'left',
resizable: true,
width: 100,
minWidth: 100,
maxWidth: 200,
},
{
title: t('views.neData.baseStation.ueNum'),
dataIndex: 'ueNum',
align: 'left',
resizable: true,
width: 80,
minWidth: 80,
maxWidth: 120,
},
{
title: t('views.neData.baseStation.state'),
dataIndex: 'state',
key: 'state',
align: 'left',
resizable: true,
width: 80,
minWidth: 80,
maxWidth: 120,
},
{
title: t('views.neData.baseStation.time'),
align: 'left',
width: 150,
customRender(opt) {
const record = opt.value;
if (record.state === 'OFF') {
return record.offTime || '-';
}
return record.onTime || '-';
},
},
]);
/**表格分页器参数 */
let tablePagination = {
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: true,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
},
};
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
}
/**
* 记录删除
* @param index ID
*/
function fnRecordDelete(index: string) {
const [neType, neId] = neTypeAndId.value;
if (!neId) return;
let msg = `Delete index as:${index}`;
if (index === '0') {
msg = `Remove the index checkbox:${tableState.selectedRowKeys.length}`;
}
Modal.confirm({
title: t('common.tipTitle'),
content: msg,
onOk() {
const reqArr = [];
if (index === '0') {
if (tableState.selectedRowKeys.length <= 0) {
return;
}
for (const v of tableState.selectedRowKeys) {
if (neType === 'MME') {
reqArr.push(delMMENbState(neId, v));
}
if (neType === 'AMF') {
reqArr.push(delAMFNbState(neId, v));
}
}
} else {
if (neType === 'MME') {
reqArr.push(delMMENbState(neId, index));
}
if (neType === 'AMF') {
reqArr.push(delAMFNbState(neId, index));
}
}
if (reqArr.length <= 0) return;
Promise.all(reqArr).then(res => {
const resArr = res.filter(
(item: any) => item.code !== RESULT_CODE_SUCCESS
);
if (resArr.length <= 0) {
message.success({
content: `${t('common.operateOk')}`,
duration: 3,
});
fnGetList();
} else {
message.error({
content: `${t('common.operateErr')}`,
duration: 3,
});
}
});
},
});
}
/**查询列表 */
function fnGetList() {
if (tableState.loading) return;
tableState.loading = true;
const [neType, neId] = neTypeAndId.value;
queryParams.neId = neId;
let req = null;
if (neType === 'MME') {
req = listMMENbStatelist(toRaw(queryParams));
}
if (neType === 'AMF') {
req = listAMFNbStatelist(toRaw(queryParams));
}
if (req === null) {
tableState.data = [];
tableState.loading = false;
return;
}
req.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// 取消勾选
if (tableState.selectedRowKeys.length > 0) {
tableState.selectedRowKeys = [];
}
tableState.data = res.data.filter((item: any) => {
// 状态过滤
if (queryParams.state) {
return item.state === queryParams.state;
}
return true;
});
} else {
tableState.data = [];
}
tableState.loading = false;
});
}
const stateNum = computed(() => {
let onNum = 0;
let offNum = 0;
tableState.data.forEach((item: any) => {
if (item.state === 'ON') {
onNum += 1;
}
if (item.state === 'OFF') {
offNum += 1;
}
});
return [onNum, offNum];
});
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
openByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
/**历史框 */
openByHistory: boolean;
/**导入框 */
openByImport: boolean;
importMsgArr: string[];
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
openByEdit: false,
title: 'NB Config List',
from: {
address: '',
index: undefined,
name: '',
nbName: undefined,
offTime: undefined,
onTime: undefined,
position: '',
state: undefined,
ueNum: undefined,
},
confirmLoading: false,
openByHistory: false,
openByImport: false,
importMsgArr: [],
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
address: [
{ required: true, message: t('views.neData.baseStation.addressPlease') },
],
name: [
{ required: true, message: t('views.neData.baseStation.namePlease') },
],
position: [
{ required: true, message: t('views.neData.baseStation.positionPlease') },
],
})
);
/**
* 对话框弹出显示为 新增或者修改
* @param noticeId 网元id, 不传为新增
*/
function fnModalVisibleByEdit(edit?: string | number) {
if (!edit) {
modalStateFrom.resetFields(); //重置表单
modalState.title = t('views.neData.baseStation.addRadio');
modalState.openByEdit = true;
// 获取最大index
if (tableState.data.length <= 0) {
modalState.from.index = 1;
} else {
const last = tableState.data[tableState.data.length - 1];
modalState.from.index = last.index + 1;
}
}
// 编辑
if (edit === '0') {
const row = tableState.data.find((row: any) => {
return row.index === tableState.selectedRowKeys[0];
});
modalStateFrom.resetFields(); //重置表单
Object.assign(modalState.from, row);
modalState.title = t('views.neData.baseStation.editRadio');
modalState.openByEdit = true;
}
}
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
const [neType, neId] = neTypeAndId.value;
if (!neId) return;
const from = JSON.parse(JSON.stringify(modalState.from));
modalStateFrom
.validate()
.then(e => {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
let result: any = null;
if (neType === 'MME') {
result = from.state
? editMMENbState(neId, from)
: addMMENbState(neId, from);
}
if (neType === 'AMF') {
result = from.state
? editAMFNbState(neId, from)
: addAMFNbState(neId, from);
}
if (result === null) {
return;
}
result
.then((res: any) => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
fnModalCancel();
fnGetList();
} else {
message.error({
content: t('common.operateErr'),
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.openByHistory = false;
modalStateFrom.resetFields();
}
/**导出当前列表 */
function fnExportList() {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.neData.baseStation.exportTip'),
onOk: async () => {
if (modalState.confirmLoading) return;
modalState.confirmLoading = true;
let rows: Record<string, any>[] = [];
// 勾选的网元数据
if (tableState.selectedRowKeys.length > 0) {
rows = tableState.data.filter(item =>
tableState.selectedRowKeys.includes(item.index)
);
} else {
rows = tableState.data;
}
const dataArr: Record<string, any>[] = [];
for (const row of rows) {
let data: any = {};
data[t('views.neData.baseStation.name')] = row.name;
data[t('views.neData.baseStation.position')] = row.position;
data[t('views.neData.baseStation.address')] = row.address;
data[t('views.neData.baseStation.nbName')] = row.nbName;
data[t('views.neData.baseStation.ueNum')] = row.ueNum;
nbState.value.find(item => {
if (item.value === row.state) {
data[t('views.neData.baseStation.state')] = item.label;
}
});
data[t('views.neData.baseStation.time')] = row.time || '-';
dataArr.push(data);
}
// 导出
writeSheet(dataArr, 'Sheet1', {
header: [
t('views.neData.baseStation.name'),
t('views.neData.baseStation.position'),
t('views.neData.baseStation.address'),
t('views.neData.baseStation.nbName'),
t('views.neData.baseStation.ueNum'),
t('views.neData.baseStation.state'),
t('views.neData.baseStation.time'),
],
}).then(fileBlob =>
saveAs(fileBlob, `nb_state_records_export_${Date.now()}.xlsx`)
);
modalState.confirmLoading = false;
tableState.selectedRowKeys = [];
},
});
}
/**对话框弹出历史窗口 */
function fnHistoryView() {
modalState.openByHistory = true;
}
/**对话框表格信息导入弹出窗口 */
function fnModalImportOpen() {
modalState.openByImport = true;
}
function fnModalImportClose() {
modalState.openByImport = false;
fnQueryReset();
}
/**对话框表格信息导入上传 */
function fnModalImportUpload(file: File) {
const hide = message.loading(t('common.loading'), 0);
const [neType, neId] = neTypeAndId.value;
modalState.importMsgArr = [];
// 获取最大index
let index = 0;
if (tableState.data.length <= 0) {
index = 1;
} else {
const last = tableState.data[tableState.data.length - 1];
index = last.index + 1;
}
const reader = new FileReader();
reader.onload = function (e: any) {
const arrayBuffer = e.target.result;
readSheet(arrayBuffer).then(async rows => {
console.log(rows);
if (rows.length <= 0) {
hide();
message.error({
content: t('views.neData.baseStation.importDataEmpty'),
duration: 3,
});
return;
}
// 开始导入
modalState.confirmLoading = true;
for (const row of rows) {
const rowId = row[t('common.rowId')];
const name = row[t('views.neData.baseStation.name')];
const position = row[t('views.neData.baseStation.position')];
const address = row[t('views.neData.baseStation.address')];
let result: any = null;
// 检查IP地址是否定义
const hasAddress = tableState.data.find(
item => item.address === address
);
if (hasAddress) {
// 定义则更新名称位置
if (neType === 'MME') {
result = await editMMENbState(
neId,
Object.assign({}, hasAddress, {
name,
position,
})
);
}
if (neType === 'AMF') {
result = await editAMFNbState(
neId,
Object.assign({}, hasAddress, {
name,
position,
})
);
}
let msg = `${t('common.rowId')}: ${rowId} ${t(
'views.neData.baseStation.editRadio'
)}${t('common.operateErr')}`;
if (result.code === RESULT_CODE_SUCCESS) {
msg = `${t('common.rowId')}: ${rowId} ${t(
'views.neData.baseStation.editRadio'
)}${t('common.operateOk')}`;
}
modalState.importMsgArr.push(msg);
} else {
// 未定义则新增
const form = {
index,
name: `${name}`,
position: `${position}`,
address: `${address}`,
};
if (neType === 'MME') {
result = await addMMENbState(neId, form);
}
if (neType === 'AMF') {
result = await addAMFNbState(neId, form);
}
let msg = `${t('common.rowId')}: ${rowId} ${t(
'views.neData.baseStation.addRadio'
)}${t('common.operateErr')}`;
if (result.code === RESULT_CODE_SUCCESS) {
index += 1;
msg = `${t('common.rowId')}: ${rowId} ${t(
'views.neData.baseStation.addRadio'
)}${t('common.operateOk')}`;
}
modalState.importMsgArr.push(msg);
}
}
hide();
modalState.confirmLoading = false;
});
};
reader.onerror = function (e) {
hide();
console.error('reader file error:', e);
};
reader.readAsArrayBuffer(file);
}
/**对话框表格信息导入模板 */
async function fnModalImportTemplate() {
const baseUrl = import.meta.env.VITE_HISTORY_BASE_URL;
const xlsxUrl = `${
baseUrl.length === 1 && baseUrl.indexOf('/') === 0
? ''
: baseUrl.indexOf('/') === -1
? '/' + baseUrl
: baseUrl
}/nbStateImput`;
const lang = currentLocale.value.split('_')[0];
saveAs(
`${xlsxUrl}/${lang}.xlsx`,
`nb_state_records_import_template_${Date.now()}.xlsx`
);
}
onMounted(() => {
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
let arr: Record<string, any>[] = [];
useNeInfoStore().neCascaderOptions.forEach(item => {
if (['AMF', 'MME'].includes(item.value)) {
arr.push(JSON.parse(JSON.stringify(item)));
}
});
neCascaderOptions.value = arr;
// 无查询参数neType时 默认选择AMF
const queryNeType = (route.query.neType as string) || 'AMF';
const item = arr.find(s => s.value === queryNeType);
if (item && item.children) {
const info = item.children[0];
neTypeAndId.value = [info.neType, info.neId];
} else {
const info = neCascaderOptions.value[0].children[0];
neTypeAndId.value = [info.neType, info.neId];
}
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
// 获取列表数据
fnGetList();
});
});
</script>
<template>
<div>
<a-card
: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-cascader
v-model:value="neTypeAndId"
:options="neCascaderOptions"
:allow-clear="false"
:placeholder="t('common.selectPlease')"
@change="fnGetList"
/>
</a-form-item>
</a-col>
<a-col :lg="4" :md="6" :xs="24">
<a-form-item
:label="t('views.neData.baseStation.state')"
name="state"
>
<a-select
v-model:value="queryParams.state"
:options="nbState"
:placeholder="t('common.selectPlease')"
@change="fnGetList"
/>
</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()">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
<template #icon>
<PlusOutlined />
</template>
{{ t('common.addText') }}
</a-button>
<a-button
type="default"
:disabled="tableState.selectedRowKeys.length != 1"
:loading="modalState.confirmLoading"
@click.prevent="fnModalVisibleByEdit('0')"
>
<template #icon><FormOutlined /></template>
{{ t('common.editText') }}
</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-button type="dashed" @click.prevent="fnModalImportOpen()">
<template #icon><ImportOutlined /></template>
{{ t('common.import') }}
</a-button>
<a-button type="dashed" @click.prevent="fnExportList()">
<template #icon><ExportOutlined /></template>
{{ t('common.export') }}
</a-button>
<a-button type="default" @click.prevent="fnHistoryView()">
<template #icon><ContainerOutlined /></template>
{{ t('views.neData.baseStation.history') }}
</a-button>
</a-space>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<div>
<template
v-if="
queryParams.state === undefined || queryParams.state === 'ON'
"
>
{{ t('views.neData.baseStation.online') }}:
<strong style="color: green">{{ stateNum[0] }} </strong>
</template>
<template
v-if="
queryParams.state === undefined || queryParams.state === 'OFF'
"
>
&nbsp;
{{ t('views.neData.baseStation.offline') }}:
<strong style="color: red">
{{ stateNum[1] }}
</strong>
</template>
</div>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="index"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:row-selection="{
type: 'checkbox',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
@resizeColumn="(w:number, col:any) => (col.width = w)"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'state'">
<DictTag
:options="nbState"
:value="record.state"
value-default="OFF"
/>
</template>
</template>
</a-table>
</a-card>
<!-- 新增框或修改框 -->
<ProModal
:drag="true"
:width="500"
:destroyOnClose="true"
: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-form-item
:label="t('views.neData.baseStation.name')"
name="name"
v-bind="modalStateFrom.validateInfos.name"
>
<a-input
v-model:value="modalState.from.name"
allow-clear
:maxlength="64"
>
</a-input>
</a-form-item>
<a-form-item
:label="t('views.neData.baseStation.position')"
name="position"
v-bind="modalStateFrom.validateInfos.position"
>
<a-input
v-model:value="modalState.from.position"
allow-clear
:maxlength="64"
>
</a-input>
</a-form-item>
<a-form-item
v-if="!modalState.from.state"
:label="t('views.neData.baseStation.address')"
name="address"
v-bind="modalStateFrom.validateInfos.address"
>
<a-input
v-model:value="modalState.from.address"
allow-clear
:maxlength="64"
>
</a-input>
</a-form-item>
</a-form>
</ProModal>
<!-- 状态历史框 -->
<HistoryModal
v-model:open="modalState.openByHistory"
:title="t('views.neData.baseStation.history')"
:ne-type="neTypeAndId[0]"
:ne-id="neTypeAndId[1]"
@cancel="fnModalCancel"
></HistoryModal>
<!-- 上传导入表格数据文件框 -->
<UploadModal
:title="t('common.import')"
@upload="fnModalImportUpload"
@close="fnModalImportClose"
v-model:open="modalState.openByImport"
:ext="['.xls', '.xlsx']"
:size="10"
>
<template #default>
<a-row justify="space-between" align="middle">
<a-col :span="12"> </a-col>
<a-col :span="6">
<a-button
type="link"
:title="t('views.system.user.downloadObj')"
@click.prevent="fnModalImportTemplate"
>
{{ t('views.system.user.downloadObj') }}
</a-button>
</a-col>
</a-row>
<a-textarea
:disabled="true"
:hidden="modalState.importMsgArr.length <= 0"
:value="modalState.importMsgArr.join('\r\n')"
:auto-size="{ minRows: 2, maxRows: 8 }"
style="background-color: transparent; color: rgba(0, 0, 0, 0.85)"
/>
</template>
</UploadModal>
</div>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>
+642
View File
@@ -0,0 +1,642 @@
<script setup lang="ts">
import { reactive, onMounted, ref, onBeforeUnmount, useTemplateRef } from 'vue';
import { Graph, GraphData, Menu, Tooltip, Util } from '@antv/g6';
import { listAMFNbStatelist } from '@/api/neData/amf';
import { parseBasePath } from '@/plugins/file-static-url';
import { edgeLineAnimateState } from '@/views/monitor/topologyBuild/hooks/registerEdge';
import { nodeImageAnimateState } from '@/views/monitor/topologyBuild/hooks/registerNode';
import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { stateNeInfo } from '@/api/ne/neInfo';
import { parseDateToStr } from '@/utils/date-utils';
import { useFullscreen } from '@vueuse/core';
import { listMMENbStatelist } from '@/api/neData/mme';
const { t } = useI18n();
/**图DOM节点实例对象 */
const graphG6Dom = useTemplateRef('graphG6Dom');
/**图数据 */
const graphData = reactive<Record<string, any>>({
nodes: [
{
id: 'omc',
label: 'OMC',
img: parseBasePath('/svg/service_db.svg'),
},
{
id: 'amf1',
label: 'amf1',
img: parseBasePath('/svg/service.svg'),
},
{
id: 'amf2',
label: 'amf2',
img: parseBasePath('/svg/service.svg'),
},
{
id: 'base1',
label: 'base1',
img: parseBasePath('/svg/base.svg'),
},
{
id: 'base2',
label: 'base2',
img: parseBasePath('/svg/base.svg'),
},
],
edges: [
{
source: 'omc',
target: 'amf1',
},
{
source: 'omc',
target: 'amf2',
},
{
source: 'amf1',
target: 'base1',
},
{
source: 'amf2',
target: 'base1',
},
{
source: 'amf2',
target: 'base2',
},
],
});
/**图实例对象 */
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, nType, nInfo }: any = evt.item?.getModel();
if (['GNB', 'ENB'].includes(nType)) {
return `
<div
style="
display: flex;
flex-direction: column;
width: 140px;
"
>
<span>
${t('views.neData.baseStation.name')}:
${label ?? '--'}
</span>
</div>
`;
}
return `
<div
style="
display: flex;
flex-direction: column;
width: 140px;
"
>
<span>
${t('views.monitor.topology.name')}:
${label ?? '--'}
</span>
</div>
`;
},
});
/**图节点展示 */
const graphNodeTooltip = new Tooltip({
offsetX: 10,
offsetY: 20,
getContent(evt) {
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
const { id, label, nType, nInfo }: any = evt.item?.getModel();
if (['GNB', 'ENB'].includes(nType)) {
return `
<div
style="
display: flex;
flex-direction: column;
width: 256px;
"
>
<div><strong>${t('views.neData.baseStation.state')}:</strong><span>
${
nInfo.state === 'ON'
? t('views.neData.baseStation.online')
: t('views.neData.baseStation.offline')
}
</span></div>
<div><strong>${t('views.neData.baseStation.time')}:</strong><span>
${nInfo.state === 'ON' ? nInfo.onTime ?? '--' : nInfo.offTime ?? '--'}
</span></div>
<div>==============================</div>
<div><strong>ID:</strong><span>${nInfo.index}</span></div>
<div><strong>${t('views.neData.baseStation.address')}:</strong><span>
${nInfo.address ?? '--'}</span></div>
<div><strong>${t('views.neData.baseStation.nbName')}:</strong><span>
${nInfo.nbName ?? '--'}</span></div>
<div><strong>${t('views.neData.baseStation.ueNum')}:</strong><span>
${nInfo.ueNum ?? '--'}</span></div>
<div><strong>${t('views.neData.baseStation.name')}:</strong><span>
${nInfo.name ?? '--'}
</span></div>
<div><strong>${t(
'views.neData.baseStation.position'
)}:</strong><span style="word-wrap: break-word;">
${nInfo.position}
</span></div>
</div>
`;
}
return `
<div
style="
display: flex;
flex-direction: column;
width: 200px;
"
>
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
${
nInfo.online
? t('views.monitor.topology.normalcy')
: t('views.monitor.topology.exceptions')
}
</span></div>
<div><strong>${t('views.monitor.topology.refreshTime')}:</strong><span>
${nInfo.refreshTime ?? '--'}
</span></div>
<div>========================</div>
<div><strong>ID:</strong><span>${nInfo.neId}</span></div>
<div><strong>${t('views.monitor.topology.name')}:</strong><span>
${nInfo.neName ?? '--'}
</span></div>
<div><strong>IP:</strong><span>${nInfo.neIP}</span></div>
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
${nInfo.version ?? '--'}
</span></div>
<div><strong>${t('views.monitor.topology.serialNum')}:</strong><span>
${nInfo.sn ?? '--'}
</span></div>
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
${nInfo.expire ?? '--'}
</span></div>
</div>
`;
},
itemTypes: ['node'],
});
/**注册自定义边或节点 */
function registerEdgeNode() {
// 边
edgeLineAnimateState();
// 节点
nodeImageAnimateState();
}
/**
* format the string
* @param {string} str The origin string
* @param {number} maxWidth max width
* @param {number} fontSize font size
* @return {string} the processed result
*/
function fittingString(str: string, maxWidth: number, fontSize: number) {
let currentWidth = 0;
let res = str;
const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
str.split('').forEach((letter, i) => {
if (currentWidth > maxWidth) return;
if (pattern.test(letter)) {
// Chinese charactors
currentWidth += fontSize;
} else {
// get the width of single letter according to the fontSize
currentWidth += Util.getLetterWidth(letter, fontSize);
}
if (currentWidth > maxWidth) {
res = `${str.substring(0, i)}\n${str.substring(i)}`;
}
});
return res;
}
/**图事件 */
function graphEvent(graph: Graph) {
graph.on('edge:mouseenter', evt => {
const { item } = evt;
if (!item) return;
graph.setItemState(item, 'circle-move', '#b5d6fb');
});
graph.on('edge:mouseleave', evt => {
const { item } = evt;
if (!item) return;
graph.setItemState(item, 'circle-move', false);
graph.setItemState(item, 'circle-move:#b5d6fb', false);
});
}
/**图数据渲染 */
function handleRanderGraph(container: HTMLElement | null, data: GraphData) {
if (!container) return;
const { clientHeight, clientWidth } = container;
// 注册自定义边或节点
registerEdgeNode();
const graph = new Graph({
container: container,
width: clientWidth,
height: clientHeight,
fitCenter: false,
fitView: true,
fitViewPadding: [40, 40, 40, 40],
modes: {
// default: ['drag-canvas', 'drag-node', 'zoom-canvas'],
default: [
'zoom-canvas',
'drag-canvas',
'drag-node',
{
type: 'drag-combo',
onlyChangeComboSize: true, // 不改变层级关系
},
{
type: 'collapse-expand-combo',
trigger: 'dblclick',
relayout: true, // 收缩展开后,不重新布局
},
],
},
groupByTypes: false,
plugins: [graphNodeMenu, graphNodeTooltip],
layout: {
type: 'dagre',
rankdir: 'TB', // 布局的方向,TB:从上到下,BT:从下到上,LR:从左到右,RL:从右到左
//align: 'UL', // 节点对齐方式 UL、UR、DL、DR
controlPoints: true,
nodesep: 20,
ranksep: 40,
},
animate: true,
defaultNode: {
type: 'image-animate-state',
labelCfg: {
offset: 8,
position: 'bottom',
style: { fill: '#ffffff', fontSize: 14, fontWeight: 500 },
},
size: 48,
img: parseBasePath('/svg/cloud.svg'),
width: 48,
height: 48,
},
defaultEdge: {
type: 'line-animate-state',
labelCfg: {
autoRotate: true,
refY: 10,
refX: 40,
},
style: {
stroke: '#fafafa',
lineWidth: 1.5,
},
},
defaultCombo: {
labelCfg: {
offset: 16,
position: 'bottom',
style: { fill: '#ffffff', fontSize: 14, fontWeight: 500 },
},
style: {
stroke: '#BDEFDB',
fill: '#BDEFDB',
opacity: 0.25,
},
collapsedSubstituteIcon: {
show: true,
img: parseBasePath('/svg/service.svg'),
width: 48,
height: 48,
},
},
});
graph.data(data);
graph.render();
graphEvent(graph);
// 创建 ResizeObserver 实例
const observer = new ResizeObserver(function (entries) {
// 当元素大小发生变化时触发回调函数
entries.forEach(function (entry) {
if (!graph) {
return;
}
graph.changeSize(entry.contentRect.width, entry.contentRect.height);
graph.fitCenter();
});
});
// 监听元素大小变化
observer.observe(container);
return graph;
}
/**
* 获取图组数据渲染到画布
*/
async function fnGraphDataLoad() {
// 加载基础网元
await useNeInfoStore().fnNelist();
const dataNe = await fnGraphDataBase();
Object.assign(graphData, dataNe);
graphG6.value = handleRanderGraph(graphG6Dom.value, dataNe);
// 添加基站
const dataNb = await fnGraphDataNb(dataNe);
Object.assign(graphData, dataNb);
// graphG6.value.clear();
graphG6.value.read(dataNb);
// 添加状态
interval.value = true;
repeatFn();
}
/**图数据网元构建 */
async function fnGraphDataBase() {
const data: GraphData = {
nodes: [],
edges: [],
};
// 添加基础网元
for (const item of useNeInfoStore().getNeSelectOtions) {
if ('OMC' === item.value) {
if (item.children?.length === 0) continue;
// 是否存在OMC保证唯一
const hasOMC = data.nodes?.findIndex(v => v.neType === 'OMC');
if (hasOMC !== -1) continue;
// 根网元
const omcInfo = item.children[0];
const node = {
id: 'OMC',
label: omcInfo.neName,
img: parseBasePath('/svg/service_db.svg'),
nInfo: { online: false, neId: omcInfo.neId, neType: omcInfo.neType },
nType: 'OMC',
};
// 添加OMC节点
data.nodes?.push(node);
continue;
}
if (['AMF', 'MME'].includes(item.value)) {
if (item.children?.length === 0) continue;
for (const child of item.children) {
const id = `${child.neType}_${child.neId}`;
const node = {
id: id,
label: child.neName,
img: parseBasePath('/svg/service.svg'),
nInfo: { online: false, neId: child.neId, neType: child.neType },
nType: item.value,
};
// 添加节点
data.nodes?.push(node);
data.edges?.push({
source: 'OMC',
target: id,
});
}
item.children.forEach((v: any) => {});
continue;
}
}
return data;
}
/**图数据基站构建 */
async function fnGraphDataNb(data: GraphData) {
const arr = data.nodes?.filter((v: any) => ['AMF', 'MME'].includes(v.nType));
if (arr === undefined || arr.length === 0) return data;
for (const item of arr) {
if (item.nType === 'AMF') {
const neId = (item.nInfo as any).neId;
const res = await listAMFNbStatelist({ neId });
if (res.code !== RESULT_CODE_SUCCESS || !Array.isArray(res.data)) {
continue;
}
for (const nb of res.data) {
const id = `${item.id}_${nb.index}`;
data.nodes?.push({
id: id,
label: fittingString(`${nb.name}`, 80, 14),
img: parseBasePath('/svg/base5G.svg'),
nInfo: nb,
nType: 'GNB',
});
data.edges?.push({
source: item.id,
target: id,
});
}
}
if (item.nType === 'MME') {
const neId = (item.nInfo as any).neId;
const res = await listMMENbStatelist({ neId });
if (res.code !== RESULT_CODE_SUCCESS || !Array.isArray(res.data)) {
continue;
}
for (const nb of res.data) {
const id = `${item.id}_${nb.index}`;
data.nodes?.push({
id: id,
label: fittingString(`${nb.name}`, 80, 14),
img: parseBasePath('/svg/base4G.svg'),
nInfo: nb,
nType: 'ENB',
});
data.edges?.push({
source: item.id,
target: id,
});
}
}
}
return data;
}
/**
* 图状态构建
* @param reload 是否重载状态
*/
async function fnGraphState(reload: boolean = false) {
// 节点状态
if (!Array.isArray(graphData.nodes)) return;
const onc = graphData.nodes.find((v: any) => v.nType === 'OMC');
if (onc) {
const { id, nInfo } = onc as any;
if (!nInfo) return;
const res = await stateNeInfo(nInfo.neType, nInfo.neId);
if (res.code === RESULT_CODE_SUCCESS) {
Object.assign(nInfo, res.data, {
refreshTime: parseDateToStr(res.data.refreshTime, 'HH:mm:ss'),
});
}
const stateColor = nInfo.online ? '#52c41a' : '#f5222d'; // 状态颜色
graphG6.value.setItemState(id, 'top-right-dot', stateColor);
}
graphData.nodes
.filter((v: any) => ['AMF', 'MME'].includes(v.nType))
.forEach(async (v: any) => {
const { id, nInfo } = v;
if (!nInfo) return;
const res = await stateNeInfo(nInfo.neType, nInfo.neId);
if (res.code === RESULT_CODE_SUCCESS) {
Object.assign(nInfo, res.data, {
refreshTime: parseDateToStr(res.data.refreshTime, 'HH:mm:ss'),
});
}
const stateColor = nInfo.online ? '#52c41a' : '#f5222d'; // 状态颜色
graphG6.value.setItemState(id, 'top-right-dot', stateColor);
// 重载时更新下级基站状态
if (reload && nInfo.neType === 'AMF') {
const res = await listAMFNbStatelist({ neId: nInfo.neId });
if (res.code == RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
for (const nb of res.data) {
const nbItem = graphData.nodes.find(
(v: any) => v.id === `${id}_${nb.index}`
);
if (nbItem) {
Object.assign(nbItem.nInfo, nb);
const stateColor = nb.state === 'ON' ? '#52c41a' : '#f5222d'; // 状态颜色
graphG6.value.setItemState(
nbItem.id,
'top-right-dot',
stateColor
);
}
}
}
}
if (reload && nInfo.neType === 'MME') {
const res = await listMMENbStatelist({ neId: nInfo.neId });
if (res.code == RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
for (const nb of res.data) {
const nbItem = graphData.nodes.find(
(v: any) => v.id === `${id}_${nb.index}`
);
if (nbItem) {
Object.assign(nbItem.nInfo, nb);
const stateColor = nb.state === 'ON' ? '#52c41a' : '#f5222d'; // 状态颜色
graphG6.value.setItemState(
nbItem.id,
'top-right-dot',
stateColor
);
}
}
}
}
});
if (reload) {
await new Promise(resolve => setTimeout(resolve, 15_000));
return;
}
// 非重载时使用初始获取的状态
graphData.nodes
.filter((v: any) => ['GNB', 'ENB'].includes(v.nType))
.forEach(async (v: any) => {
const { id, nInfo } = v;
if (!nInfo) return;
const stateColor = nInfo.state === 'ON' ? '#52c41a' : '#f5222d'; // 状态颜色
graphG6.value.setItemState(id, 'top-right-dot', stateColor);
});
}
/**递归调度器 */
const interval = ref<boolean>(false);
/**递归刷新图状态 */
function repeatFn(reload: boolean = false) {
if (!interval.value || !graphG6Dom.value) {
return;
}
fnGraphState(reload)
.finally(() => {
repeatFn(true); // 递归调用自己
})
.catch(error => {
console.error(error);
});
}
const viewportDom = ref<HTMLElement | null>(null);
const { isFullscreen, toggle } = useFullscreen(viewportDom);
function fullscreen() {
toggle();
if (!graphG6Dom.value) return;
if (isFullscreen.value) {
graphG6Dom.value.style.height = 'calc(100vh - 300px)';
} else {
graphG6Dom.value.style.height = '100vh';
}
const { clientHeight, clientWidth } = graphG6Dom.value;
graphG6.value.changeSize(clientHeight, clientWidth);
graphG6.value.fitView(40);
}
onMounted(() => {
fnGraphDataLoad();
});
onBeforeUnmount(() => {
interval.value = false;
});
</script>
<template>
<a-card :bordered="false" :body-style="{ padding: '0' }" ref="viewportDom">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
{{ t('views.neData.baseStation.topologyTitle') }}
</a-space>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-button type="default" @click.prevent="fullscreen()">
<template #icon>
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</template>
{{ t('loayouts.rightContent.fullscreen') }}
</a-button>
</template>
<div ref="graphG6Dom" class="chart"></div>
</a-card>
</template>
<style lang="less" scoped>
.chart {
width: 100%;
height: calc(100vh - 300px);
background-color: rgb(43, 47, 51);
}
</style>
+1 -1
View File
@@ -134,7 +134,7 @@ export default function useConfigList({
/**指定每页可以显示多少条 */ /**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'], pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */ /**只有一页时是否隐藏分页器 */
hideOnSinglePage: true, hideOnSinglePage: false,
/**是否可以快速跳转至某页 */ /**是否可以快速跳转至某页 */
showQuickJumper: true, showQuickJumper: true,
/**是否可以改变 pageSize */ /**是否可以改变 pageSize */
+208 -295
View File
@@ -13,6 +13,9 @@ import useConfigList from './hooks/useConfigList';
import useConfigArray from './hooks/useConfigArray'; import useConfigArray from './hooks/useConfigArray';
import useConfigArrayChild from './hooks/useConfigArrayChild'; import useConfigArrayChild from './hooks/useConfigArrayChild';
import { getAllNeConfig, getNeConfigData } from '@/api/ne/neConfig'; import { getAllNeConfig, getNeConfigData } from '@/api/ne/neConfig';
import { useRoute } from 'vue-router';
const route = useRoute();
const neInfoStore = useNeInfoStore(); const neInfoStore = useNeInfoStore();
const { t } = useI18n(); const { t } = useI18n();
const { ruleVerification, smfByUPFIdLoadData, smfByUPFIdOptions } = useOptions({ const { ruleVerification, smfByUPFIdLoadData, smfByUPFIdOptions } = useOptions({
@@ -76,6 +79,7 @@ function fnSelectConfigNode(_: any, info: any) {
if (treeState.selectNode.paramName == key) { if (treeState.selectNode.paramName == key) {
return; return;
} }
fnActiveConfigNode(key); fnActiveConfigNode(key);
} }
@@ -168,6 +172,31 @@ function fnActiveConfigNode(key: string | number) {
// 列表字段 // 列表字段
const columns: Record<string, any>[] = []; const columns: Record<string, any>[] = [];
for (const rule of arrayState.dataRule.record) { for (const rule of arrayState.dataRule.record) {
if (rule.name === 'name') {
columns.push({
title: rule.display,
dataIndex: rule.name,
align: 'left',
resizable: true,
width: 150,
minWidth: 100,
maxWidth: 350,
customFilterDropdown: true,
onFilter: (value: any, record: any) => {
const regex = new RegExp('^[0-9]{4,8}$');
const name = record.name.value?.toString().trim() || '';
const filterValue = value?.toString().trim();
return regex.test(name) && name.includes(filterValue); // 正则匹配且模糊查询
},
onFilterDropdownOpenChange: (visible: any) => {
if (visible) {
setTimeout(() => {
searchInput.value.focus();
}, 100);
}
},
});
} else {
columns.push({ columns.push({
title: rule.display, title: rule.display,
dataIndex: rule.name, dataIndex: rule.name,
@@ -178,6 +207,10 @@ function fnActiveConfigNode(key: string | number) {
maxWidth: 350, maxWidth: 350,
}); });
} }
}
columns.push({ columns.push({
title: t('common.operate'), title: t('common.operate'),
dataIndex: 'index', dataIndex: 'index',
@@ -195,9 +228,7 @@ function fnActiveConfigNode(key: string | number) {
}, 300); }, 300);
} else { } else {
message.warning({ message.warning({
content: `${param.paramDisplay} ${t( content: `${param.paramDisplay} ${t('views.ne.neConfig.noConfigData')}`,
'views.configManage.configParamForm.noConfigData'
)}`,
duration: 3, duration: 3,
}); });
} }
@@ -205,11 +236,11 @@ function fnActiveConfigNode(key: string | number) {
} }
/**查询配置可选属性值列表 */ /**查询配置可选属性值列表 */
function fnGetNeConfig() { function fnGetNeConfig(routeSelect?: string) {
const neType = neTypeSelect.value[0]; const neType = neTypeSelect.value[0];
if (!neType) { if (!neType) {
message.warning({ message.warning({
content: t('views.configManage.configParamForm.neTypePleace'), content: t('views.ne.neConfig.neTypePleace'),
duration: 3, duration: 3,
}); });
return; return;
@@ -242,7 +273,8 @@ function fnGetNeConfig() {
const item = JSON.parse(JSON.stringify(treeState.data[0])); const item = JSON.parse(JSON.stringify(treeState.data[0]));
treeState.selectNode = item; treeState.selectNode = item;
treeState.selectLoading = false; treeState.selectLoading = false;
fnActiveConfigNode(item.key); // fnActiveConfigNode(((route.query.treeName as string)||item.key));
fnActiveConfigNode(routeSelect || item.key);
} }
} }
}); });
@@ -364,6 +396,25 @@ const {
arrayEditClose, arrayEditClose,
}); });
const state = reactive({
searchText: '',
searchedColumn: '',
});
const searchInput = ref();
const handleSearch = (selectedKeys: any, confirm: any, dataIndex: any) => {
confirm();
state.searchText = selectedKeys[0];
state.searchedColumn = dataIndex;
};
const handleReset = (clearFilters: any) => {
clearFilters({ confirm: true });
state.searchText = '';
};
onMounted(() => { onMounted(() => {
// 获取网元网元列表 // 获取网元网元列表
neInfoStore.fnNelist().then(res => { neInfoStore.fnNelist().then(res => {
@@ -383,7 +434,7 @@ onMounted(() => {
return; return;
} }
// 默认选择AMF // 默认选择AMF
const item = neCascaderOptions.value.find(s => s.value === 'AMF'); const item = neCascaderOptions.value.find(s => s.value === ((route.query.neType as string) || 'MF'));
if (item && item.children) { if (item && item.children) {
const info = item.children[0]; const info = item.children[0];
neTypeSelect.value = [info.neType, info.neId]; neTypeSelect.value = [info.neType, info.neId];
@@ -391,7 +442,7 @@ onMounted(() => {
const info = neCascaderOptions.value[0].children[0]; const info = neCascaderOptions.value[0].children[0];
neTypeSelect.value = [info.neType, info.neId]; neTypeSelect.value = [info.neType, info.neId];
} }
fnGetNeConfig(); fnGetNeConfig((route.query.treeName as string));
} }
} else { } else {
message.warning({ message.warning({
@@ -406,51 +457,30 @@ onMounted(() => {
<template> <template>
<PageContainer> <PageContainer>
<a-row :gutter="16"> <a-row :gutter="16">
<a-col <a-col :lg="6" :md="6" :xs="24" style="margin-bottom: 24px" v-show="collapsible">
:lg="6"
:md="6"
:xs="24"
style="margin-bottom: 24px"
v-show="collapsible"
>
<!-- 网元类型 --> <!-- 网元类型 -->
<a-card size="small" :bordered="false" :loading="treeState.loading"> <a-card size="small" :bordered="false" :loading="treeState.loading">
<template #title> <template #title>
{{ t('views.configManage.configParamForm.treeTitle') }} {{ t('views.ne.neConfig.treeTitle') }}
</template> </template>
<a-form layout="vertical" autocomplete="off"> <a-form layout="vertical" autocomplete="off">
<a-form-item name="neId "> <a-form-item name="neId ">
<a-cascader <a-cascader v-model:value="neTypeSelect" :options="neCascaderOptions" :allow-clear="false"
v-model:value="neTypeSelect" @change="fnGetNeConfig()" />
:options="neCascaderOptions"
:allow-clear="false"
@change="fnGetNeConfig"
/>
</a-form-item> </a-form-item>
<a-form-item name="listeningPort"> <a-form-item name="treeStateData">
<a-tree <a-tree :tree-data="treeState.data" :selected-keys="[treeState.selectNode.paramName]"
:tree-data="treeState.data" @select="fnSelectConfigNode">
:selected-keys="[treeState.selectNode.paramName]"
@select="fnSelectConfigNode"
>
</a-tree> </a-tree>
</a-form-item> </a-form-item>
</a-form> </a-form>
</a-card> </a-card>
</a-col> </a-col>
<a-col :lg="collapsible ? 18 : 24" :md="collapsible ? 18 : 24" :xs="24"> <a-col :lg="collapsible ? 18 : 24" :md="collapsible ? 18 : 24" :xs="24">
<a-card <a-card size="small" :bordered="false" :body-style="{ maxHeight: '700px', 'overflow-y': 'auto' }"
size="small" :loading="treeState.selectLoading">
:bordered="false"
:body-style="{ maxHeight: '600px', 'overflow-y': 'auto' }"
:loading="treeState.selectLoading"
>
<template #title> <template #title>
<a-button <a-button type="text" size="small" @click.prevent="changeCollapsible()">
type="text"
size="small"
@click.prevent="changeCollapsible()"
>
<template #icon> <template #icon>
<MenuFoldOutlined v-show="collapsible" /> <MenuFoldOutlined v-show="collapsible" />
<MenuUnfoldOutlined v-show="!collapsible" /> <MenuUnfoldOutlined v-show="!collapsible" />
@@ -461,18 +491,14 @@ onMounted(() => {
{{ treeState.selectNode.paramDisplay }} {{ treeState.selectNode.paramDisplay }}
</a-typography-text> </a-typography-text>
<a-typography-text type="danger" v-else> <a-typography-text type="danger" v-else>
{{ t('views.configManage.configParamForm.treeSelectTip') }} {{ t('views.ne.neConfig.treeSelectTip') }}
</a-typography-text> </a-typography-text>
</template> </template>
<template #extra> <template #extra>
<a-space :size="8" align="center" v-show="!treeState.selectLoading"> <a-space :size="8" align="center" v-show="!treeState.selectLoading">
<a-tooltip> <a-tooltip placement="topRight">
<template #title>{{ t('common.reloadText') }}</template> <template #title>{{ t('common.reloadText') }}</template>
<a-button <a-button type="default" size="small" @click.prevent="fnActiveConfigNode('#')">
type="default"
size="small"
@click.prevent="fnActiveConfigNode('#')"
>
<template #icon> <template #icon>
<ReloadOutlined /> <ReloadOutlined />
</template> </template>
@@ -482,17 +508,9 @@ onMounted(() => {
</template> </template>
<!-- 单列表格列表 --> <!-- 单列表格列表 -->
<a-table <a-table v-if="treeState.selectNode.paramType === 'list'" class="table" row-key="name" :size="listState.size"
v-if="treeState.selectNode.paramType === 'list'" :columns="listState.columns" :data-source="listState.data" :pagination="tablePagination" :bordered="true"
class="table" :scroll="{ x: true, y: '500px' }">
row-key="name"
:size="listState.size"
:columns="listState.columns"
:data-source="listState.data"
:pagination="tablePagination"
:bordered="true"
:scroll="{ x: true, y: '500px' }"
>
<template #bodyCell="{ column, text, record }"> <template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'value'"> <template v-if="column.dataIndex === 'value'">
<a-tooltip placement="topLeft"> <a-tooltip placement="topLeft">
@@ -500,69 +518,30 @@ onMounted(() => {
{{ record.comment }} {{ record.comment }}
</template> </template>
<div class="editable-cell"> <div class="editable-cell">
<div <div v-if="
v-if="
listState.editRecord['display'] === record['display'] listState.editRecord['display'] === record['display']
" " class="editable-cell__input-wrapper">
class="editable-cell__input-wrapper" <a-input-number v-if="record['type'] === 'int'" v-model:value="listState.editRecord['value']"
> :disabled="listState.confirmLoading" style="width: 100%"></a-input-number>
<a-input-number <a-switch v-else-if="record['type'] === 'bool'" v-model:checked="listState.editRecord['value']"
v-if="record['type'] === 'int'" :checked-children="t('common.switch.open')" :un-checked-children="t('common.switch.shut')"
v-model:value="listState.editRecord['value']" :disabled="listState.confirmLoading"></a-switch>
:disabled="listState.confirmLoading" <a-select v-else-if="record['type'] === 'enum'" v-model:value="listState.editRecord['value']"
style="width: 100%" :allow-clear="true" :disabled="listState.confirmLoading" style="width: 100%">
></a-input-number> <a-select-option :value="+v" :key="+v" v-for="(k, v) in JSON.parse(record['filter'])">
<a-switch
v-else-if="record['type'] === 'bool'"
v-model:checked="listState.editRecord['value']"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
:disabled="listState.confirmLoading"
></a-switch>
<a-select
v-else-if="record['type'] === 'enum'"
v-model:value="listState.editRecord['value']"
:allow-clear="true"
:disabled="listState.confirmLoading"
style="width: 100%"
>
<a-select-option
:value="+v"
:key="+v"
v-for="(k, v) in JSON.parse(record['filter'])"
>
{{ k }} {{ k }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input <a-input v-else v-model:value="listState.editRecord['value']"
v-else :disabled="listState.confirmLoading"></a-input>
v-model:value="listState.editRecord['value']" <a-space :size="8" align="center" direction="horizontal" style="margin-left: 18px">
:disabled="listState.confirmLoading"
></a-input>
<a-space
:size="8"
align="center"
direction="horizontal"
style="margin-left: 18px"
>
<a-tooltip placement="bottomRight"> <a-tooltip placement="bottomRight">
<template #title> {{ t('common.ok') }} </template> <template #title> {{ t('common.ok') }} </template>
<a-popconfirm <a-popconfirm :title="t('views.ne.neConfig.editOkTip', {
:title=" num: record['display'],
t( })
'views.configManage.configParamForm.editOkTip', " placement="topRight" :disabled="listState.confirmLoading" @confirm="listEditOk()">
{ num: record['display'] } <a-button type="text" class="editable-cell__icon-edit" :disabled="listState.confirmLoading">
)
"
placement="topRight"
:disabled="listState.confirmLoading"
@confirm="listEditOk()"
>
<a-button
type="text"
class="editable-cell__icon-edit"
:disabled="listState.confirmLoading"
>
<template #icon> <template #icon>
<CheckOutlined /> <CheckOutlined />
</template> </template>
@@ -571,12 +550,8 @@ onMounted(() => {
</a-tooltip> </a-tooltip>
<a-tooltip placement="bottomRight"> <a-tooltip placement="bottomRight">
<template #title> {{ t('common.cancel') }} </template> <template #title> {{ t('common.cancel') }} </template>
<a-button <a-button type="text" class="editable-cell__icon-edit" :disabled="listState.confirmLoading"
type="text" @click.prevent="listEditClose()">
class="editable-cell__icon-edit"
:disabled="listState.confirmLoading"
@click.prevent="listEditClose()"
>
<template #icon> <template #icon>
<CloseOutlined /> <CloseOutlined />
</template> </template>
@@ -584,24 +559,16 @@ onMounted(() => {
</a-tooltip> </a-tooltip>
</a-space> </a-space>
</div> </div>
<div <div v-else class="editable-cell__text-wrapper" @dblclick="listEdit(record)">
v-else
class="editable-cell__text-wrapper"
@dblclick="listEdit(record)"
>
<template v-if="record['type'] === 'enum'"> <template v-if="record['type'] === 'enum'">
{{ JSON.parse(record['filter'])[text] || '&nbsp;' }} {{ JSON.parse(record['filter'])[text] || '&nbsp;' }}
</template> </template>
<template v-else>{{ `${text}` || '&nbsp;' }}</template> <template v-else>{{ `${text}` || '&nbsp;' }}</template>
<EditOutlined <EditOutlined class="editable-cell__icon" @click="listEdit(record)" style="margin-left: 18px"
class="editable-cell__icon"
@click="listEdit(record)"
style="margin-left: 18px"
v-if=" v-if="
!listState.confirmLoading && !listState.confirmLoading &&
!['read-only', 'read', 'ro'].includes(record.access) !['read-only', 'read', 'ro'].includes(record.access)
" " />
/>
</div> </div>
</div> </div>
</a-tooltip> </a-tooltip>
@@ -610,66 +577,78 @@ onMounted(() => {
</a-table> </a-table>
<!-- array类型 --> <!-- array类型 -->
<template v-if="treeState.selectNode.paramType === 'array'"> <template v-else-if="treeState.selectNode.paramType === 'array'">
<a-table <a-table class="table" row-key="index"
class="table"
row-key="index"
:columns="treeState.selectNode.paramPerms.includes('get') ? arrayState.columnsDnd.filter((s: any) => s.key !== 'index') : arrayState.columnsDnd" :columns="treeState.selectNode.paramPerms.includes('get') ? arrayState.columnsDnd.filter((s: any) => s.key !== 'index') : arrayState.columnsDnd"
:data-source="arrayState.columnsData" :data-source="arrayState.columnsData" :size="arrayState.size" :pagination="tablePagination"
:size="arrayState.size" :bordered="true" :scroll="{ x: arrayState.columnsDnd.length * 200, y: 480 }"
:pagination="tablePagination" @resizeColumn="(w: number, col: any) => (col.width = w)" :show-expand-column="false"
:bordered="true" v-model:expanded-row-keys="arrayState.arrayChildExpandKeys">
:scroll="{ x: arrayState.columnsDnd.length * 200, y: 480 }"
@resizeColumn="(w:number, col:any) => (col.width = w)"
:show-expand-column="false"
v-model:expanded-row-keys="arrayState.arrayChildExpandKeys"
>
<!-- 多列新增操作 --> <!-- 多列新增操作 -->
<template #title> <template #title>
<a-space :size="16" align="center"> <a-space :size="16" align="center">
<a-button <a-button type="primary" @click.prevent="arrayAdd()" size="small"
type="primary" v-if="treeState.selectNode.paramPerms.includes('post')">
@click.prevent="arrayAdd()" <template #icon>
size="small" <PlusOutlined />
v-if="treeState.selectNode.paramPerms.includes('post')" </template>
>
<template #icon> <PlusOutlined /> </template>
{{ t('common.addText') }} {{ t('common.addText') }}
</a-button> </a-button>
<TableColumnsDnd <TableColumnsDnd type="ghost" :cache-id="treeState.selectNode.key"
type="ghost"
:cache-id="treeState.selectNode.key"
:columns="treeState.selectNode.paramPerms.includes('get') ? [...arrayState.columns.filter((s: any) => s.key !== 'index')] : arrayState.columns" :columns="treeState.selectNode.paramPerms.includes('get') ? [...arrayState.columns.filter((s: any) => s.key !== 'index')] : arrayState.columns"
v-model:columns-dnd="arrayState.columnsDnd" v-model:columns-dnd="arrayState.columnsDnd"></TableColumnsDnd>
></TableColumnsDnd>
</a-space> </a-space>
</template> </template>
<template #customFilterDropdown="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }">
<div style="padding: 8px">
<a-input ref="searchInput" :placeholder="`Search ${column.dataIndex}`" :value="selectedKeys[0]"
style="width: 188px; margin-bottom: 8px; display: block"
@change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
@pressEnter="handleSearch(selectedKeys, confirm, column.dataIndex)" />
<a-button type="primary" size="small" style="width: 90px; margin-right: 8px"
@click="handleSearch(selectedKeys, confirm, column.dataIndex)">
<template #icon>
<SearchOutlined />
</template>
Search
</a-button>
<a-button size="small" style="width: 90px" @click="handleReset(clearFilters)">
Reset
</a-button>
</div>
</template>
<template #customFilterIcon="{ filtered }">
<search-outlined :style="{ color: filtered ? '#108ee9' : undefined }" />
</template>
<!-- 多列数据渲染 --> <!-- 多列数据渲染 -->
<template #bodyCell="{ column, text, record }"> <template #bodyCell="{ column, text, record }">
<template v-if="column?.key === 'index'"> <template v-if="column?.key === 'index'">
<a-space :size="16" align="center"> <a-space :size="16" align="center">
<a-tooltip <a-tooltip v-if="treeState.selectNode.paramPerms.includes('put')">
v-if="treeState.selectNode.paramPerms.includes('put')"
>
<template #title>{{ t('common.editText') }}</template> <template #title>{{ t('common.editText') }}</template>
<a-button type="link" @click.prevent="arrayEdit(text)"> <a-button type="link" @click.prevent="arrayEdit(text)">
<template #icon><FormOutlined /></template> <template #icon>
<FormOutlined />
</template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip <a-tooltip v-if="
v-if="
treeState.selectNode.paramPerms.includes('delete') && treeState.selectNode.paramPerms.includes('delete') &&
!( !(
neTypeSelect[0] === 'IMS' && neTypeSelect[0] === 'IMS' &&
treeState.selectNode.paramName === 'plmn' && treeState.selectNode.paramName === 'plmn' &&
text['value'] === 0 text['value'] === 0
) )
" ">
>
<template #title>{{ t('common.deleteText') }}</template> <template #title>{{ t('common.deleteText') }}</template>
<a-button type="link" @click.prevent="arrayDelete(text)"> <a-button type="link" @click.prevent="arrayDelete(text)">
<template #icon><DeleteOutlined /></template> <template #icon>
<DeleteOutlined />
</template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
</a-space> </a-space>
@@ -681,24 +660,18 @@ onMounted(() => {
</template> </template>
<div class="editable-cell"> <div class="editable-cell">
<template v-if="text.array"> <template v-if="text.array">
<a-button <a-button type="default" size="small" @click.prevent="
type="default"
size="small"
@click.prevent="
arrayChildExpand(record['index'], text) arrayChildExpand(record['index'], text)
" ">
> <template #icon>
<template #icon><BarsOutlined /></template> <BarsOutlined />
{{ </template>
t('views.configManage.configParamForm.arrayMore') {{ t('views.ne.neConfig.arrayMore') }}
}}
</a-button> </a-button>
<!--特殊字段拓展显示--> <!--特殊字段拓展显示-->
<span <span v-if="
v-if="
text.name === 'dnnList' && Array.isArray(text.value) text.name === 'dnnList' && Array.isArray(text.value)
" ">
>
({{ ({{
text.value.length > 4 text.value.length > 4
? `${text.value ? `${text.value
@@ -725,37 +698,23 @@ onMounted(() => {
<!-- 多列嵌套类型 --> <!-- 多列嵌套类型 -->
<template #expandedRowRender> <template #expandedRowRender>
<a-table <a-table class="table" row-key="index" :columns="arrayChildState.columnsDnd"
class="table" :data-source="arrayChildState.columnsData" :size="arrayChildState.size" :pagination="tablePagination"
row-key="index" :bordered="true" :scroll="{
:columns="arrayChildState.columnsDnd"
:data-source="arrayChildState.columnsData"
:size="arrayChildState.size"
:pagination="tablePagination"
:bordered="true"
:scroll="{
x: arrayChildState.columnsDnd.length * 200, x: arrayChildState.columnsDnd.length * 200,
y: 200, y: 200,
}" }" @resizeColumn="(w: number, col: any) => (col.width = w)">
@resizeColumn="(w:number, col:any) => (col.width = w)"
>
<template #title> <template #title>
<a-space :size="16" align="center"> <a-space :size="16" align="center">
<a-button <a-button type="primary" @click.prevent="arrayChildAdd" size="small">
type="primary" <template #icon>
@click.prevent="arrayChildAdd" <PlusOutlined />
size="small" </template>
>
<template #icon> <PlusOutlined /> </template>
{{ t('common.addText') }} {{ arrayChildState.title }} {{ t('common.addText') }} {{ arrayChildState.title }}
</a-button> </a-button>
<TableColumnsDnd <TableColumnsDnd type="ghost" :cache-id="`${treeState.selectNode.key}:${arrayChildState.loc}`"
type="ghost" :columns="[...arrayChildState.columns]" v-model:columns-dnd="arrayChildState.columnsDnd"
:cache-id="`${treeState.selectNode.key}:${arrayChildState.loc}`" v-if="arrayChildState.loc"></TableColumnsDnd>
:columns="[...arrayChildState.columns]"
v-model:columns-dnd="arrayChildState.columnsDnd"
v-if="arrayChildState.loc"
></TableColumnsDnd>
</a-space> </a-space>
</template> </template>
<template #bodyCell="{ column, text, record }"> <template #bodyCell="{ column, text, record }">
@@ -763,22 +722,20 @@ onMounted(() => {
<a-space :size="8" align="center"> <a-space :size="8" align="center">
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.editText') }}</template> <template #title>{{ t('common.editText') }}</template>
<a-button <a-button type="link" @click.prevent="arrayChildEdit(text)">
type="link" <template #icon>
@click.prevent="arrayChildEdit(text)" <FormOutlined />
> </template>
<template #icon><FormOutlined /></template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
<template #title> <template #title>
{{ t('common.deleteText') }} {{ t('common.deleteText') }}
</template> </template>
<a-button <a-button type="link" @click.prevent="arrayChildDelete(text)">
type="link" <template #icon>
@click.prevent="arrayChildDelete(text)" <DeleteOutlined />
> </template>
<template #icon><DeleteOutlined /></template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
</a-space> </a-space>
@@ -791,12 +748,10 @@ onMounted(() => {
<div class="editable-cell"> <div class="editable-cell">
<template v-if="text.array"> <template v-if="text.array">
<a-button type="default" size="small"> <a-button type="default" size="small">
<template #icon><BarsOutlined /></template> <template #icon>
{{ <BarsOutlined />
t( </template>
'views.configManage.configParamForm.arrayMore' {{ t('views.ne.neConfig.arrayMore') }}
)
}}
</a-button> </a-button>
</template> </template>
@@ -816,102 +771,55 @@ onMounted(() => {
</template> </template>
</a-table> </a-table>
</template> </template>
<template v-else>
<a-alert type="warning" show-icon message="No Data" />
</template>
</a-card> </a-card>
</a-col> </a-col>
</a-row> </a-row>
<!-- 新增框或修改框 --> <!-- 新增框或修改框 -->
<ProModal <ProModal :drag="true" :width="800" :destroyOnClose="true"
:drag="true" :body-style="{ maxHeight: '600px', 'overflow-y': 'auto' }" :keyboard="false" :mask-closable="false"
:width="800" :open="modalState.open" :title="modalState.title" :confirm-loading="modalState.confirmLoading" @ok="fnModalOk"
:destroyOnClose="true" @cancel="fnModalCancel">
:body-style="{ maxHeight: '600px', 'overflow-y': 'auto' }" <a-form class="form" layout="horizontal" autocomplete="off" :validate-on-rule-change="false" :validateTrigger="[]"
:keyboard="false" :label-col="{ span: 6 }" :labelWrap="true">
:mask-closable="false" <a-form-item v-for="item in modalState.data" :label="item.display" :name="item.name"
:open="modalState.open" :required="item.optional === 'false'" style="margin-bottom: 4px">
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
class="form"
layout="horizontal"
autocomplete="off"
:validate-on-rule-change="false"
:validateTrigger="[]"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-form-item
v-for="item in modalState.data"
:label="item.display"
:name="item.name"
:required="item.optional === 'false'"
style="margin-bottom: 4px"
>
<a-tooltip placement="topLeft"> <a-tooltip placement="topLeft">
<template #title v-if="item.comment"> <template #title v-if="item.comment">
{{ item.comment }} {{ item.comment }}
</template> </template>
<div> <div>
<div <div v-if="
v-if="
!Array.isArray(item.array) && !Array.isArray(item.array) &&
modalState.from[item.name] !== undefined modalState.from[item.name] !== undefined
" ">
>
<!-- 特殊SMF-upfid选择 --> <!-- 特殊SMF-upfid选择 -->
<a-select <a-select v-if="
v-if="
neTypeSelect[0] === 'SMF' && neTypeSelect[0] === 'SMF' &&
modalState.from[item.name]['name'] === 'upfId' modalState.from[item.name]['name'] === 'upfId'
" " v-model:value="modalState.from[item.name]['value']" :options="smfByUPFIdOptions"
v-model:value="modalState.from[item.name]['value']" :disabled="['read-only', 'read', 'ro'].includes(item.access)" :token-separators="[',', ';']"
:options="smfByUPFIdOptions" mode="multiple" :max-tag-count="5" :allow-clear="true" style="width: 100%">
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
:token-separators="[',', ';']"
mode="multiple"
:max-tag-count="5"
:allow-clear="true"
style="width: 100%"
>
</a-select> </a-select>
<!-- 常规 --> <!-- 常规 -->
<a-input-number <a-input-number v-else-if="item['type'] === 'int'" v-model:value="modalState.from[item.name]['value']"
v-else-if="item['type'] === 'int'" :disabled="['read-only', 'read', 'ro'].includes(item.access)" style="width: 100%"></a-input-number>
v-model:value="modalState.from[item.name]['value']" <a-switch v-else-if="item['type'] === 'bool'" v-model:checked="modalState.from[item.name]['value']"
:disabled="['read-only', 'read', 'ro'].includes(item.access)" :checked-children="t('common.switch.open')" :un-checked-children="t('common.switch.shut')"
style="width: 100%" :disabled="['read-only', 'read', 'ro'].includes(item.access)"></a-switch>
></a-input-number> <a-select v-else-if="item['type'] === 'enum'" v-model:value="modalState.from[item.name]['value']"
<a-switch :disabled="['read-only', 'read', 'ro'].includes(item.access)" :allow-clear="true" style="width: 100%">
v-else-if="item['type'] === 'bool'" <a-select-option :value="+v" :key="+v" v-for="(k, v) in JSON.parse(item['filter'])">
v-model:checked="modalState.from[item.name]['value']"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
></a-switch>
<a-select
v-else-if="item['type'] === 'enum'"
v-model:value="modalState.from[item.name]['value']"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
:allow-clear="true"
style="width: 100%"
>
<a-select-option
:value="+v"
:key="+v"
v-for="(k, v) in JSON.parse(item['filter'])"
>
{{ k }} {{ k }}
</a-select-option> </a-select-option>
</a-select> </a-select>
<a-input <a-input v-else v-model:value="modalState.from[item.name]['value']"
v-else :disabled="['read-only', 'read', 'ro'].includes(item.access)"></a-input>
v-model:value="modalState.from[item.name]['value']"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
></a-input>
</div> </div>
<div v-else> <div v-else>
{{ `${item.value || '&nbsp;'}` }} {{ `${item.value || '&nbsp;'}` }}
@@ -930,19 +838,24 @@ onMounted(() => {
display: none; display: none;
cursor: pointer; cursor: pointer;
} }
&__icon:hover { &__icon:hover {
color: var(--ant-primary-color); color: var(--ant-primary-color);
} }
&__icon-edit:hover { &__icon-edit:hover {
color: var(--ant-primary-color); color: var(--ant-primary-color);
} }
&__text-wrapper { &__text-wrapper {
font-size: 16px; font-size: 16px;
color: inherit; color: inherit;
} }
&__text-wrapper:hover &__icon { &__text-wrapper:hover &__icon {
display: inline-block; display: inline-block;
} }
&__input-wrapper { &__input-wrapper {
display: flex; display: flex;
justify-content: start; justify-content: start;
@@ -0,0 +1,513 @@
import {
addNeConfigData,
delNeConfigData,
editNeConfigData,
} from '@/api/ne/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, neTypeSelect, neIdSelect, fnActiveConfigNode, ruleVerification, modalState, fnModalCancel}
* @returns
*/
export default function useConfigArray({
t,
treeState,
neTypeSelect,
neIdSelect,
fnActiveConfigNode,
ruleVerification,
modalState,
fnModalCancel,
}: any) {
/**多列列表状态类型 */
type ArrayStateType = {
/**紧凑型 */
size: SizeType;
/**多列嵌套记录字段 */
columns: Record<string, any>[];
/**表格字段列排序 */
columnsDnd: Record<string, any>[];
/**多列记录数据 */
columnsData: Record<string, any>[];
/**多列嵌套展开key */
arrayChildExpandKeys: any[];
/**多列记录数据 */
data: Record<string, any>[];
/**多列记录规则 */
dataRule: Record<string, any>;
};
/**多列列表状态 */
let arrayState: ArrayStateType = reactive({
size: 'small',
columns: [],
columnsDnd: [],
columnsData: [],
arrayChildExpandKeys: [],
data: [],
dataRule: {},
});
/**多列表编辑 */
function arrayEdit(rowIndex: Record<string, any>) {
const item = arrayState.data.find((s: any) => s.key === rowIndex.value);
if (!item) return;
const from = arrayInitEdit(item, arrayState.dataRule);
// 处理信息
const row: Record<string, any> = {};
for (const v of from.record) {
if (Array.isArray(v.array)) {
continue;
}
row[v.name] = Object.assign({}, v);
}
// 特殊SMF-upfid选择
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(row, 'upfId')) {
const v = row.upfId.value;
if (typeof v === 'string') {
if (v === '') {
row.upfId.value = [];
} else if (v.includes(';')) {
row.upfId.value = v.split(';');
} else if (v.includes(',')) {
row.upfId.value = v.split(',');
} else {
row.upfId.value = [v];
}
}
}
modalState.from = row;
modalState.type = 'arrayEdit';
modalState.title = `${treeState.selectNode.paramDisplay} ${from.title}`;
modalState.key = from.key;
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
modalState.open = true;
// 关闭嵌套
arrayState.arrayChildExpandKeys = [];
}
/**多列表编辑关闭 */
function arrayEditClose() {
arrayState.arrayChildExpandKeys = [];
fnModalCancel();
}
/**多列表编辑确认 */
function arrayEditOk(from: Record<string, any>) {
const loc = `${from['index']['value']}`;
// 特殊SMF-upfid选择
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(from, 'upfId')) {
const v = from.upfId.value;
if (Array.isArray(v)) {
from.upfId.value = v.join(';');
}
}
// 遍历提取属性和值
let data: Record<string, any> = {};
for (const key in from) {
// 子嵌套的不插入
if (from[key]['array']) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(from[key]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
data[key] = from[key]['value'];
}
// 请求
const reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
paramData: data,
loc: loc,
})
);
}
} else {
reqArr.push(
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc: loc,
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
arrayEditClose();
return;
}
const hide = message.loading(t('common.loading'), 0);
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.warning({
content: t('views.ne.neConfig.updateItemErr'),
duration: 3,
});
} else {
message.success({
content: t('views.ne.neConfig.updateItem', {
num: modalState.title,
}),
duration: 3,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
fnActiveConfigNode('#');
}
})
.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() {
// 请求
const reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
delNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
loc: loc,
})
);
}
} else {
reqArr.push(
delNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
loc: loc,
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
arrayEditClose();
return;
}
const hide = message.loading(t('common.loading'), 0);
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.error({
content: `${rejected.reason}`,
duration: 2,
});
} else {
message.success({
content: t('views.ne.neConfig.delItemOk', {
num: title,
}),
duration: 2,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
fnActiveConfigNode('#');
}
})
.finally(() => {
hide();
arrayEditClose();
});
},
});
}
/**多列表新增单行 */
function arrayAdd() {
const from = arrayInitAdd(arrayState.data, arrayState.dataRule);
// 处理信息
const row: Record<string, any> = {};
for (const v of from.record) {
if (Array.isArray(v.array)) {
continue;
}
row[v.name] = Object.assign({}, v);
}
// 特殊SMF-upfid选择
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(row, 'upfId')) {
const v = row.upfId.value;
if (typeof v === 'string') {
if (v === '') {
row.upfId.value = [];
} else if (v.includes(';')) {
row.upfId.value = v.split(';');
} else if (v.includes(',')) {
row.upfId.value = v.split(',');
} else {
row.upfId.value = [v];
}
}
}
modalState.from = row;
modalState.type = 'arrayAdd';
modalState.title = `${treeState.selectNode.paramDisplay} ${from.title}`;
modalState.key = from.key;
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
modalState.open = true;
}
/**多列表新增单行确认 */
function arrayAddOk(from: Record<string, any>) {
// 特殊SMF-upfid选择
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(from, 'upfId')) {
const v = from.upfId.value;
if (Array.isArray(v)) {
from.upfId.value = v.join(';');
}
}
// 遍历提取属性和值
let data: Record<string, any> = {};
for (const key in from) {
// 子嵌套的不插入
if (from[key]['array']) {
continue;
}
// 检查规则
const [ok, msg] = ruleVerification(from[key]);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
data[key] = from[key]['value'];
}
// 请求
const reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
addNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
paramData: data,
loc: `${from['index']['value']}`,
})
);
}
} else {
reqArr.push(
addNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc: `${from['index']['value']}`,
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
arrayEditClose();
return;
}
const hide = message.loading(t('common.loading'), 0);
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.warning({
content: t('views.ne.neConfig.addItemErr'),
duration: 3,
});
} else {
message.success({
content: t('views.ne.neConfig.addItemOk', {
num: modalState.title,
}),
duration: 3,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
fnActiveConfigNode('#');
}
})
.finally(() => {
hide();
arrayEditClose();
});
}
/**多列表编辑行数据初始化 */
function arrayInitEdit(data: Record<string, any>, dataRule: any) {
const dataFrom = data.record;
const ruleFrom = Object.assign({}, JSON.parse(JSON.stringify(dataRule)));
for (const row of ruleFrom.record) {
// 子嵌套的不初始
if (row.array) {
row.value = [];
continue;
}
// 查找项的值
const item = dataFrom.find((s: any) => s.name === row.name);
if (!item) {
continue;
}
// 可选的
row.optional = 'true';
// 根据规则类型转值
if (['enum', 'int'].includes(row.type)) {
row.value = Number(item.value);
} else if ('bool' === row.type) {
row.value = Boolean(item.value);
} else {
row.value = item.value;
}
}
ruleFrom.key = data.key;
ruleFrom.title = data.title;
return ruleFrom;
}
/**多列表新增行数据初始化 */
function arrayInitAdd(data: any[], dataRule: any) {
// 有数据时取得最后的index
let dataLastIndex = 0;
if (data.length !== 0) {
const lastFrom = Object.assign(
{},
JSON.parse(JSON.stringify(data.at(-1)))
);
if (lastFrom.record.length > 0) {
dataLastIndex = parseInt(lastFrom.key);
dataLastIndex += 1;
}
}
const ruleFrom = Object.assign({}, JSON.parse(JSON.stringify(dataRule)));
for (const row of ruleFrom.record) {
// 子嵌套的不初始
if (row.array) {
row.value = [];
continue;
}
// 可选的
row.optional = 'true';
// index值
if (row.name === 'index') {
let newIndex =
dataLastIndex !== 0 ? dataLastIndex : parseInt(row.value);
if (isNaN(newIndex)) {
newIndex = 0;
}
row.value = newIndex;
ruleFrom.key = newIndex;
ruleFrom.title = `Index-${newIndex}`;
continue;
}
// 根据规则类型转值
if (['enum', 'int'].includes(row.type)) {
row.value = Number(row.value);
}
if ('bool' === row.type) {
row.value = Boolean(row.value);
}
// 特殊SMF-upfid选择
if (neTypeSelect.value[0] === 'SMF' && row.name === 'upfId') {
const v = row.value;
if (typeof v === 'string') {
if (v === '') {
row.value = [];
} else if (v.includes(';')) {
row.value = v.split(';');
} else if (v.includes(',')) {
row.value = v.split(',');
} else {
row.value = [v];
}
}
}
}
return ruleFrom;
}
// 监听表格字段列排序变化关闭展开
watch(
() => arrayState.columnsDnd,
() => {
arrayEditClose();
}
);
return {
arrayState,
arrayEdit,
arrayEditClose,
arrayEditOk,
arrayDelete,
arrayAdd,
arrayAddOk,
arrayInitEdit,
arrayInitAdd,
};
}
@@ -0,0 +1,454 @@
import {
addNeConfigData,
editNeConfigData,
delNeConfigData,
} from '@/api/ne/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, neTypeSelect, neIdSelect, fnActiveConfigNode, ruleVerification, modalState, arrayState, arrayInitEdit, arrayInitAdd, arrayEditClose}
* @returns
*/
export default function useConfigArrayChild({
t,
treeState,
neTypeSelect,
neIdSelect,
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 reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
);
}
} else {
reqArr.push(
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
arrayEditClose();
return;
}
const hide = message.loading(t('common.loading'), 0);
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.warning({
content: t('views.ne.neConfig.updateItemErr'),
duration: 3,
});
} else {
message.success({
content: t('views.ne.neConfig.updateItem', {
num: modalState.title,
}),
duration: 3,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
fnActiveConfigNode('#');
}
})
.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() {
// 请求
const reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
delNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
loc,
})
);
}
} else {
reqArr.push(
delNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
loc,
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
arrayEditClose();
return;
}
const hide = message.loading(t('common.loading'), 0);
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.error({
content: `${rejected.reason}`,
duration: 2,
});
} else {
message.success({
content: t('views.ne.neConfig.delItemOk', {
num: title,
}),
duration: 2,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
fnActiveConfigNode('#');
}
})
.finally(() => {
hide();
arrayEditClose();
});
},
});
}
/**多列表嵌套行新增单行 */
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 reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
addNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
);
}
} else {
reqArr.push(
addNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
arrayEditClose();
return;
}
const hide = message.loading(t('common.loading'), 0);
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.warning({
content: t('views.ne.neConfig.addItemErr'),
duration: 3,
});
} else {
message.success({
content: t('views.ne.neConfig.addItemOk', {
num: modalState.title,
}),
duration: 3,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
fnActiveConfigNode('#');
}
})
.finally(() => {
hide();
arrayEditClose();
});
}
return {
arrayChildState,
arrayChildExpand,
arrayChildEdit,
arrayChildEditOk,
arrayChildDelete,
arrayChildAdd,
arrayChildAddOk,
};
}
@@ -0,0 +1,187 @@
import { editNeConfigData } from '@/api/ne/neConfig';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { message } from 'ant-design-vue/es';
import { reactive, toRaw } from 'vue';
/**
* list类型参数处理
* @param param 父级传入 {t, treeState, neTypeSelect, neIdSelect, ruleVerification}
* @returns
*/
export default function useConfigList({
t,
treeState,
neTypeSelect,
neIdSelect,
ruleVerification,
}: any) {
/**单列表状态类型 */
type ListStateType = {
/**紧凑型 */
size: SizeType;
/**单列记录字段 */
columns: Record<string, any>[];
/**单列记录数据 */
data: Record<string, any>[];
/**编辑行记录 */
editRecord: Record<string, any>;
/**确认提交等待 */
confirmLoading: boolean;
};
/**单列表状态 */
let listState: ListStateType = reactive({
size: 'small',
columns: [
{
title: 'Key',
dataIndex: 'display',
align: 'left',
width: '30%',
},
{
title: 'Value',
dataIndex: 'value',
align: 'left',
width: '70%',
},
],
data: [],
confirmLoading: false,
editRecord: {},
});
/**单列表编辑 */
function listEdit(row: Record<string, any>) {
if (
listState.confirmLoading ||
['read-only', 'read', 'ro'].includes(row.access)
) {
return;
}
listState.editRecord = Object.assign({}, row);
}
/**单列表编辑关闭 */
function listEditClose() {
listState.confirmLoading = false;
listState.editRecord = {};
}
/**单列表编辑确认 */
function listEditOk() {
if (listState.confirmLoading) return;
const from = toRaw(listState.editRecord);
// 检查规则
const [ok, msg] = ruleVerification(from);
if (!ok) {
message.warning({
content: `${msg}`,
duration: 3,
});
return;
}
// 请求
const reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
paramData: {
[from['name']]: from['value'],
},
})
);
}
} else {
reqArr.push(
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: {
[from['name']]: from['value'],
},
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
listState.confirmLoading = false;
listState.editRecord = {};
return;
}
listState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.warning({
content: t('views.ne.neConfig.updateValueErr'),
duration: 3,
});
} else {
message.success({
content: t('views.ne.neConfig.updateValue', {
num: from['display'],
}),
duration: 3,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
// 改变表格数据
const item = listState.data.find(
(item: Record<string, any>) => from['name'] === item['name']
);
if (item) {
Object.assign(item, listState.editRecord);
}
}
})
.finally(() => {
hide();
listState.confirmLoading = false;
listState.editRecord = {};
});
}
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 10,
/**默认的每页条数 */
defaultPageSize: 10,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: true,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
},
});
return { tablePagination, listState, listEdit, listEditClose, listEditOk };
}
+192
View File
@@ -0,0 +1,192 @@
import { getNeConfigData } from '@/api/ne/neConfig';
import { regExpIPv4, regExpIPv6, validURL } from '@/utils/regular-utils';
import { ref } from 'vue';
/**
* 参数公共函数
* @param param 父级传入 {t}
* @returns
*/
export default function useOptions({ t }: any) {
/**规则校验 */
function ruleVerification(row: Record<string, any>): (string | boolean)[] {
let result = [true, ''];
const type = row.type;
const value = row.value;
const filter = row.filter;
const display = row.display;
// 子嵌套的不检查
if (row.array) {
return result;
}
// 可选的同时没有值不检查
if (row.optional === 'true' && !value) {
return result;
}
switch (type) {
case 'int':
// filter: "0~128"
if (filter && filter.indexOf('~') !== -1) {
const filterArr = filter.split('~');
const minInt = parseInt(filterArr[0]);
const maxInt = parseInt(filterArr[1]);
const valueInt = parseInt(value);
if (valueInt < minInt || valueInt > maxInt) {
return [
false,
t('views.ne.neConfig.requireInt', {
display,
filter,
}),
];
}
}
break;
case 'ipv4':
if (!regExpIPv4.test(value)) {
return [
false,
t('views.ne.neConfig.requireIpv4', { display }),
];
}
break;
case 'ipv6':
if (!regExpIPv6.test(value)) {
return [
false,
t('views.ne.neConfig.requireIpv6', { display }),
];
}
break;
case 'enum':
if (filter && filter.indexOf('{') === 1) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
if (!Object.keys(filterJson).includes(`${value}`)) {
return [
false,
t('views.ne.neConfig.requireEnum', { display }),
];
}
}
break;
case 'bool':
// filter: '{"0":"false", "1":"true"}'
if (filter && filter.indexOf('{') === 1) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
if (!Object.values(filterJson).includes(`${value}`)) {
return [
false,
t('views.ne.neConfig.requireBool', { display }),
];
}
}
break;
case 'string':
// filter: "0~128"
// 字符串长度判断
if (filter && filter.indexOf('~') !== -1) {
try {
const filterArr = filter.split('~');
let rule = new RegExp(
'^\\S{' + filterArr[0] + ',' + filterArr[1] + '}$'
);
if (!rule.test(value)) {
return [
false,
t('views.ne.neConfig.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
// 字符串http判断
if (value.startsWith('http')) {
try {
if (!validURL(value)) {
return [
false,
t('views.ne.neConfig.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
break;
case 'regex':
// filter: "^[0-9]{3}$"
if (filter) {
try {
let regex = new RegExp(filter);
if (!regex.test(value)) {
return [
false,
t('views.ne.neConfig.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
break;
default:
return [
false,
t('views.ne.neConfig.requireUn', { display }),
];
}
return result;
}
/**upfId可选择 */
const smfByUPFIdOptions = ref<{ value: string; label: string }[]>([]);
/**加载smf配置的upfId */
function smfByUPFIdLoadData(neId: string) {
getNeConfigData({
neType: 'SMF',
neId: neId,
paramName: 'upfConfig',
}).then(res => {
smfByUPFIdOptions.value = [];
for (const s of res.data) {
smfByUPFIdOptions.value.push({
value: s.id,
label: s.id,
});
}
});
}
return {
ruleVerification,
smfByUPFIdLoadData,
smfByUPFIdOptions,
};
}
File diff suppressed because it is too large. Load diff
+5 -5
View File
@@ -104,16 +104,16 @@ let modalState: ModalStateType = reactive({
from: { from: {
id: undefined, id: undefined,
neId: '001', neId: '001',
neType: 'AMF', neType: 'MF',
neName: '', neName: '',
ip: '', ip: '',
port: 33030, port: 33030,
pvFlag: 'PNF', pvFlag: 'PNF',
rmUid: '4400HXAMF001', rmUid: '4400PSAPMF0001',
neAddress: '', neAddress: '',
dn: '', dn: '',
vendorName: '', vendorName: '',
province: '', province: 'Area',
remark: '', remark: '',
// 主机 // 主机
hosts: [ hosts: [
@@ -321,14 +321,14 @@ function fnNeTypeChange(v: any) {
}); });
} }
modalState.from.rmUid = `4400HX${v}${modalState.from.neId}`; // 4400HX1AMF001 modalState.from.rmUid = `4400PSAP${v}${modalState.from.neId}`; // 4400HX1MF001
} }
/**表单修改网元neId */ /**表单修改网元neId */
function fnNeIdChange(e: any) { function fnNeIdChange(e: any) {
const v = e.target.value; const v = e.target.value;
if (v.length < 1) return; if (v.length < 1) return;
modalState.from.rmUid = `4400HX${modalState.from.neType}${v}`; // 4400HX1AMF001 modalState.from.rmUid = `4400PSAP${modalState.from.neType}${v}`; // 4400HX1MF001
} }
/**表单修改网元IP */ /**表单修改网元IP */
+12 -7
View File
@@ -78,14 +78,19 @@ function fnModalVisibleByTypeAndId(neType: string, neId: string) {
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
const data = res.data; const data = res.data;
const ipType = data?.oamConfig?.ipType || 'ipv4';
let omcIP = '127.0.0.1';
if (data.oamConfig && Reflect.has(data.oamConfig, ipType)) {
omcIP = data?.oamConfig[ipType];
}
Object.assign(modalState.from, { Object.assign(modalState.from, {
omcIP: data.oamConfig[data.oamConfig.ipType], omcIP: omcIP,
oamEnable: data.oamConfig.enable, oamEnable: data?.oamConfig?.enable || false,
oamPort: data.oamConfig.port, oamPort: data?.oamConfig?.port || 33030,
snmpEnable: data.snmpConfig.enable, snmpEnable: data?.snmpConfig?.enable || false,
snmpPort: data.snmpConfig.port, snmpPort: data?.snmpConfig?.port || 4957,
kpiEnable: data.kpiConfig.enable, kpiEnable: data?.kpiConfig?.enable || false,
kpiTimer: data.kpiConfig.timer, kpiTimer: data?.kpiConfig?.timer || 60,
}); });
modalState.title = t('views.ne.neInfo.oam.title'); modalState.title = t('views.ne.neInfo.oam.title');
modalState.openByEdit = true; modalState.openByEdit = true;
+14 -17
View File
@@ -237,8 +237,13 @@ function fnModalEditOk(from: Record<string, any>) {
item.neName = from.neName; item.neName = from.neName;
item.ip = from.ip; item.ip = from.ip;
item.port = from.port; item.port = from.port;
if (item.status !== '2') { if (res.data.online) {
item.status = res.data.online ? '1' : '0'; item.status = '1';
if (res.data.standby) {
item.status = '3';
}
} else {
item.status = '0';
} }
Object.assign(item.serverState, res.data); Object.assign(item.serverState, res.data);
const resouresUsage = parseResouresUsage(item.serverState); const resouresUsage = parseResouresUsage(item.serverState);
@@ -357,7 +362,8 @@ function fnGetList(pageNum?: number) {
if (pageNum) { if (pageNum) {
queryParams.pageNum = pageNum; queryParams.pageNum = pageNum;
} }
listNeInfo(toRaw(queryParams)).then(res => { listNeInfo(toRaw(queryParams))
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选 // 取消勾选
if (tableState.selectedRowKeys.length > 0) { if (tableState.selectedRowKeys.length > 0) {
@@ -383,6 +389,10 @@ function fnGetList(pageNum?: number) {
}); });
} }
tableState.loading = false; tableState.loading = false;
})
.finally(() => {
// 刷新缓存的网元信息
useNeInfoStore().fnRefreshNelist();
}); });
} }
@@ -441,14 +451,9 @@ onMounted(() => {
} }
}); });
// 刷新缓存的网元信息
useNeInfoStore()
.fnRefreshNelist()
.finally(() => {
// 获取列表数据 // 获取列表数据
fnGetList(); fnGetList();
}); });
});
</script> </script>
<template> <template>
@@ -662,15 +667,7 @@ onMounted(() => {
</a-divider> </a-divider>
<div> <div>
<span>{{ t('views.ne.neInfo.serviceState') }}:</span> <span>{{ t('views.ne.neInfo.serviceState') }}:</span>
<a-tag <DictTag :options="dict.neInfoStatus" :value="record.status" />
:color="record.serverState.online ? 'processing' : 'error'"
>
{{
record.serverState.online
? t('views.ne.common.normalcy')
: t('views.ne.common.exceptions')
}}
</a-tag>
</div> </div>
<div> <div>
<span>{{ t('views.ne.neVersion.version') }}:</span> <span>{{ t('views.ne.neVersion.version') }}:</span>
+6 -6
View File
@@ -105,7 +105,7 @@ let tableColumns = ref<TableColumnsType>([
title: t('views.ne.common.serialNum'), title: t('views.ne.common.serialNum'),
dataIndex: 'serialNum', dataIndex: 'serialNum',
align: 'left', align: 'left',
width: 100, width: 120,
}, },
{ {
title: t('views.ne.common.expiryDate'), title: t('views.ne.common.expiryDate'),
@@ -126,12 +126,12 @@ let tableColumns = ref<TableColumnsType>([
{ {
title: t('common.updateTime'), title: t('common.updateTime'),
dataIndex: 'updateTime', dataIndex: 'updateTime',
align: 'center', align: 'left',
customRender(opt) { customRender(opt) {
if (!opt.value) return ''; if (!opt.value) return '';
return parseDateToStr(opt.value); return parseDateToStr(opt.value);
}, },
width: 150, width: 200,
}, },
{ {
title: t('common.operate'), title: t('common.operate'),
@@ -413,7 +413,7 @@ onMounted(() => {
:loading="modalState.confirmLoading" :loading="modalState.confirmLoading"
@click.prevent="fnRecordStateBatch()" @click.prevent="fnRecordStateBatch()"
> >
<template #icon><CloudSyncOutlined /></template> <template #icon><SyncOutlined /></template>
{{ t('views.ne.neLicense.reloadBatch') }} {{ t('views.ne.neLicense.reloadBatch') }}
</a-button> </a-button>
</a-space> </a-space>
@@ -508,7 +508,7 @@ onMounted(() => {
<a-tooltip placement="topRight"> <a-tooltip placement="topRight">
<template #title>{{ t('views.ne.neLicense.reload') }}</template> <template #title>{{ t('views.ne.neLicense.reload') }}</template>
<a-button type="link" @click.prevent="fnRecordState(record)"> <a-button type="link" @click.prevent="fnRecordState(record)">
<template #icon><CloudSyncOutlined /> </template> <template #icon><SyncOutlined /> </template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip placement="topRight"> <a-tooltip placement="topRight">
@@ -517,7 +517,7 @@ onMounted(() => {
type="link" type="link"
@click.prevent="fnModalVisibleByEdit(record.id)" @click.prevent="fnModalVisibleByEdit(record.id)"
> >
<template #icon><CloudUploadOutlined /> </template> <template #icon><UploadOutlined /> </template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
</a-space> </a-space>
@@ -54,7 +54,7 @@ let modalState: ModalStateType = reactive({
neAddress: '', neAddress: '',
dn: '-', dn: '-',
vendorName: '-', vendorName: '-',
province: '-', province: 'Area',
// 主机 // 主机
hosts: [ hosts: [
{ {
@@ -187,7 +187,7 @@ function fnModalOk() {
} }
}, },
onOk: () => { onOk: () => {
from.rmUid = `4400HX${from.neType}${from.neId}`; // 4400HX1AMF001 from.rmUid = `4400PSAP${from.neType}${from.neId}`; // 4400HX1AMF001
from.neName = `${from.neType}_${from.neId}`; // AMF_001 from.neName = `${from.neType}_${from.neId}`; // AMF_001
const hide = message.loading(t('common.loading'), 0); const hide = message.loading(t('common.loading'), 0);
const result = const result =
@@ -50,9 +50,10 @@ let fromState = ref({
ims_sip_ip: '192.168.8.110', ims_sip_ip: '192.168.8.110',
upf_type: 'LightUPF', upf_type: 'LightUPF',
upf_driver_type: 'vmxnet3', upf_driver_type: 'vmxnet3',
upf_card_name: 'eth0', upfn3_card_name: 'eth0',
upfn3_pci: '0000:00:00.0', upfn3_pci: '0000:00:00.0',
upfn3_mac: '00:00:00:00:00:00', upfn3_mac: '00:00:00:00:00:00',
upfn6_card_name: 'eth0',
upfn6_pci: '0000:00:00.0', upfn6_pci: '0000:00:00.0',
upfn6_mac: '00:00:00:00:00:00', upfn6_mac: '00:00:00:00:00:00',
}, },
@@ -243,7 +244,12 @@ watch(
</a-select> </a-select>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="12" :md="12" :xs="24"> <a-col
:lg="12"
:md="12"
:xs="24"
v-if="fromState.external.upf_type === 'LightUPF'"
>
<a-form-item label="UE_POOL" name="external.ue_pool"> <a-form-item label="UE_POOL" name="external.ue_pool">
<a-input <a-input
v-model:value="fromState.external.ue_pool" v-model:value="fromState.external.ue_pool"
@@ -260,9 +266,63 @@ watch(
</a-input> </a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col
:lg="12"
:md="12"
:xs="24"
v-if="fromState.external.upf_type === 'StandardUPF'"
>
<a-form-item label="DRIVER_TYPE" name="external.upf_driver_type">
<a-select
v-model:value="fromState.external.upf_driver_type"
:placeholder="t('common.selectPlease')"
>
<a-select-option value="vmxnet3">vmxnet3</a-select-option>
<a-select-option value="dpdk">dpdk</a-select-option>
<a-select-option value="host">host</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row> </a-row>
<a-row> <a-row v-if="fromState.external.upf_type === 'LightUPF'">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N3_IP" name="external.upfn3_ip">
<a-input
v-model:value="fromState.external.upfn3_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> netwrok ip </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="N3_GW" name="external.upfn3_gw">
<a-input
v-model:value="fromState.external.upfn3_gw"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> geteway </template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
<a-row v-if="fromState.external.upf_type === 'StandardUPF'">
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N3_IP" name="external.upfn3_ip"> <a-form-item label="N3_IP" name="external.upfn3_ip">
<a-input <a-input
@@ -294,13 +354,6 @@ watch(
</template> </template>
</a-input> </a-input>
</a-form-item> </a-form-item>
</a-col>
<a-col
:lg="12"
:md="12"
:xs="24"
v-if="fromState.external.upf_type === 'StandardUPF'"
>
<a-form-item label="N3_PCI" name="external.upfn3_pci"> <a-form-item label="N3_PCI" name="external.upfn3_pci">
<a-input <a-input
v-model:value="fromState.external.upfn3_pci" v-model:value="fromState.external.upfn3_pci"
@@ -338,10 +391,27 @@ watch(
</template> </template>
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item label="N3_NIC_NAME" name="external.upfn3_card_name">
<a-input
v-model:value="fromState.external.upfn3_card_name"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
use `ip a` show info, to inet name
<br />
or <br />
use `lshw -class network -businfo` show device name
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col> </a-col>
</a-row>
<template v-if="fromState.external.upf_type === 'StandardUPF'">
<a-row>
<a-col :lg="12" :md="12" :xs="24"> <a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N6_IP" name="external.upfn6_ip"> <a-form-item label="N6_IP" name="external.upfn6_ip">
<a-input <a-input
@@ -361,18 +431,6 @@ watch(
> >
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item label="DRIVER_TYPE" name="external.upf_driver_type">
<a-select
v-model:value="fromState.external.upf_driver_type"
:placeholder="t('common.selectPlease')"
>
<a-select-option value="vmxnet3">vmxnet3</a-select-option>
<a-select-option value="dpdk">dpdk</a-select-option>
<a-select-option value="host">host</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N6_PCI" name="external.upfn6_pci"> <a-form-item label="N6_PCI" name="external.upfn6_pci">
<a-input <a-input
v-model:value="fromState.external.upfn6_pci" v-model:value="fromState.external.upfn6_pci"
@@ -391,9 +449,9 @@ watch(
> >
</a-input> </a-input>
</a-form-item> </a-form-item>
<a-form-item label="NIC_NAME" name="external.upf_card_name"> <a-form-item label="N6_NIC_NAME" name="external.upfn6_card_name">
<a-input <a-input
v-model:value="fromState.external.upf_card_name" v-model:value="fromState.external.upfn6_card_name"
allow-clear allow-clear
:placeholder="t('common.inputPlease')" :placeholder="t('common.inputPlease')"
:maxlength="50" :maxlength="50"
@@ -406,16 +464,13 @@ watch(
or <br /> or <br />
use `lshw -class network -businfo` show device name use `lshw -class network -businfo` show device name
</template> </template>
<InfoCircleOutlined <InfoCircleOutlined style="opacity: 0.45; color: inherit" />
style="opacity: 0.45; color: inherit"
/>
</a-tooltip> </a-tooltip>
</template> </template>
</a-input> </a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
</a-row> </a-row>
</template>
</a-col> </a-col>
<a-col :lg="8" :md="8" :xs="24"> <a-col :lg="8" :md="8" :xs="24">
@@ -166,8 +166,8 @@ function fnBeforeUploadFile(file: FileType) {
const suff = fileName.substring(fileName.lastIndexOf('.')); const suff = fileName.substring(fileName.lastIndexOf('.'));
if (!['.deb', '.rpm'].includes(suff)) { if (!['.deb', '.rpm'].includes(suff)) {
message.error( message.error(
t('views.configManage.softwareManage.onlyAble', { t('views.ne.neSoftware.fileTypeNotEq', {
fileText: '(.deb、.rpm)', txt: '(.deb、.rpm)',
}), }),
3 3
); );
@@ -238,8 +238,8 @@ function fnBeforeUploadFileDep(file: FileType) {
const suff = fileName.substring(fileName.lastIndexOf('.')); const suff = fileName.substring(fileName.lastIndexOf('.'));
if (!['.deb', '.rpm'].includes(suff)) { if (!['.deb', '.rpm'].includes(suff)) {
message.error( message.error(
t('views.configManage.softwareManage.onlyAble', { t('views.ne.neSoftware.fileTypeNotEq', {
fileText: '(.deb、.rpm)', txt: '(.deb、.rpm)',
}), }),
3 3
); );
@@ -171,8 +171,8 @@ function fnBeforeUploadFile(file: FileType) {
const suff = fileName.substring(fileName.lastIndexOf('.')); const suff = fileName.substring(fileName.lastIndexOf('.'));
if (!['.deb', '.rpm'].includes(suff)) { if (!['.deb', '.rpm'].includes(suff)) {
message.error( message.error(
t('views.configManage.softwareManage.onlyAble', { t('views.ne.neSoftware.fileTypeNotEq', {
fileText: '(.deb、.rpm)', txt: '(.deb、.rpm)',
}), }),
3 3
); );
@@ -286,8 +286,8 @@ function fnBeforeUploadFileDep(file: FileType) {
const suff = fileName.substring(fileName.lastIndexOf('.')); const suff = fileName.substring(fileName.lastIndexOf('.'));
if (!['.deb', '.rpm'].includes(suff)) { if (!['.deb', '.rpm'].includes(suff)) {
message.error( message.error(
t('views.configManage.softwareManage.onlyAble', { t('views.ne.neSoftware.fileTypeNotEq', {
fileText: '(.deb、.rpm)', txt: '(.deb、.rpm)',
}), }),
3 3
); );
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff. Show more