568 Commits

Author SHA1 Message Date
TsMask
4c315a1548 fix: 租户管理表格操作去掉按钮权限 2025-10-27 15:50:44 +08:00
TsMask
4dfe872c27 chore: 更新版本号 2.2510.4 2025-10-24 19:59:47 +08:00
TsMask
d2e6e52fd0 fix: AMF配置WhiteList Content导入index0无效问题 2025-10-24 17:57:21 +08:00
TsMask
df7dacdc39 feat: 更新背景图片固定BA的 2025-10-23 14:31:43 +08:00
TsMask
0b54c730d7 feat: 基站状态补充randId列 2025-10-23 14:16:09 +08:00
zhongzm
70ddcb56ba feat:拖拽优化 2025-10-20 17:32:16 +08:00
zhongzm
719a08d993 feat:Roaming CDR自定义导出功能 2025-10-20 17:04:16 +08:00
zhongzm
81f287e9ff feat:冗余代码优化 2025-10-18 15:23:30 +08:00
zhongzm
cf724e8693 feat:基站名称和位置信息改为非必填 2025-10-17 18:13:03 +08:00
zhongzm
869e2b23fb feat:网元信息更新响应修复 2025-10-17 18:03:18 +08:00
TsMask
5d33611ef1 feat: UDM鉴权cnFlag添加5G/4G接入选择 2025-10-17 15:41:03 +08:00
TsMask
95c3001f35 feat: 更新getAllNeConfig函数,支持传入neId参数 2025-10-17 10:18:36 +08:00
zhongzm
6597b20ff9 feat:标签修复 2025-10-15 16:52:50 +08:00
TsMask
25538b2cc4 feat: 更新根网管节点处理逻辑,支持无OMC情况 2025-10-15 10:26:20 +08:00
TsMask
14590545f5 style: 移除ID列显示 2025-10-13 19:26:11 +08:00
TsMask
f7a9258473 style: 操作日志信息json结构格式美化 2025-10-13 17:33:22 +08:00
TsMask
49b78d9933 feat: 备份网元日志文件数据查看 2025-10-13 17:32:54 +08:00
zhongzm
b7fa412733 feat:悬浮标签修改 2025-10-13 17:10:03 +08:00
TsMask
14864cbf9b chore: 更新版本号 2.2510.2 2025-10-11 19:12:42 +08:00
TsMask
c40942694e fix: MML回车undefined问题,关闭搜索匹配 2025-10-11 17:31:24 +08:00
zhongzm
c1df7e2700 feat:自定义指标折叠优化 2025-10-10 19:08:57 +08:00
zhongzm
f6c3d730af feat:关键指标折叠优化 2025-10-10 19:08:39 +08:00
zhongzm
22cab89749 feat:黄金指标折叠优化 2025-10-10 19:08:14 +08:00
zhongzm
c85b588719 feat:图表自适应布局重构优化 2025-10-10 11:34:34 +08:00
TsMask
2ac730dfe2 style: 移除ID列显示 2025-10-09 19:36:26 +08:00
TsMask
669bdf5b7d style: UDM-鉴权数据,默认只支持导出非加密 2025-10-09 17:39:39 +08:00
TsMask
744867243f style: 移除网元版本和授权ID列显示 2025-10-09 11:46:06 +08:00
TsMask
79830b752a fix: SMSC-CDR添加结果原因说明 2025-09-28 18:16:53 +08:00
TsMask
4bb5a99b37 feat: UDM-auth数据导出按钮权限定义 2025-09-28 18:16:11 +08:00
zhongzm
dfa8539b9b feat:图表生成函数重构优化 2025-09-28 10:16:12 +08:00
TsMask
226a104417 style: UPF N3 Ping测试功能移除耗时显示 2025-09-25 17:00:51 +08:00
zhongzm
539ecbc039 feat:控制台打印注释 2025-09-25 16:59:41 +08:00
zhongzm
61adb5c8ae feat:修正时间单位显示问题 2025-09-25 16:46:14 +08:00
TsMask
be2595f49d style: MML返回多结果显示undefined 2025-09-25 15:28:20 +08:00
TsMask
63d02ab19d feat: UDM数据添加创建时间列 2025-09-23 10:50:18 +08:00
zhongzm
12d2c50248 feat:修正mos和cct查询时间范围 2025-09-22 15:44:46 +08:00
zhongzm
2bdad2db47 feat:mos和cct时间轴补0数据 2025-09-22 15:26:59 +08:00
zhongzm
33055d74ba feat:语音仪表盘样式修改 2025-09-22 11:53:24 +08:00
TsMask
702eeb9caa style: 翻译MOS Average改为MOS 2025-09-19 19:04:07 +08:00
TsMask
8d01eb48e6 fix: 更新cdr/ue时间强制转换数值类型 2025-09-19 19:03:13 +08:00
zhongzm
b98f2394b8 feat:语音仪表盘mos和cct显示修复和样式调整 2025-09-19 17:44:41 +08:00
TsMask
80f72549a2 feat: 添加系统备份功能,包括导入导出OMC的API和界面支持 2025-09-19 16:12:42 +08:00
TsMask
bd729b7437 style: CCT改为Call Connection Time 2025-09-19 15:41:01 +08:00
TsMask
3aaa90746b Merge branch 'multi-tenant' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into multi-tenant 2025-09-19 15:38:47 +08:00
TsMask
b55b4ad49d feat: 添加活动告警自动刷新功能 2025-09-19 15:38:45 +08:00
TsMask
ae171a2a82 fix: 根据搜索条件导出,去除最大记录限制 2025-09-19 15:29:54 +08:00
zhongzm
2828deaece feat:语音仪表盘增加指标 2025-09-19 09:14:52 +08:00
zhongzm
f8376c803f feat:折叠按钮样式修改 2025-09-18 16:27:23 +08:00
zhongzm
fe13f56aa1 feat:网元信息界面部分显示修改 2025-09-17 20:13:37 +08:00
zhongzm
1e27a414db feat:折叠图表按钮样式修改 2025-09-17 19:47:29 +08:00
TsMask
6edd445e3d feat: 新增kpi导出文件查看 2025-09-17 15:10:28 +08:00
TsMask
4a5008f1b5 style: 全局菜单搜索仅过滤菜单名称 2025-09-17 15:10:08 +08:00
TsMask
749e880aa7 feat: 补充UPF N3 Ping测试功能丢包耗时 2025-09-15 15:10:46 +08:00
TsMask
f0789acf42 style: IMS看板字母单词大写 2025-09-15 10:56:23 +08:00
TsMask
5acbec4c45 fix: UDM-redis连接配置展开key重复 2025-09-12 18:23:33 +08:00
TsMask
4f6ad8edc4 fix: 去掉未知变量Table.EXPAND_COLUMN 2025-09-12 18:22:54 +08:00
TsMask
4048d2b736 style: 仪表盘鼠标悬浮提示更新 2025-09-12 18:14:46 +08:00
TsMask
f919d31232 style: 仪表盘鼠标悬浮提示更新 2025-09-12 17:53:28 +08:00
TsMask
5e2184945c feat: UDM-redis连接多加从库地址 2025-09-12 17:49:30 +08:00
zhongzm
c3e046d9d9 Merge remote-tracking branch 'origin/multi-tenant' into multi-tenant 2025-09-12 16:32:32 +08:00
zhongzm
33dc7cb7dc feat:网元信息平滑自动更新,修复新增和删除问题 2025-09-12 16:32:23 +08:00
TsMask
5cbe885d7e feat: 添加UPF N3 Ping测试功能 2025-09-12 15:22:54 +08:00
zhongzm
6b45194c65 feat:还原User Activity 2025-09-12 14:55:14 +08:00
zhongzm
1d3ab0f6af feat:样式调整补 2025-09-12 14:50:55 +08:00
zhongzm
2d29bd4226 Merge remote-tracking branch 'origin/multi-tenant' into multi-tenant 2025-09-12 14:42:40 +08:00
zhongzm
6bbd5d723e feat:仪表盘自适应取消 2025-09-12 14:42:22 +08:00
TsMask
2a45acad33 fix: 修改图表标题,将“Busy Time”更改为“Busy Hour” 2025-09-12 14:22:48 +08:00
zhongzm
c7dd92d3b3 feat:neinfo表格结构修改,增加定时刷新 2025-09-12 13:34:02 +08:00
TsMask
905d8c3bb2 feat: 添加ims-cdr数据moc/cct 2025-09-11 20:11:55 +08:00
zhongzm
bb6cae1433 feat:语音仪表盘标签修改 2025-09-09 19:20:48 +08:00
zhongzm
d85889b376 feat:neInfo修改新增和编辑 2025-09-09 19:13:30 +08:00
zhongzm
d1a20296d9 feat:表格改为嵌套显示 2025-09-09 18:42:36 +08:00
zhongzm
2f8c80572b feat:大屏部分信息显示修改以及样式微调 2025-09-09 15:34:12 +08:00
zhongzm
7ef9d23e94 feat:黄金指标bytes改MB/GB 2025-09-09 14:29:44 +08:00
lai
0a130781d1 自适应功能 2025-09-09 12:06:49 +08:00
zhongzm
6ce5a24614 feat:关键仪表盘显示指标修改 2025-09-09 10:25:29 +08:00
lai
627f847d5e 新增搜索菜单按钮 2025-09-08 19:30:30 +08:00
zhongzm
89c2a61737 feat:kpi图表隐藏按钮添加 2025-09-08 17:04:18 +08:00
zhongzm
4403b06416 feat:语音仪表盘增加新指标显示 2025-09-08 15:30:40 +08:00
zhongzm
55f3e1c673 feat:大屏新模块改为UE Online Information 2025-09-08 15:29:46 +08:00
zhongzm
007b640209 feat:中文补充 2025-09-05 18:17:54 +08:00
zhongzm
59af80fe7f feat:仪表盘修复闪烁、跳转问题 2025-09-05 18:03:59 +08:00
TsMask
ed3b5bb5e8 feat: 添加权限控制到IMS用户数量显示 2025-09-05 17:47:42 +08:00
TsMask
58f78432f0 feat: 恢复UDM Auth创建时间列 2025-09-05 17:37:28 +08:00
TsMask
a2a5e52576 style: 去除udm导入模板#行 2025-09-05 17:37:25 +08:00
TsMask
b50c962201 fix: 更新模板文件,添加导入说明并修正数据格式 2025-09-05 15:44:06 +08:00
TsMask
2209a7206f feat: 实现PCAP文件分批下载,避免浏览器限制同时下载10个文件的问题 2025-09-05 15:43:52 +08:00
zhongzm
828c640307 feat:仪表盘增加volte、voip显示 2025-09-04 18:13:23 +08:00
zhongzm
4293c611be feat:Configuration、Performance、MML权限按钮添加 2025-09-04 14:25:26 +08:00
TsMask
f155a0dc8e fix: 修改表格行键为'imsi'以确保数据正确绑定 2025-09-03 17:12:17 +08:00
TsMask
dbf6dfbe26 fix: 修正导出按钮的权限标识,从'neUser:auth:export'改为'neUser:sub:export' 2025-09-03 11:50:35 +08:00
zhongzm
826e63a861 Merge remote-tracking branch 'origin/multi-tenant' into multi-tenant 2025-09-02 18:14:17 +08:00
zhongzm
357292d445 feat:指标尾缀neId改为neName 2025-09-02 18:14:05 +08:00
TsMask
87e5261942 feat: 优化图表显示逻辑,动态控制图表显示与关闭 2025-09-02 18:07:42 +08:00
zhongzm
5d60156456 feat:语音仪表盘部分文字信息修改补充 2025-09-02 17:50:16 +08:00
TsMask
ccd5edb76f feat: 优化图表显示逻辑,移除不必要的状态判断 2025-09-02 17:49:07 +08:00
zhongzm
99f7ddb760 feat:语音仪表盘部分文字信息修改 2025-09-02 17:43:44 +08:00
zhongzm
40137f182c feat:Data Usage Report图表显示状态判断 2025-09-02 16:43:22 +08:00
zhongzm
4666a0e9b3 Merge remote-tracking branch 'origin/multi-tenant' into multi-tenant 2025-09-02 14:37:34 +08:00
zhongzm
17650d134a feat:IMSI和布局修改 2025-09-02 14:37:25 +08:00
TsMask
f40107a50f style: kpiOverView指标名称修改 2025-09-02 14:32:30 +08:00
zhongzm
656dad2f3e feat:底部遮挡修复 2025-09-02 14:22:56 +08:00
TsMask
c2dac86db0 fix: 仪表盘点击alarm summary重定向至活动告警(非历史告警) 2025-09-02 10:50:57 +08:00
TsMask
858e3e6559 style: 性能-IMS KPI- VoNR改成 VoNR/VoLTE/VoIP 2025-09-02 10:45:59 +08:00
TsMask
86cb6e0ffe fix: 上传软件包版本号提取正则修改 2025-09-02 10:37:04 +08:00
TsMask
3c485dea48 style: 左侧展开宽度256px 2025-09-01 19:41:49 +08:00
TsMask
9b79fe8271 fix: udm-sub页面删除按钮perms标识错误 2025-09-01 19:40:06 +08:00
TsMask
4f854f77b7 style: MME attach request/success 改成 4g attach request/success 2025-09-01 15:34:00 +08:00
TsMask
2191db06d4 feat: 开站配置omc的ssh地址 2025-08-29 19:15:29 +08:00
TsMask
274353d016 feat: 基站状态页面添加租户名显示列 2025-08-29 18:45:03 +08:00
zhongzm
f0e63da1a2 feat:loaddata权限添加 2025-08-29 17:23:14 +08:00
TsMask
4b34db817e feat: 网元授权显示用户数/基站数 2025-08-29 16:25:13 +08:00
TsMask
a4aeb06d13 style: 网元当前版本显示是否有包存在 2025-08-29 16:24:34 +08:00
zhongzm
3389fbad53 feat:BA UE部分按钮权限分配 2025-08-29 16:18:26 +08:00
TsMask
3aa6822a72 fix: 已经添加的网元类型禁止修改neId 2025-08-28 20:59:02 +08:00
TsMask
bdfad20dd5 fix: cdr/ue导出操作事件重发判断 2025-08-28 16:15:26 +08:00
TsMask
d8c9bbc775 fix: cdr/ue去除导出条数限制 2025-08-28 14:59:08 +08:00
TsMask
5e014309cc fix: UPF流量统计字节数据格式化显示不一致问题 2025-08-28 14:58:20 +08:00
TsMask
390b9cf73e fix: kpi原始指标实时数据推送方向修正,重绘渲染问题修正 2025-08-28 11:21:37 +08:00
zhongzm
760beb0087 Merge remote-tracking branch 'origin/multi-tenant' into multi-tenant 2025-08-27 15:30:42 +08:00
zhongzm
5b2f883934 feat:大屏基站信息修改 2025-08-27 15:29:51 +08:00
TsMask
98521a3a89 feat:优化UPF实时流量统一10s刷新,切换显示下一个 2025-08-27 15:10:30 +08:00
zhongzm
51c9f7fca3 feat:自定义指标loading和title优化 2025-08-27 14:31:49 +08:00
zhongzm
d593abf718 feat:大屏部分信息修改 2025-08-27 14:17:38 +08:00
zhongzm
fdd390c389 feat:sortOrder参数修改 2025-08-27 14:10:38 +08:00
zhongzm
cfba4ecdb2 feat:黄金指标loading和title优化 2025-08-27 12:58:44 +08:00
zhongzm
e45c17c4c5 feat:关键指标loading和title优化 2025-08-27 12:03:02 +08:00
TsMask
f58a0151fe 修复: 注释掉位数据转换中的位数乘以8的代码 2025-08-27 11:52:08 +08:00
TsMask
ff9aca14b8 style: 隐藏掉auth数据创建时间列,等udm支持 2025-08-26 18:39:57 +08:00
TsMask
de74af4fec style: 关掉全局控制语言开关控制 2025-08-26 18:06:44 +08:00
zhongzm
393a90a0d0 feat:修改code常量 2025-08-26 17:07:20 +08:00
TsMask
3f06ab62b1 feat: 修改kpi查询数据排序方式为升序 2025-08-26 14:38:35 +08:00
TsMask
a148dc82a9 syste: 移除无效引用 2025-08-25 15:22:45 +08:00
zhongzm
a039fd7a3a feat:综合大屏悬浮信息修改 2025-08-22 18:38:25 +08:00
zhongzm
00485a5c7f feat:关键指标底部容器显示溢出问题 2025-08-22 18:13:19 +08:00
zhongzm
50cf5e70fb feat:语音仪表盘参数修改 2025-08-22 17:54:02 +08:00
zhongzm
7e5864b97b feat:关键指标统计需求修改 2025-08-22 17:12:26 +08:00
zhongzm
194ad023bd feat:语音仪表盘中英补充 2025-08-22 16:28:04 +08:00
zhongzm
f16b07a2d6 feat:关键指标添加MME网元指标(上一个是未中英的语音仪表盘) 2025-08-22 16:19:24 +08:00
zhongzm
7bf8bad9e4 feat:关键指标添加MME网元指标 2025-08-22 16:11:56 +08:00
zhongzm
ab1725e673 feat:自定义指标统计信息改天数显示 2025-08-22 15:38:55 +08:00
zhongzm
77631de0ff feat:黄金指标统计信息改天数显示 2025-08-22 15:38:30 +08:00
TsMask
f6bdecd877 feat: 支持kpi指标命名控制启用关闭 2025-08-20 17:02:18 +08:00
TsMask
e05084afdc fix: 增加多个导出操作的超时时间至180秒 2025-08-19 16:09:08 +08:00
TsMask
4c49640763 Merge branch 'multi-tenant' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into multi-tenant 2025-08-15 18:59:15 +08:00
TsMask
4433990878 chore: 更新版本号 2.2508.1 2025-08-15 18:59:11 +08:00
zhongzm
6d092cb3e7 feat:重复调用修正 2025-08-15 15:30:30 +08:00
TsMask
66957520f7 fix: 给config.js加随机数避免缓存 2025-08-14 20:45:57 +08:00
TsMask
3596d7815b feat: UDM鉴权用户显示创建时间 2025-08-14 20:25:59 +08:00
TsMask
b72724649e Merge branch 'multi-tenant' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into multi-tenant 2025-08-14 19:49:25 +08:00
TsMask
aed81672a2 fix: 系统用户登录主页重复刷新问题 2025-08-14 19:49:22 +08:00
zhongzm
a1c530640c Merge remote-tracking branch 'origin/multi-tenant' into multi-tenant 2025-08-14 19:31:03 +08:00
zhongzm
bcd7599676 feat:kpi自定义仪表盘需求更改 2025-08-14 19:30:53 +08:00
TsMask
5edb5932b5 fix: 租户主页暗色模式显示不一致 2025-08-14 19:17:19 +08:00
TsMask
7bd524976a fix: 租户和系统用户的主页显示差异 2025-08-14 17:15:18 +08:00
TsMask
7b7907616f fix: 图片上传取值fileName失败 2025-08-14 15:10:24 +08:00
TsMask
e763418e38 fix: 第三方用户不可删除和修改密码 2025-08-14 10:31:40 +08:00
TsMask
36e4f2d5f1 fix: 恢复显示快速PLMN修改窗口 2025-08-14 10:30:50 +08:00
TsMask
38f0b9b560 feat: 第三方登录认证功能和管理页 2025-08-12 09:53:38 +08:00
TsMask
246bb9a7e0 rev: 回退看板,修复切换upf的流量统计显示 2025-08-07 15:46:01 +08:00
TsMask
678bfce993 chore: 更新版本号 2.250725 2025-07-25 14:12:56 +08:00
TsMask
f8bc9fc622 feat: 参数配置AMF导入Index字段存在更新,不存在默认新增 2025-07-21 17:12:19 +08:00
TsMask
d37fd4deae feat: 参数配置刷新无法正常打开,导入白名单字段格式转换 2025-07-21 15:40:13 +08:00
zhongzm
394cef66c2 Merge remote-tracking branch 'origin/multi-tenant' into multi-tenant 2025-07-18 17:23:56 +08:00
zhongzm
7432f1e237 feat:Users添加默认显示,避免样式偏移 2025-07-18 17:23:45 +08:00
TsMask
8eea52c4e5 chore: 更新版本号 2.250718 2025-07-18 16:54:00 +08:00
zhongzm
4c777245fc feat:综合仪表盘样式优化 2025-07-18 16:49:33 +08:00
TsMask
42464bc5bd feat: 参数配置imeiWhitelist批量导入xlsx文件 2025-07-16 10:29:36 +08:00
zhongzm
a436efab67 feat:增加数据点,增加初始显示 2025-07-15 11:52:07 +08:00
zhongzm
5ae056cb03 Merge remote-tracking branch 'origin/multi-tenant' into multi-tenant 2025-07-11 11:11:59 +08:00
zhongzm
e37a7fb42b feat:折线效果以及图表数据点数修改 2025-07-11 11:11:41 +08:00
TsMask
e3306bab3a feat(dashboard): add overview2 component with images and functionality
- Introduced new images for the dashboard overview (rect.png, title.png).
- Created index.vue for the overview2 component, implementing various features including:
  - User activity tracking and statistics.
  - Network element state retrieval and display.
  - Integration of multiple sub-components (Topology, NeResources, UserActivity, IMSActivity, AlarnTypeBar, UPFFlow).
  - Added dropdowns for selecting UDM and NE resources.
  - Implemented periodic data fetching for network statistics.
  - Enhanced user interface with responsive design and improved styling.
2025-07-10 14:25:33 +08:00
TsMask
2cdbcd6de8 fix: 调整安装UE_POOL改到基础项 2025-07-10 10:54:51 +08:00
TsMask
fae66a2b79 feat: 在组件挂载时获取网元列表并更新授权状态 2025-07-10 10:33:26 +08:00
zhongzm
f45ad79015 feat:默认选择全部网元 2025-07-09 10:26:00 +08:00
TsMask
f9dd0964d2 chore: 更新版本号 2.250705 2025-07-05 10:27:21 +08:00
zhongzm
30ca7ae14d Merge remote-tracking branch 'origin/multi-tenant' into multi-tenant 2025-07-04 17:31:05 +08:00
zhongzm
82cb796a50 feat:kpi看板数据显示修复 2025-07-04 17:30:54 +08:00
lai
a98aeda96f 告警仪表盘 2025-07-04 17:08:48 +08:00
lai
1a10a5a9d1 修改综合仪表盘样式 2025-07-04 17:07:53 +08:00
TsMask
edd3865094 feat: 添加日期快捷选择功能并更新多个组件以使用新选择器 2025-07-04 15:14:13 +08:00
zhongzm
8990fa8dee feat:kpi看板 2025-07-03 19:26:01 +08:00
TsMask
2b624b3c04 fix: SMF-DATA图表日期显示不完整 2025-07-02 17:44:36 +08:00
TsMask
93b600affb fix: 跟踪任务编号的传入获取信息 2025-07-02 15:25:34 +08:00
TsMask
bea6f032ac fix: 英文翻译Filter out Alerts 2025-07-02 14:58:15 +08:00
TsMask
20b8531dc8 fix: 跟踪加载网元列表 2025-07-02 10:02:37 +08:00
TsMask
05c1638984 fix: 参数配置可见visible属性判断 2025-06-30 16:17:13 +08:00
TsMask
218dfef16b chore: 更新版本号 2.250627 2025-06-27 19:20:56 +08:00
TsMask
bcdb64777a fix: MME配置tai-tac数值类型 2025-06-27 17:39:43 +08:00
TsMask
dd9df3a08c Merge branch 'multi-tenant' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into multi-tenant 2025-06-27 16:58:32 +08:00
TsMask
fa12a5d3a8 fix: AMF配置slice确保sd为6位字符串 2025-06-27 16:58:30 +08:00
zhongzm
0196b322f0 Merge remote-tracking branch 'origin/multi-tenant' into multi-tenant 2025-06-27 16:57:03 +08:00
zhongzm
87cfc2ed79 feat:黄金指标界面指标显示bug修复 2025-06-27 16:56:50 +08:00
TsMask
b9bcd4c265 fix: 网元PLMN新增修改使用最后记录信息 2025-06-27 15:46:12 +08:00
TsMask
a54de8a9bb feat: 关闭UDM鉴权导出功能 2025-06-27 11:53:29 +08:00
TsMask
c5d7026fc5 fix: 修改时间范围选择器以使用当天的开始时间 2025-06-26 20:41:42 +08:00
TsMask
93841a02ea fix: 修复导出功能中时间分组字段的日期格式化 2025-06-26 20:40:31 +08:00
TsMask
42bd112649 feat: 在工具目录下添加文件管理放备份输出的文件展示 2025-06-25 10:48:53 +08:00
TsMask
d0add206ba fix: 网元配置一键plmn修改amf-tai/slice失败 2025-06-24 17:00:42 +08:00
TsMask
b9659837c2 fix: UDM-IMS数据批量新增/批量删除命令调整 2025-06-24 16:58:35 +08:00
TsMask
04f81af6c3 style: 刷新网元授权状态更新时间局部变化 2025-06-24 16:57:47 +08:00
TsMask
8bd1ea4faa fix 网元手动发送ftp备份路径多一层ne_coifig目录 2025-06-23 11:56:56 +08:00
TsMask
fa00b6bfa8 chore: 更新版本号 2.250620 2025-06-20 19:14:51 +08:00
TsMask
6a8e08a81a style: kpi菜单时间去掉当前小时加入今天选择项 2025-06-19 20:55:02 +08:00
TsMask
25a33678f2 feat: 快速修改PLMN接入数据操作 2025-06-18 11:11:15 +08:00
TsMask
1e8081e83e feat: 快速修改PLMN 2025-06-18 10:33:51 +08:00
TsMask
88aea40885 feat: kpi菜单时间选择预设1h/1d/7d/15d 2025-06-17 20:02:42 +08:00
TsMask
a341800efc fix: 修正上传文件到网元时使用错误的文件路径参数 2025-06-17 11:25:44 +08:00
TsMask
d75a2a1651 feat: 开站参数添加ausf是否存在标志 2025-06-17 11:16:16 +08:00
TsMask
38c0e3d08d fix: 返回数据参数调整,显示文件大小 2025-06-16 18:40:34 +08:00
TsMask
951d48f470 chore: 更新版本号 2.250613 2025-06-13 20:13:16 +08:00
TsMask
5f81d73c95 fix: udm-voip批量用户名数值限制导致无法填写 2025-06-12 19:35:33 +08:00
TsMask
9ee5fef458 style: 抓包文件目录列表显示文件大小 2025-06-11 20:35:27 +08:00
TsMask
1723d314c5 fix: 更新文件路径从 /tmp/omc/tcpdump 到 /usr/local/omc/tcpdump 2025-06-11 19:20:47 +08:00
TsMask
f14316256c fix: 空格内容无法编辑,提示信息显示方向 2025-06-10 18:56:47 +08:00
TsMask
a945e4dc5f feat: 网元信令跟踪代码同步 2025-06-10 18:55:54 +08:00
TsMask
8575d5d711 chore: 更新版本号 2.250607 2025-06-07 09:58:54 +08:00
TsMask
a9cb35ba4f fix: 告警清除自定义产生的改换接口 2025-06-04 15:16:45 +08:00
TsMask
7e21e25cf3 fix: 修改认证信息标签宽度和国际化文本格式 2025-05-23 20:08:24 +08:00
TsMask
ce9b8815df chore: 更新版本号 2.250523 2025-05-23 17:17:00 +08:00
lai
f2018eca30 add allNe Tooltip 2025-05-23 17:08:04 +08:00
lai
af513ba157 拓扑图修改 2025-05-22 18:32:42 +08:00
zhongzm
1aa1b471a5 feat:关键指标、黄金指标仪表盘多网元实现 2025-05-22 15:00:27 +08:00
TsMask
f42b921a50 fix: 网元授权许可证状态统一刷新功能 2025-05-22 09:56:28 +08:00
TsMask
db26e9a054 fix: 看板禁止图形交互模式 2025-05-20 17:32:42 +08:00
TsMask
1dd2ae6a4c feat: 添加KPI标题映射 2025-05-20 17:32:23 +08:00
TsMask
2d672250fd fix: 更新英文和中文翻译中的“批量刷新”文本 2025-05-20 17:31:59 +08:00
TsMask
59f959fcc2 feat: Tools-Host Terminal支持搜索过滤 2025-05-16 15:23:22 +08:00
TsMask
d35179c8ae chore: 更新版本号 2.250516 2025-05-16 09:56:19 +08:00
TsMask
9e0f6d3946 fix: 英文修改基站名称为 RAN Node Name 2025-05-13 18:31:20 +08:00
TsMask
b627b6aa83 fix: 修正文件切片相关API的URL路径格式 2025-05-12 19:16:55 +08:00
TsMask
e98c6cda51 fix: 修正文件切片相关API的URL路径格式 2025-05-12 17:14:31 +08:00
TsMask
481b099655 fix: 更新文件列表中的路径和标签值 2025-05-12 16:47:51 +08:00
TsMask
d9534b635e fix: 仪表盘数据会话属累加 2025-05-12 15:21:04 +08:00
TsMask
d7173ff737 chore: 更新版本号 2.250509 2025-05-09 19:15:59 +08:00
TsMask
571bc840ad feat: 统一FTP配置,本地备份文件浏览 2025-05-09 19:13:02 +08:00
TsMask
8664e72189 feat: 支持下载UDM Auth, UDM sub, User PCC导入模板 2025-05-09 18:48:27 +08:00
TsMask
f1aec581c7 fix: SMFByImsi删除图缩放按钮 2025-05-09 14:53:13 +08:00
TsMask
2492c7562a fix: UDM-Auth/Sub批量数名称修改 2025-05-09 12:02:37 +08:00
TsMask
83690f95d7 fix: SMF-UEl列表数量错误 2025-05-09 12:02:06 +08:00
TsMask
02b071f4a0 fix: OAM读取结构错误 2025-05-09 12:01:39 +08:00
TsMask
beca94906c fix: 仪表盘用户数据刷新时显示0闪烁 2025-05-07 16:55:33 +08:00
TsMask
d6c10050b4 chore: 更新版本号 2.250428 2025-04-28 19:44:28 +08:00
TsMask
5fc9aa7c2f fix: 主页重定向仪表盘 2025-04-28 18:28:21 +08:00
TsMask
9214474325 fix: 修正时间格式常量,添加GMT和RFC格式 2025-04-28 14:51:40 +08:00
TsMask
55008a112c feat: 添加多语言翻译结果码和结果原因字段 2025-04-28 14:50:36 +08:00
TsMask
600ee94ed9 style: 调整时间字段宽度 2025-04-28 14:48:54 +08:00
TsMask
55f5734d7b fix: 看板用户活动乱序问题 2025-04-28 14:37:58 +08:00
TsMask
a4fa53556b fix: 看板流量图确保时间以HH:mm:ss格式显示 2025-04-28 14:20:35 +08:00
TsMask
ca8f16fb0c feat: 添加CDR SIP响应代码类别类型字典 2025-04-28 14:19:47 +08:00
TsMask
6d7cde6058 fix: 优化udm-voip/ims功能页面 2025-04-22 15:49:58 +08:00
TsMask
2b8e222f23 fix: 锁屏密码dom错误 2025-04-22 14:33:58 +08:00
TsMask
00e97feac7 chore: 依赖版本更新 2025-04-22 11:46:04 +08:00
TsMask
10dd1270fc style: 补充多语言翻译 2025-04-22 11:45:45 +08:00
TsMask
94181fa0da fix: 网元OAM下发开关控制重启 2025-04-22 11:45:31 +08:00
TsMask
9203113c09 fix: 锁屏密码base处理,无密码进入 2025-04-22 11:44:59 +08:00
TsMask
e326bd1ef8 fix: 时间改用R3399格式 2025-04-22 11:44:24 +08:00
TsMask
69bae32c80 feat: ws心跳消息 2025-04-22 11:43:44 +08:00
TsMask
00c20df133 feat: 网元信令跟踪功能 2025-04-22 11:43:17 +08:00
zhongzm
bcb94ae9e7 feat:UDMims列调整,voip初始值修改 2025-04-16 11:30:30 +08:00
zhongzm
64072af7ad feat:参数修改 2025-04-16 10:28:49 +08:00
zhongzm
b9f540f1ee feat:sub界面调整tenant name列位置 2025-04-15 21:02:11 +08:00
zhongzm
cb7e3038fa feat:voip界面显示修复以及用户名限制数字输入 2025-04-15 20:54:59 +08:00
zhongzm
460a4e1b3b feat:UDMIMS界面显示修复 2025-04-15 20:22:33 +08:00
zhongzm
14495fce2b feat:显示修复,option修复 2025-04-12 17:27:41 +08:00
zhongzm
e06b7d4fd3 feat:显示修复 2025-04-12 16:14:53 +08:00
zhongzm
72bd9004cc feat:imsUDM界面添加功能修复 2025-04-11 20:52:04 +08:00
zhongzm
84b0ab50f4 feat:VOIP auth界面 2025-04-11 20:07:06 +08:00
TsMask
aaaf5679ae fix: 看板用户数初始neId传入失败,禁止选择当前项 2025-04-11 17:58:55 +08:00
zhongzm
b1bfb7a915 feat:删除remark和smdata的校验 2025-04-11 16:42:41 +08:00
zhongzm
58919ad4d4 feat:IMSSUB界面修改显示,增加参数 2025-04-11 15:43:02 +08:00
zhongzm
49f0037145 feat:上传界面按钮列文字提示(补) 2025-04-11 11:54:30 +08:00
zhongzm
d0b5ee7e75 feat:上传界面按钮列文字提示 2025-04-11 11:53:24 +08:00
zhongzm
c37552661c feat:UE的imsSub界面 2025-04-11 11:19:11 +08:00
zhongzm
d6a7d8348e feat:UE的Export界面
(cherry picked from commit 48ddafaec9)
2025-04-09 10:28:07 +08:00
TsMask
586003c9b9 fix: 修正获取UDM子项数量 2025-03-25 18:53:31 +08:00
TsMask
163ce9b64c fix: 修正获取UDM子项数量的响应数据结构并移除无用的缓存ID 2025-03-25 18:43:04 +08:00
TsMask
1d5268d348 fix: 更新IMS的KPI ID列表以包含更多项 2025-03-25 16:35:14 +08:00
TsMask
ef098b5d02 fix: 看板用户数切换展示 2025-03-25 15:56:52 +08:00
TsMask
1eabb4445e fix: 看板用户数切换展示 2025-03-25 15:56:37 +08:00
TsMask
b10aed3d14 fix: KPI总览无数据时展示title 2025-03-25 15:56:05 +08:00
TsMask
6590a0c811 fix: 禁止admin修改菜单分配 2025-03-21 17:08:20 +08:00
TsMask
8eed156143 fix: 增加文件下载超时时间至600秒 2025-03-21 15:24:29 +08:00
TsMask
0ab9a98ba9 chore: 更新版本号 2.250321 2025-03-21 15:15:27 +08:00
TsMask
72dadaf3f2 feat: 看板UPF流量总计7or30天 2025-03-21 15:13:47 +08:00
TsMask
f0079d67fd style: 用户列表时间列宽200px 2025-03-21 15:09:10 +08:00
TsMask
bc4560b8d9 fix: 自定义指标只有UPF显示sum列其他网元隐藏 2025-03-21 15:08:38 +08:00
TsMask
466e56b90f fix: 禁止admin修改菜单分配 2025-03-21 15:06:53 +08:00
TsMask
aa0397ad1a fix: pcap分析Protocol列换行问题 2025-03-19 17:59:17 +08:00
TsMask
c91f3e7927 fix: 告警事件导出异常/告警ID列移除 2025-03-17 18:40:38 +08:00
TsMask
69cf72af22 fix: 告警时间转换导致查询修改错误 2025-03-17 18:10:35 +08:00
TsMask
f3491a1a31 Merge branch 'main' into multi-tenant 2025-03-14 11:05:35 +08:00
TsMask
71ef748af8 chore: 更新版本号 2.250314 2025-03-14 11:03:32 +08:00
TsMask
5caa285ede fix: 网元快速安装移除网元历史包选择 2025-03-12 11:25:15 +08:00
TsMask
e14c1ce771 fix: 系统设置移除自定义主页 2025-03-12 11:14:16 +08:00
TsMask
8d22ab850a style:: 网元概览详细内存CPU信息 2025-03-12 11:13:50 +08:00
TsMask
82af22b997 fix: 移除IMS CDR视图中的展开信息 2025-03-12 11:12:56 +08:00
TsMask
37a1f748e7 feat: SMF数据单位转换MB显示 2025-03-12 11:12:30 +08:00
TsMask
a24122ecd7 feat: PCAP文件目录下载目录为ZIP文件功能 2025-03-12 11:09:27 +08:00
TsMask
ccbea1fc51 style: 调整告警页面表格列对齐和宽度,去掉pvfig 2025-03-12 11:05:17 +08:00
TsMask
ac7079b91a Merge remote-tracking branch 'origin/main' into multi-tenant 2025-03-11 18:24:42 +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
8af7031c92 Merge branch 'main' into multi-tenant 2025-03-08 14:24:08 +08:00
TsMask
e62fc0c039 fix: 系统菜单按钮权限状态可修改 2025-03-08 14:23:54 +08:00
TsMask
ad31a52663 Merge branch 'main' into multi-tenant 2025-03-08 11:09:27 +08:00
TsMask
de16b96971 chore: 更新版本号 2.250308 2025-03-08 11:02:23 +08:00
TsMask
ed9bff5d61 Merge branch 'main' into multi-tenant 2025-03-07 18:26:24 +08:00
TsMask
f0e34726ec feat: 添加软件包文件未发现提示信息 2025-03-05 11:11:58 +08:00
TsMask
3ca8154279 Merge remote-tracking branch 'origin/main' into multi-tenant 2025-03-04 17:00:15 +08:00
TsMask
2cbd2e0aa7 fix: 看板UPF切换问题 2025-03-04 16:59:16 +08:00
TsMask
73d7d64225 Merge branch 'main' into multi-tenant 2025-03-04 16:11:01 +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
3d4d785f33 Merge remote-tracking branch 'origin/main' into multi-tenant 2025-02-25 14:55:22 +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
1d02b17c20 Merge remote-tracking branch 'origin/main' into multi-tenant 2025-02-21 10:41:56 +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
5f75197a42 Merge remote-tracking branch 'origin/main' into multi-tenant 2025-02-14 20:16:54 +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
2056d7c51b fix: 看板加载拓扑图数据 2025-02-07 10:36:56 +08:00
TsMask
91866293bc fix: 租户页面CDR多语言显示 2025-02-07 10:36:16 +08:00
TsMask
2714537140 Merge remote-tracking branch 'origin/main' into multi-tenant 2025-01-24 20:51:23 +08:00
TsMask
c037660b01 Merge remote-tracking branch 'origin/main' into multi-tenant 2025-01-22 19:34:28 +08:00
TsMask
fb9db32da7 fix: 路由免用户登录认证判断 2025-01-17 18:42:06 +08:00
TsMask
874720b68d Merge remote-tracking branch 'origin/main' into multi-tenant 2025-01-17 18:40:15 +08:00
TsMask
597883fa08 Merge remote-tracking branch 'origin/main' into multi-tenant 2025-01-17 16:47:34 +08:00
TsMask
73a1a4c51b Merge remote-tracking branch 'origin/main' into multi-tenant 2025-01-16 20:59:41 +08:00
TsMask
7962c7e7a8 Merge remote-tracking branch 'origin/main' into multi-tenant 2025-01-15 18:45:52 +08:00
TsMask
6f5759b5ba Merge remote-tracking branch 'origin/main' into multi-tenant 2025-01-15 17:58:06 +08:00
TsMask
a7df09d56f Merge remote-tracking branch 'origin/main' into multi-tenant 2025-01-15 17:22:15 +08:00
TsMask
bc8207d29d Merge branch 'main' into multi-tenant 2025-01-10 19:39:58 +08:00
TsMask
6bfd0f4792 style: 网元配置多语言映射缺失 2025-01-10 19:37:29 +08:00
TsMask
93e00ed436 Merge remote-tracking branch 'origin/main' into multi-tenant 2025-01-03 21:53:07 +08:00
TsMask
33bdbd6d08 Merge remote-tracking branch 'origin/main' into multi-tenant 2025-01-03 21:34:31 +08:00
TsMask
efa30f4ee3 Merge remote-tracking branch 'origin/main' into multi-tenant 2024-12-25 17:57:34 +08:00
TsMask
401a7d65a0 feat: 同步标准版的看板页面 2024-12-25 14:21:49 +08:00
TsMask
91242e74b0 reven: 还愿网元配置 2024-12-25 10:30:49 +08:00
TsMask
f8f2ba1f92 style: 网元信息服务区域默认Area 2024-12-24 20:35:49 +08:00
TsMask
e3792eff57 fix: 网元配置类型切换清空下拉树 2024-12-24 19:49:51 +08:00
TsMask
081c79b85b Merge remote-tracking branch 'origin/main' into multi-tenant 2024-12-24 19:45:43 +08:00
TsMask
c1fdb68d96 Merge remote-tracking branch 'origin/main' into multi-tenant 2024-12-24 17:35:40 +08:00
TsMask
8f1de1c396 chore: 更新版本号 2.241130 2024-12-20 18:28:06 +08:00
lai
9b84ff9452 样式修改 2024-12-20 17:18:16 +08:00
lai
e4222e7b03 去除修改删除按钮 2024-12-20 17:17:15 +08:00
TsMask
c86c498ab9 fix: 编译cjs和css方式导致切换主题色异常 2024-12-19 21:06:51 +08:00
TsMask
2e91ab319f style: 看板UDM-签约数量参数调整 2024-12-19 11:33:37 +08:00
TsMask
d537f56b9d feat: 网元配置多网元同时配置HA功能 2024-12-19 11:31:55 +08:00
TsMask
7c4710e2e7 style: CDR/UE展开详情布局调整 2024-12-19 11:30:43 +08:00
TsMask
ea368f9162 style: smsc时间显示列宽度200px 2024-12-19 11:23:23 +08:00
TsMask
7cab3f6556 chore: 编译依赖拆包manualChunks行为 2024-12-19 11:21:46 +08:00
TsMask
a53eaaf533 fix: 角色分配菜单勾选父子级联转出子节点关联根节点数组 2024-12-19 11:21:33 +08:00
TsMask
a0cc882a4b chore: 更新版本号 2.241213 2024-12-13 21:32:31 +08:00
TsMask
d6c0f89de5 style: 在线基站列表网元类型切换时刷新 2024-12-11 15:48:13 +08:00
TsMask
f3ae3da2a5 style: UDM签约cag参数默认为空字符 2024-12-11 15:47:09 +08:00
TsMask
d5c91e733b fix: telnet终端输入回车值无法正确发出 2024-12-10 10:28:56 +08:00
TsMask
ea6dfa7558 fix: redis终端改为命令输入框,禁止窗口输入 2024-12-10 10:28:41 +08:00
TsMask
6129002b38 fix: SMF-Data隐藏RatingGroup显示 2024-12-10 10:26:40 +08:00
TsMask
e11cb11ec9 fix: 修复 租户页面合并问题 2024-12-09 17:03:29 +08:00
simonzhangsz
a95a96dfc0 update package 2024-12-07 11:55:35 +08:00
lai
5be94b499d Merge remote-tracking branch 'origin/main' into multi-tenant 2024-12-06 18:37:56 +08:00
TsMask
f66454256b fix: CDR时间转换问题 2024-12-05 14:50:46 +08:00
TsMask
1c07167f1a fix: 看板活动事件默认加载20,改amf事件Group 2024-11-19 20:46:32 +08:00
lai
d5c42f761e 修改接口 2024-11-19 18:57:30 +08:00
TsMask
831e7ee987 fix: 工具ipfer操作客户端host输入判断 2024-11-19 17:02:09 +08:00
TsMask
f15e52573b fix: UDM鉴权键入Tenant Name主动重新获取列表 2024-11-19 11:36:03 +08:00
TsMask
fcf32800fe fix: 非管理员角色不能设置布局样式 2024-11-19 11:30:48 +08:00
TsMask
a96c587559 fix: 修复网元概览数据饼图状态异常 2024-11-19 10:25:16 +08:00
TsMask
0544495d70 fix: 快速开站网元排序进行逐个安装 2024-11-15 19:44:03 +08:00
TsMask
5d7334a4de fix: 非管理员角色不能设置布局样式 2024-11-15 16:47:12 +08:00
TsMask
802a91a96d fix: 修复TelInput组件号码无法解析问题 2024-11-15 15:20:09 +08:00
lai
44cd1d354a 同步主线antd3 2024-11-07 15:17:56 +08:00
lai
612592ec2a Merge branch 'main' into multi-tenant 2024-10-31 17:34:38 +08:00
lai
2b9fa490d4 租户首页添加区分多个UPF情况 2024-10-29 18:52:51 +08:00
lai
0ab04ab819 增加Network Function IPv4属性 2024-10-24 11:12:04 +08:00
lai
580b931610 增加关闭按钮 2024-10-24 11:11:20 +08:00
lai
dd7df775e2 修复mmeEVent修改label异常 2024-10-24 11:10:31 +08:00
lai
18c2a2e4dc 首页变动 2024-10-24 11:09:33 +08:00
lai
fbdd04b4dd 增加分页 2024-10-24 11:00:49 +08:00
lai
b508d4393c 首页添加 加载状态 2024-10-17 19:44:43 +08:00
lai
d257151718 首页加载顺序问题 2024-10-17 17:30:11 +08:00
lai
7b60c30548 完善搜索时带上匹配模式后的imsi 2024-10-17 14:47:40 +08:00
lai
cf5c8906c3 根据条件导出 2024-10-17 12:02:25 +08:00
lai
249c9a5a6e 调整位置 2024-10-17 11:03:57 +08:00
lai
a78b830a05 增加租户列 2024-10-17 10:27:20 +08:00
lai
879cf783b6 告警导出异常 2024-10-16 20:30:58 +08:00
lai
895a55f367 补充 2024-10-16 19:53:01 +08:00
lai
536c77ff08 与其他设置风格一致 2024-10-16 19:28:42 +08:00
lai
37e70e7af8 更改路径方式 2024-10-16 17:06:41 +08:00
lai
87e044276c 更改首页代码文件路径 2024-10-16 15:09:53 +08:00
lai
cd3e87c1c9 多租户自定义主页 2024-10-16 14:50:15 +08:00
lai
009c4cf590 修复租户基站信息网元异常情况获取 2024-10-16 11:07:56 +08:00
lai
3c45427bff 增加短信图标 2024-10-16 10:51:14 +08:00
lai
56cbe9915c 英文拼写 2024-10-16 10:45:00 +08:00
lai
cf3103db46 多租户首页添加smsc-cdr 2024-10-16 10:33:40 +08:00
lai
879499b595 新增租户SMSC-CDR 2024-10-15 14:17:58 +08:00
e199498a8e Merge branch 'main' into multi-tenant 2024-10-12 10:34:26 +08:00
lai
82ecee9941 管理员-smsc cdr 租户列 2024-10-10 19:40:48 +08:00
4ee1d87d04 fix: tenant of upf time period change to timestamp 2024-09-30 19:29:08 +08:00
lai
39268e2162 针对获取基站信息时网元异常问题 2024-09-30 11:13:34 +08:00
lai
d6b755c234 调整触发位置 2024-09-27 19:23:04 +08:00
lai
a889f97bd6 避免缓存导致 2024-09-27 19:18:28 +08:00
lai
a77b968a17 限制新增时没有租户数据时取消租户角色 2024-09-27 17:56:41 +08:00
lai
da3d712e97 实时更新租户树 2024-09-27 14:22:21 +08:00
lai
bb662aefe3 Merge branch 'multi-tenant' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into multi-tenant 2024-09-27 11:54:14 +08:00
lai
7bbffccd21 解决tenant栏弹出问题 2024-09-27 11:54:09 +08:00
TsMask
0de115ad9e fix: 多租户SMF在线用户接口 2024-09-25 16:31:27 +08:00
lai
ec67414cb6 修复多于代码 2024-09-24 17:29:38 +08:00
TsMask
c30e8b4891 Merge remote-tracking branch 'origin/main' into multi-tenant 2024-09-21 17:18:39 +08:00
TsMask
7d5d82e7fc Merge remote-tracking branch 'origin/main' into multi-tenant 2024-09-20 19:18:04 +08:00
60083183c5 fix: fnGetList change to fnGet45GList 2024-08-23 18:49:58 +08:00
8fbd4f3952 Merge branch 'main' into multi-tenant 2024-08-19 10:20:57 +08:00
lai
2048b6f2db 删除多余代码 2024-08-15 15:31:02 +08:00
TsMask
ea6fca405b Merge remote-tracking branch 'origin/main' into multi-tenant 2024-08-12 11:42:53 +08:00
lai
9bc5eba0d6 角色单选 2024-08-02 18:58:36 +08:00
lai
b8c56c8868 Merge remote-tracking branch 'origin/main' into multi-tenant 2024-08-02 10:47:33 +08:00
9785837980 Merge branch 'main' into multi-tenant 2024-08-02 10:01:53 +08:00
lai
977a4e11ee Merge remote-tracking branch 'origin/main' into multi-tenant 2024-07-24 18:36:11 +08:00
lai
21e42709a7 Merge remote-tracking branch 'origin/main' into multi-tenant 2024-07-24 18:01:08 +08:00
lai
f8a43042db 修复 修改时并未正确渲染 2024-07-24 15:01:44 +08:00
lai
a9ee7aa925 Merge branch 'multi-tenant' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into multi-tenant 2024-07-24 09:58:50 +08:00
lai
811aedaaf4 去除多余字段 2024-07-24 09:57:42 +08:00
172b4a4856 fix: remove more action of ue event 2024-07-23 21:42:20 +08:00
lai
447ee401cb 取消过滤条件 2024-07-23 19:17:45 +08:00
lai
5f8e9954fe Merge branch 'multi-tenant' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into multi-tenant 2024-07-23 19:01:16 +08:00
lai
f8a234822d 重置不清数据 2024-07-23 19:01:14 +08:00
09245fdfda fix: tip for tenant of sim card asset 2024-07-23 18:32:11 +08:00
lai
3dafe8d1f6 增加自动填充 2024-07-23 18:05:25 +08:00
lai
c7606df740 修正 2024-07-23 17:38:58 +08:00
lai
e5c40d11d3 修正 2024-07-23 17:15:14 +08:00
lai
c9d8bb87de 增加ueEVENT 模块 2024-07-23 17:14:57 +08:00
lai
f7c2adf58c 去除默认值 2024-07-23 17:14:03 +08:00
lai
363288a141 去除默认值 2024-07-23 14:43:30 +08:00
lai
b1dbacffcc 修改默认值 2024-07-23 11:27:01 +08:00
lai
3089f8911e 去除多余校验 2024-07-23 11:14:17 +08:00
lai
83ec17343a 去除打印 2024-07-23 10:27:47 +08:00
lai
ef9db9ddf6 增加MME-EVENT事件 2024-07-23 10:03:34 +08:00
lai
1e8da20c44 增加admin管理员权限 2024-07-22 19:27:47 +08:00
lai
d9b2b6b567 替换权限标识 2024-07-22 19:11:32 +08:00
lai
636c7a5939 补充 2024-07-18 11:26:15 +08:00
lai
15f6cf0b4c 新增IMSI填写方式 2024-07-17 18:05:23 +08:00
lai
c024c304d5 针对切换时异常 2024-07-17 15:44:31 +08:00
lai
a6e100b5c2 搜索框样式 2024-07-16 18:23:36 +08:00
lai
48bac47c6b 调整 2024-07-16 17:49:24 +08:00
lai
24a147afaf Merge branch 'multi-tenant' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into multi-tenant 2024-07-16 17:38:03 +08:00
lai
5b17c9e497 签约用户搜索框机制 2024-07-16 17:37:57 +08:00
lai
79f143c22c 租户管理调整 2024-07-16 17:37:42 +08:00
TsMask
a8764fe627 Merge remote-tracking branch 'origin/main' into multi-tenant 2024-07-16 10:01:47 +08:00
lai
6a0ed31cdc 添加租户自动补全 2024-07-15 17:09:49 +08:00
lai
8a2e21a794 去除默认值 2024-07-15 15:51:54 +08:00
lai
51226fbfb4 增加租户名的自动完成框 2024-07-15 15:51:46 +08:00
lai
af5ac7d29a 修改input框 2024-07-15 11:10:40 +08:00
lai
0b98a1f697 Merge branch 'multi-tenant' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into multi-tenant 2024-07-12 10:21:27 +08:00
lai
f66cd875fb 去除没必要的权限控制 2024-07-12 10:21:19 +08:00
TsMask
795a6fabee Merge remote-tracking branch 'origin/main' into multi-tenant 2024-07-11 20:44:29 +08:00
lai
1f7becef0a 全局变量销毁时还原 2024-07-11 20:24:06 +08:00
lai
aa4f11bf44 空时限制UPF图片展示 2024-07-11 20:10:46 +08:00
lai
0b6a08a977 去除租户界面选择IMS 2024-07-11 19:47:10 +08:00
lai
5089e769da Merge branch 'multi-tenant' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into multi-tenant 2024-07-11 19:36:08 +08:00
lai
c36f402528 调整租户首页 2024-07-11 19:36:05 +08:00
lai
1f6a13951f ims在线用户信息 2024-07-11 17:26:43 +08:00
4b69b44df5 fix: tenant tips 2024-07-11 16:48:56 +08:00
5643c625ef fix: tenant tip 2024-07-11 16:42:54 +08:00
1607c97b82 fix: imsi tip 2024-07-11 16:40:50 +08:00
d0ac6d4e2e fix: imsi tips 2024-07-11 16:40:04 +08:00
0ad9d546cd fix: tenant tips 2024-07-11 16:37:53 +08:00
f8d54e35c9 fix: tenant management cn&en tips 2024-07-11 16:36:37 +08:00
lai
be2a077bba 去除多余字段 2024-07-11 11:02:35 +08:00
lai
cb5dfebb59 租户cdr独立起来 2024-07-11 10:37:46 +08:00
lai
71d23cbc32 Merge branch 'multi-tenant' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 into multi-tenant 2024-07-11 10:18:35 +08:00
lai
93315776da 调整显示问题 2024-07-11 10:18:31 +08:00
lai
0d5cbe6459 租户管理增加基站id 2024-07-11 10:18:18 +08:00
lai
9a88364d8b label调整 2024-07-11 10:17:54 +08:00
lai
c0ec4893c8 smfCDR的显示问题 2024-07-11 10:17:17 +08:00
TsMask
24cd0ff101 Merge remote-tracking branch 'origin/main' into multi-tenant 2024-07-09 16:24:50 +08:00
lai
7e92659217 增加点击修改时选择UPF的操作 2024-07-09 14:40:44 +08:00
lai
e8fefab74e 租户SMF在线信息无需再选具体网元 2024-07-09 10:38:14 +08:00
lai
78c86be8a0 添加UPF租赁类型时选择RMUID下拉框 2024-07-09 10:34:05 +08:00
lai
8f8c9f8395 取消不必要的代码 2024-07-09 10:33:19 +08:00
lai
7d5635560d 修复全屏时下拉框显示不出来问题 2024-07-09 10:32:52 +08:00
lai
e83bf43bc8 鉴权数据修改时不能修改IMSI 2024-07-09 10:32:29 +08:00
TsMask
f89ba87fef Merge remote-tracking branch 'origin/main' into multi-tenant 2024-07-06 15:54:53 +08:00
lai
ca8a4128fb 取消勾选 2024-07-05 19:38:04 +08:00
lai
21621c2056 默认值修改 2024-07-05 19:18:53 +08:00
lai
22797a8ae8 修改中英文 2024-07-05 18:07:32 +08:00
lai
374b6feef3 中英文以及排版 2024-07-05 17:03:04 +08:00
lai
c1902f978a 选择角色为tenant才显示框 2024-07-05 15:39:06 +08:00
lai
b431ae70a5 租户主页基站中英文 2024-07-05 15:05:05 +08:00
lai
8e57bcfd25 根据最新菜单进行修改 2024-07-05 14:58:25 +08:00
lai
9e5a000383 前缀后缀 2024-07-05 14:51:56 +08:00
lai
f4771892aa 改动变量名 2024-07-05 14:30:28 +08:00
lai
842f09cc90 增加租户目录中英文 2024-07-05 14:19:59 +08:00
lai
2cb92eaf69 中英文 2024-07-05 11:24:17 +08:00
lai
02eb368a45 调整位置 2024-07-04 18:33:03 +08:00
lai
4c7b5c8b55 租户SMF的cdr 2024-07-04 18:17:25 +08:00
lai
7aebab3734 管理员smfCDR增加租户名字段 2024-07-04 17:06:15 +08:00
lai
d26fb9af85 仪表盘中ws的改动 2024-07-04 16:51:22 +08:00
lai
28213cde43 同时获取45G基站信息 2024-07-04 16:50:58 +08:00
lai
c21af696fe 签约用户中英文 2024-07-04 16:50:02 +08:00
lai
ebd8821e64 增加imsi匹配搜索框 2024-07-04 16:19:04 +08:00
lai
361fdf6959 修改租户界面排版 2024-07-04 14:40:17 +08:00
lai
60dc02010d 改动监控页的ws文件 2024-07-01 19:22:31 +08:00
lai
7c420c7c95 自动补全 2024-07-01 19:20:23 +08:00
a78e7049cf merge main to multi-tenant 2024-06-28 14:26:06 +08:00
TsMask
ef3796c34f Merge remote-tracking branch 'origin/main' into multi-tenant 2024-06-27 20:19:46 +08:00
lai
c38855acf8 调整互动以及租户未激活时标红 2024-06-27 15:24:52 +08:00
lai
a2672d300f 添加卸载生命周期 2024-06-26 21:24:40 +08:00
lai
bfaf0e6aaf 无边框 2024-06-26 21:04:15 +08:00
lai
7fe0f5d84d 增加下拉框 2024-06-26 21:03:03 +08:00
lai
1eeddb20c5 更新用户管理的租户数据源 2024-06-26 16:10:35 +08:00
lai
52b61ec34a 误操作原始文件 2024-06-26 14:47:10 +08:00
lai
faefef7d2f 更新租户管理排版 2024-06-26 14:42:23 +08:00
lai
938679cbda 根据最新菜单进行更新 2024-06-26 14:36:01 +08:00
lai
83aefc5dde 增加刷新按钮 2024-06-25 18:02:59 +08:00
lai
0157ec50d3 悬浮按钮 2024-06-25 17:52:34 +08:00
lai
2f7c947708 租户管理 2024-06-25 16:46:47 +08:00
lai
4c16c39243 调整操作日志界面 2024-06-24 19:59:11 +08:00
lai
693ed533f9 租户界面归类 2024-06-24 10:40:08 +08:00
lai
584b25d9fc 租户UPF不带neId 2024-06-21 15:27:51 +08:00
lai
1fa59d163e 更换权限标识 2024-06-21 11:41:14 +08:00
lai
c94f9c5943 解决titile 连在一起 2024-06-20 20:36:52 +08:00
lai
2480ae1c0b 调整租户管理的界面 2024-06-20 20:36:25 +08:00
lai
440dc81182 签约用户切割为两份 2024-06-20 18:34:58 +08:00
lai
0b64302b54 提示框位置下移 2024-06-20 14:53:20 +08:00
lai
0e4e8e09e6 关闭modal框时销毁元素 2024-06-20 14:37:16 +08:00
lai
0b05f40e65 转义租赁类型 2024-06-19 16:17:27 +08:00
lai
ff0b3a40cd 补充参数类型 2024-06-19 14:41:06 +08:00
lai
fe541886d5 更换接口 2024-06-19 10:50:34 +08:00
lai
6406ed0e57 补充参数 2024-06-19 10:04:39 +08:00
lai
1aa2f2382a 部门管理代码还原 2024-06-18 18:31:41 +08:00
lai
197a962c0d 更新标识 2024-06-18 18:29:23 +08:00
lai
ac923d5943 路由地址改变 2024-06-18 17:11:17 +08:00
lai
92fe93b4dc 新增基站信息 数据隔离 2024-06-18 16:58:11 +08:00
lai
a26301af1e UE在线信息数据隔离 2024-06-18 16:38:30 +08:00
lai
6aa07dc756 添加跳转功能 2024-06-18 16:14:49 +08:00
lai
f5faa054b3 新增租赁类型 2024-06-18 15:15:03 +08:00
lai
5ada366f17 数值与名称换成白底 2024-06-18 11:00:18 +08:00
lai
80a41dd559 界面新增租户限制 2024-06-17 19:47:38 +08:00
lai
7031d4cdd4 限制操作列的显示 2024-06-17 19:19:45 +08:00
lai
f72b79c628 退出时清空表格记忆缓存 2024-06-17 19:19:23 +08:00
lai
490db476dd 取消携带tenantName 2024-06-17 16:55:56 +08:00
lai
1bae6e55e0 增加区分租户的列(搜索时没传递userName) 2024-06-17 14:06:51 +08:00
lai
d2503348fd 修改路径 2024-06-17 11:53:35 +08:00
lai
03710d6811 操作日志的搜索框区分 2024-06-14 17:20:38 +08:00
lai
322dccfbc1 租户的仪表盘 2024-06-14 17:20:24 +08:00
lai
0d0603058c 取消打印 2024-06-13 15:35:16 +08:00
lai
acef786c56 取消默认值 2024-06-13 15:34:05 +08:00
lai
4e726913f1 租户管理界面调整 2024-06-13 15:33:53 +08:00
lai
f699f6f3ba 用户管理界面调整 2024-06-13 15:11:03 +08:00
lai
e35356a8c6 租户管理异常(数据库需要把key字段改名) 2024-06-06 17:46:52 +08:00
lai
9152f14430 新增tenant接口 2024-06-06 15:54:16 +08:00
lai
faa3fa546b 租户管理界面 2024-06-06 15:51:59 +08:00
lai
5c31093c28 首页的变更(仪表盘稍后做调整) 2024-06-06 14:39:03 +08:00
lai
eee652bd1f 添加所属租户选项 2024-06-06 14:38:28 +08:00
lai
b7b66ad28d 增加权限限制 2024-06-06 14:37:27 +08:00
lai
29e092421c 中英文翻译 2024-06-06 11:33:21 +08:00
TsMask
0022e259ca Merge branch 'main' into multi-tenant 2024-06-06 11:06:23 +08:00
lai
0a711ee3ce test 2024-06-05 21:07:24 +08:00
228 changed files with 37167 additions and 5994 deletions

View File

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

View File

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

View File

@@ -1,5 +1,43 @@
# 版本发布日志 # 版本发布日志
## 2.2510.4-20251024
- 修复 AMF配置WhiteList Content导入index0无效问题
- 新增 更新背景图片固定BA的
- 新增 基站状态补充randId列
- 新增 Roaming CDR自定义导出功能
- 优化 基站名称和位置信息改为非必填
- 修复 网元信息更新响应修复
- 新增 UDM鉴权cnFlag添加5G/4G接入选择
- 新增 更新getAllNeConfig函数支持传入neId参数
- 修复 更新根网管节点处理逻辑支持无OMC情况
- 优化 移除ID列显示
- 优化 操作日志信息json结构格式美化
- 优化 备份网元日志文件数据查看
- 优化 悬浮标签修改
## 2.2510.2-20251011
- 新增 UDM-auth数据导出按钮权限定义
- 新增 SMSC-CDR添加结果原因说明
- 优化 移除CDR/UE/网元版本/授权列表显示ID列
- 修复 MML回车undefined问题关闭搜索匹配
## 2.2408.1-20250815
- 修复 给config.js加随机数避免缓存
- 优化 UDM鉴权用户显示创建时间
- 修复 系统用户登录主页重复刷新问题
- 新增 kpi自定义仪表盘需求更改
- 修复 租户主页暗色模式显示不一致
- 修复 租户和系统用户的主页显示差异
- 修复 图片上传取值fileName失败
- 优化 第三方用户不可删除和修改密码
- 修复 恢复显示快速PLMN修改窗口
- 新增 第三方登录认证功能和管理页
- 修复 回退看板修复切换upf的流量统计显示
## 2.2404.1-20240402 ## 2.2404.1-20240402
- 新增 网元安装流程相关页面与操作相关接口联调 - 新增 网元安装流程相关页面与操作相关接口联调

View File

@@ -46,5 +46,31 @@ export NODE_OPTIONS=--max-old-space-size=50000
```text ```text
https://192.168.5.23/ https://192.168.5.23/
admin / admin admin
admin
``` ```
## 多租户涉及页面
ems_frontend_vue3\src\views\dashboard\smfCDR\index.vue
ems_frontend_vue3\src\views\dashboard\imsCDR\index.vue
ems_frontend_vue3\src\views\dashboard\amfUE\index.vue
ems_frontend_vue3\src\views\dashboard\mmeUE\index.vue
ems_frontend_vue3\src\views\system\user\index.vue
ems_frontend_vue3\src\views\system\tenant\index.vue
ems_frontend_vue3\src\views\system\log\operate\index.vue
ems_frontend_vue3\src\views\neUser\sub\index.vue
ems_frontend_vue3\src\views\neUser\ims\index.vue
ems_frontend_vue3\src\views\neUser\ue\index.vue
ems_frontend_vue3\src\views\neUser\base5G\index.vue
ems_frontend_vue3\src\views\tenant\amfUE\index.vue
ems_frontend_vue3\src\views\tenant\base5G\index.vue
ems_frontend_vue3\src\views\tenant\ims\index.vue
ems_frontend_vue3\src\views\tenant\imsCDR\index.vue
ems_frontend_vue3\src\views\tenant\mmeUE\index.vue
ems_frontend_vue3\src\views\tenant\operate\index.vue
ems_frontend_vue3\src\views\tenant\smfCDR\index.vue
ems_frontend_vue3\src\views\tenant\sub\index.vue
ems_frontend_vue3\src\views\tenant\ue\index.vue

View File

@@ -2,14 +2,18 @@
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="google" content="notranslate"> <meta name="google" content="notranslate" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>loading...</title> <title>loading...</title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<link rel="preload" href="/loading.js" as="script"> <link rel="preload" href="/loading.js" as="script"/>
<script async src="/loading.js"></script> <script async src="/loading.js"></script>
<link rel="preload" href="/config.js" as="script"> <script>
<script async src="/config.js"></script> var script = document.createElement('script');
script.src = '/config.js?nocache=' + new Date().getTime();
script.async = true;
document.head.appendChild(script);
</script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@@ -8,54 +8,55 @@
}, },
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc && vite build", "build": "vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@ant-design/icons-vue": "^7.0.1", "@ant-design/icons-vue": "7.0.1",
"@antv/g6": "4.8.24", "@antv/g6": "4.8.24",
"@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-javascript": "6.2.3",
"@codemirror/lang-yaml": "^6.1.2", "@codemirror/lang-yaml": "6.1.2",
"@codemirror/merge": "^6.8.0", "@codemirror/merge": "6.10.0",
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "6.1.2",
"@tato30/vue-pdf": "^1.11.3", "@tato30/vue-pdf": "1.11.3",
"@vueuse/core": "^12.5.0", "@vueuse/core": "13.0.0",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "0.10.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "5.5.0",
"ant-design-vue": "^4.2.6", "ant-design-vue": "4.2.6",
"antdv-pro-layout": "^4.2.0", "antdv-pro-layout": "4.2.0",
"antdv-pro-modal": "^4.0.6", "antdv-pro-modal": "4.0.6",
"codemirror": "^6.0.1", "codemirror": "6.0.1",
"crypto-js": "^4.2.0", "crypto-js": "4.2.0",
"dayjs": "^1.11.11", "dayjs": "1.11.13",
"echarts": "~5.6.0", "echarts": "5.6.0",
"file-saver": "^2.0.5", "echarts-liquidfill": "^3.1.0",
"grid-layout-plus": "^1.0.6", "file-saver": "2.0.5",
"intl-tel-input": "~25.2.0", "grid-layout-plus": "1.0.6",
"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.3.0", "pinia": "2.3.0",
"vue": "^3.5.13", "vue": "3.5.13",
"vue-i18n": "^11.1.0", "vue-i18n": "11.1.2",
"vue-router": "^4.5.0", "vue-router": "4.5.0",
"vue3-smooth-dnd": "^0.0.6", "vue3-smooth-dnd": "0.0.6",
"xlsx": "~0.18.5" "xlsx": "0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@types/crypto-js": "^4.2.2", "@types/crypto-js": "4.2.2",
"@types/file-saver": "^2.0.7", "@types/file-saver": "2.0.7",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "3.0.6",
"@types/node": "^18.0.0", "@types/node": "^18.0.0",
"@types/nprogress": "^0.2.3", "@types/nprogress": "0.2.3",
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "5.2.3",
"less": "^4.2.1", "less": "4.2.2",
"typescript": "~5.6.3", "typescript": "5.8.2",
"unplugin-vue-components": "^0.28.0", "unplugin-vue-components": "0.28.0",
"vite": "^6.1.0", "vite": "6.2.2",
"vite-plugin-compression": "~0.5.1", "vite-plugin-compression": "0.5.1",
"vue-tsc": "^2.2.0" "vue-tsc": "2.2.8"
} }
} }

BIN
public/background/dark.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
public/background/light.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

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.

View File

@@ -0,0 +1 @@
001012082101039,1234,internet|ims_sig,internet|ims_sig,321321,255,321312,32131,32131

View File

@@ -0,0 +1,2 @@
001011100001157,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111
001011100001158,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111

View File

@@ -0,0 +1,2 @@
001011100001157,62357000583,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&internet&ims,1,64,24,65,def_eps,1,010200000000,-
001011100001158,62357000585,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&internet&ims,1,64,24,65,def_eps,1,010200000000,-

View File

@@ -0,0 +1,2 @@
62357000580,123456
62357000581,123456

View File

@@ -0,0 +1,2 @@
001012082101039,62357000580,1,ims.mnc001.mcc001.3gppnetwork.org
62357000581,62357000581,0,ims.mnc001.mcc001.3gppnetwork.org

View File

@@ -9,6 +9,8 @@ import advancedFormat from 'dayjs/plugin/advancedFormat';
import useLayoutStore from '@/store/modules/layout'; import useLayoutStore from '@/store/modules/layout';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { CACHE_LOCAL_PROCONFIG } from './constants/cache-keys-constants';
import { localRemove } from './utils/cache-local-utils';
const { t, currentLocale } = useI18n(); const { t, currentLocale } = useI18n();
const { themeConfig, initPrimaryColor, changeConf } = useLayoutStore(); const { themeConfig, initPrimaryColor, changeConf } = useLayoutStore();
dayjs.extend(advancedFormat); dayjs.extend(advancedFormat);
@@ -34,6 +36,7 @@ onBeforeMount(() => {
maxCount: 15, maxCount: 15,
}); });
initPrimaryColor(); initPrimaryColor();
localRemove(CACHE_LOCAL_PROCONFIG);
// 输出应用版本号 // 输出应用版本号
const appStore = useAppStore(); const appStore = useAppStore();

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',
}, },
@@ -211,12 +211,12 @@ export function getPass() {
* @param data 鉴权对象 * @param data 鉴权对象
* @returns object * @returns object
*/ */
export function clearAlarm(data: Record<string, any>) { export function clearAlarm2(data: Record<string, any>) {
var time = new Date(); var time = new Date();
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,
@@ -232,6 +232,19 @@ export function clearAlarm(data: Record<string, any>) {
}); });
} }
/**
* 清除告警信息
* @param ids 记录ID
* @returns object
*/
export function clearAlarm(ids: string[]) {
return request({
url: `/neData/alarm/clear`,
method: 'PUT',
data: { ids },
});
}
/** /**
* 手工同步 * 手工同步
* @param data 鉴权对象 * @param data 鉴权对象
@@ -360,3 +373,137 @@ export async function top3Sel(filterFlag?: string) {
} }
return result; return result;
} }
export async function alarmDashGetAct() {
let totalSQL = `select count(*) as total from alarm where alarm_status='1' `;
let rowsSQL = `select ne_type,alarm_id,alarm_title,orig_severity,event_time from alarm WHERE alarm_status='1' and orig_severity!='Event' order by event_time desc limit 0,10 `;
// 查询
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
method: 'get',
params: {
SQL: totalSQL,
rowsSQL: rowsSQL,
},
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
const data: DataList = {
total: 0,
rows: [],
code: result.code,
msg: result.msg,
};
result.data.data.forEach((item: any) => {
const itemData = item['alarm'];
if (Array.isArray(itemData)) {
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
data.total = itemData[0]['total'];
} else {
data.rows = itemData.map(v => parseObjLineToHump(v));
}
}
});
return data;
}
return result;
}
export async function alarmDashGetHis() {
let totalSQL = `select count(*) as total from alarm where alarm_status='0' `;
let rowsSQL = `select ne_type,alarm_id,alarm_title,orig_severity,event_time from alarm WHERE alarm_status='0' and orig_severity!='Event' order by event_time desc limit 0,10 `;
// 查询
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
method: 'get',
params: {
SQL: totalSQL,
rowsSQL: rowsSQL,
},
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
const data: DataList = {
total: 0,
rows: [],
code: result.code,
msg: result.msg,
};
result.data.data.forEach((item: any) => {
const itemData = item['alarm'];
if (Array.isArray(itemData)) {
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
data.total = itemData[0]['total'];
} else {
data.rows = itemData.map(v => parseObjLineToHump(v));
}
}
});
return data;
}
return result;
}
export async function getAlarmTrend(params: any) {
const days = Number(params.days || 1);
let groupFormat = days === 1 ? '%H:00' : '%Y-%m-%d';
let timeCondition = days === 1
? `event_time >= DATE_SUB(NOW(), INTERVAL 1 DAY)`
: `event_time >= DATE_SUB(CURDATE(), INTERVAL ${days} DAY)`;
let totalSQL = `select count(*) as total from alarm where alarm_status='0' `;
let rowsSQL = ` SELECT
DATE_FORMAT(event_time, '${groupFormat}') AS time,
SUM(CASE WHEN orig_severity='Critical' THEN 1 ELSE 0 END) AS Critical,
SUM(CASE WHEN orig_severity='Major' THEN 1 ELSE 0 END) AS Major,
SUM(CASE WHEN orig_severity='Minor' THEN 1 ELSE 0 END) AS Minor,
SUM(CASE WHEN orig_severity='Warning' THEN 1 ELSE 0 END) AS Warning
FROM alarm
WHERE alarm_status='0'
AND ${timeCondition}
GROUP BY time
ORDER BY time ASC `;
// 查询
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
method: 'get',
params: {
SQL: totalSQL,
rowsSQL: rowsSQL,
},
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
const data: DataList = {
total: 0,
rows: [],
code: result.code,
msg: result.msg,
};
result.data.data.forEach((item: any) => {
const itemData = item['alarm'];
if (Array.isArray(itemData)) {
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
data.total = itemData[0]['total'];
} else {
data.rows = itemData.map(v => parseObjLineToHump(v));
}
}
});
return data;
}
return result;
}

View File

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

View File

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

View File

@@ -61,3 +61,69 @@ export function getCaptchaImage() {
whithToken: false, whithToken: false,
}); });
} }
/**
* 登录认证源
* @returns object
*/
export function getLoginSource() {
return request({
url: '/auth/login/source',
method: 'GET',
whithToken: false,
});
}
/**
* LDAP登录
* @returns object
*/
export function loginLDAP(data: Record<string, string>) {
return request({
url: '/auth/login/ldap',
method: 'POST',
data: data,
whithToken: false,
});
}
/**
* SMTP登录
* @returns object
*/
export function loginSMTP(data: Record<string, string>) {
return request({
url: '/auth/login/smtp',
method: 'POST',
data: data,
whithToken: false,
});
}
/**
* 登录认证源OAuth2跳转登录URL
* @returns object
*/
export function loginOAuth2URL(state: string): string {
// 兼容旧前端可改配置文件
const baseUrl = import.meta.env.PROD
? sessionGet('baseUrl') || import.meta.env.VITE_API_BASE_URL
: import.meta.env.VITE_API_BASE_URL;
return `${baseUrl}/auth/login/oauth2/authorize?state=${state}`;
}
/**
* 登录认证源OAuth2认证登录
* @returns object
*/
export function loginOAuth2Token(code: string, state: string) {
return request({
url: '/auth/login/oauth2/token',
method: 'POST',
data: {
code,
state,
},
whithToken: false,
});
}

View File

@@ -5,10 +5,11 @@ import { request } from '@/plugins/http-fetch';
* @param query 查询参数 * @param query 查询参数
* @returns object * @returns object
*/ */
export function getAllNeConfig(neType: string) { export function getAllNeConfig(neType: string, neId: string) {
return request({ return request({
url: `/ne/config/list/${neType}`, url: `/ne/config/list/${neType}`,
method: 'get', method: 'get',
params: { neId },
timeout: 60_000, timeout: 60_000,
}); });
} }

View File

@@ -38,7 +38,7 @@ export function exportAMFDataUE(data: Record<string, any>) {
method: 'post', method: 'post',
data, data,
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 180_000,
}); });
} }

68
src/api/neData/backup.ts Normal file
View File

@@ -0,0 +1,68 @@
import { request } from '@/plugins/http-fetch';
/**
* 备份文件-获取FTP配置
* @returns object
*/
export function getBackupFTP() {
return request({
url: '/neData/backup/ftp',
method: 'GET',
});
}
/**
* 备份文件-文件FTP发送
* @param data 对象
* @returns object
*/
export function pushBackupFTP(data: Record<string, any>) {
return request({
url: '/neData/backup/ftp',
method: 'POST',
data,
});
}
/**
* 备份文件-更新FTP配置
* @param data 对象
* @returns object
*/
export function updateBackupFTP(data: Record<string, any>) {
return request({
url: '/neData/backup/ftp',
method: 'PUT',
data,
});
}
/**
* 备份文件-导出OMC
* @returns object
*/
export function exportBackupOMC() {
return request({
url: '/neData/backup/export-omc',
method: 'POST',
responseType: 'blob',
timeout: 180_000,
});
}
/**
* 备份文件-导入OMC
* @param filePath 备份文件上传返回的/upload 路径
* @returns object
*/
export function importBackupOMC(filePath: string) {
return request({
url: '/neData/backup/import-omc',
method: 'POST',
data: {
neType: 'OMC',
path: filePath,
},
timeout: 180_000,
});
}

View File

@@ -38,6 +38,6 @@ export function exportIMSDataCDR(data: Record<string, any>) {
method: 'post', method: 'post',
data, data,
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 180_000,
}); });
} }

148
src/api/neData/ims_sub.ts Normal file
View File

@@ -0,0 +1,148 @@
import { request } from '@/plugins/http-fetch';
/**
* UDM签约用户重载数据
* @param neId 网元ID
* @returns object
*/
export function resetIMSSub(neId: string) {
return request({
url: `/ue/udm/imsuser/resetData/${neId}`,
method: 'put',
timeout: 180_000,
});
}
/**
* UDM签约用户列表
* @param query 查询参数
* @returns object
*/
export function listIMSSub(query: Record<string, any>) {
return request({
url: '/ue/udm/imsuser/list',
method: 'get',
params: query,
timeout: 30_000,
});
}
/**
* UDM签约用户信息
* @param neId 网元ID
* @param imsi IMSI
* @returns object
*/
export function getIMSSub(neId: string, imsi: string) {
return request({
url: `/ue/udm/imsuser/${neId}/${imsi}`,
method: 'get',
});
}
/**
* UDM签约用户新增
* @param data 签约对象
* @returns object
*/
export function addIMSSub(data: Record<string, any>) {
return request({
url: `/ue/udm/imsuser/${data.neId}`,
method: 'post',
data: data,
timeout: 180_000,
});
}
/**
* UDM签约用户批量新增
* @param data 签约对象
* @param num 数量
* @returns object
*/
export function batchAddIMSSub(data: Record<string, any>, num: number) {
return request({
url: `/ue/udm/imsuser/${data.neId}/${num}`,
method: 'post',
data: data,
timeout: 180_000,
});
}
/**
* UDM签约用户修改
* @param data 签约对象
* @returns object
*/
// export function updateIMSSub(data: Record<string, any>) {
// return request({
// url: `/ue/udm/imsuser/${data.neId}`,
// method: 'put',
// data: data,
// timeout: 180_000,
// });
// }
/**
* UDM签约用户删除
* @param data 签约对象
* @returns object
*/
export function delIMSSub(neId: string, imsi_msisdn: string, tag: string) {
return request({
url: `/ue/udm/imsuser/${neId}/${imsi_msisdn}`,
method: 'delete',
params: { volte: tag },
timeout: 180_000,
});
}
/**
* UDM签约用户批量删除
* @param neId 网元ID
* @param imsi IMSI
* @param num 数量
* @returns object
*/
export function batchDelIMSSub(
neId: string,
imsi: string,
num: number,
tag: string
) {
return request({
url: `/ue/udm/imsuser/${neId}/${imsi}/${num}`,
method: 'delete',
params: { volte: tag },
timeout: 180_000,
});
}
/**
* UDM签约用户导出
* @param data 数据参数
* @returns bolb
*/
export function exportIMSSub(data: Record<string, any>) {
return request({
url: '/ue/udm/imsuser/export',
method: 'post',
data,
responseType: 'blob',
timeout: 180_000,
});
}
/**
* UDM签约用户导入
* @param data 表单数据对象
* @returns object
*/
export function importIMSSub(data: Record<string, any>) {
return request({
url: `/ue/udm/imsuser/import`,
method: 'post',
data,
timeout: 180_000,
});
}

View File

@@ -38,7 +38,7 @@ export function exportMMEDataUE(data: Record<string, any>) {
method: 'post', method: 'post',
data, data,
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 180_000,
}); });
} }

View File

@@ -25,6 +25,6 @@ export function exportNBState(data: Record<string, any>) {
method: 'post', method: 'post',
data, data,
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 180_000,
}); });
} }

View File

@@ -38,6 +38,6 @@ export function exportSGWCDataCDR(data: Record<string, any>) {
method: 'post', method: 'post',
data, data,
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 180_000,
}); });
} }

View File

@@ -38,7 +38,7 @@ export function exportSMFDataCDR(data: Record<string, any>) {
method: 'post', method: 'post',
data, data,
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 180_000,
}); });
} }

View File

@@ -38,6 +38,6 @@ export function exportSMSCDataCDR(data: Record<string, any>) {
method: 'post', method: 'post',
data, data,
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 180_000,
}); });
} }

View File

@@ -140,3 +140,17 @@ export function exportUDMAuth(data: Record<string, any>) {
timeout: 180_000, timeout: 180_000,
}); });
} }
/**
* UDM鉴权用户导出DecAuth
* @param neId 网元ID
* @returns bolb
*/
export function exportUDMDecAuth(neId: string) {
return request({
url: `/neData/udm/auth/export-dec?neId=${neId}`,
method: 'get',
responseType: 'blob',
timeout: 180_000,
});
}

142
src/api/neData/voip_auth.ts Normal file
View File

@@ -0,0 +1,142 @@
import { request } from '@/plugins/http-fetch';
/**
* UDM鉴权用户重载数据
* @param neId 网元ID
* @returns object
*/
export function resetUDMAuth(neId: string) {
return request({
url: `/ue/udm/voipauth/resetData/${neId}`,
method: 'put',
timeout: 180_000,
});
}
/**
* UDM鉴权用户列表
* @param query 查询参数
* @returns object
*/
export function listUDMAuth(query: Record<string, any>) {
return request({
url: '/ue/udm/voipauth/list',
method: 'get',
params: query,
timeout: 30_000,
});
}
/**
* UDM鉴权用户信息
* @param neId 网元ID
* @param userName 用户名
* @returns object
*/
export function getUDMAuth(neId: string, userName: string) {
return request({
url: `/ue/udm/voipauth/${neId}/${userName}`,
method: 'get',
});
}
/**
* UDM鉴权用户新增
* @param data 鉴权对象
* @returns object
*/
export function addUDMAuth(data: Record<string, any>) {
return request({
url: `/ue/udm/voipauth/${data.neId}`,
method: 'post',
data: data,
timeout: 180_000,
});
}
/**
* UDM鉴权用户批量新增
* @param data 鉴权对象
* @param num 数量
* @returns object
*/
export function batchAddUDMAuth(data: Record<string, any>, num: number) {
return request({
url: `/ue/udm/voipauth/${data.neId}/${num}`,
method: 'post',
data: data,
timeout: 180_000,
});
}
/**
* UDM鉴权用户修改
* @param data 鉴权对象
* @returns object
*/
export function updateUDMAuth(data: Record<string, any>) {
return request({
url: `/ue/udm/voipauth/${data.neId}`,
method: 'put',
data: data,
timeout: 180_000,
});
}
/**
* UDM鉴权用户删除
* @param neId 网元ID
* @param imsi IMSI
* @returns object
*/
export function delUDMAuth(neId: string, imsi: string) {
return request({
url: `/ue/udm/voipauth/${neId}/${imsi}`,
method: 'delete',
timeout: 180_000,
});
}
/**
* UDM鉴权用户批量删除
* @param neId 网元ID
* @param imsi IMSI
* @param num 数量
* @returns object
*/
export function batchDelUDMAuth(neId: string, imsi: string, num: number) {
return request({
url: `/ue/udm/voipauth/${neId}/${imsi}/${num}`,
method: 'delete',
timeout: 180_000,
});
}
/**
* UDM鉴权用户导入
* @param data 表单数据对象
* @returns object
*/
export function importUDMAuth(data: Record<string, any>) {
return request({
url: `/ue/udm/voipauth/import`,
method: 'post',
data,
timeout: 180_000,
});
}
/**
* UDM鉴权用户导出
* @param data 数据参数
* @returns bolb
*/
export function exportUDMAuth(data: Record<string, any>) {
return request({
url: '/ue/udm/voipauth/export',
method: 'post',
data,
responseType: 'blob',
timeout: 180_000,
});
}

View File

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

View File

@@ -21,12 +21,14 @@ export async function listUEInfoBySMF(query: Record<string, any>) {
}; };
// 解析数据 // 解析数据
if (result.code === RESULT_CODE_SUCCESS && result.data) { if (result.code === RESULT_CODE_SUCCESS && result.data) {
if (Array.isArray(result.data.data)) { if (result.data.total && result.data.data) {
const rows = result.data.data; data.total = result.data.total;
data.total = rows.length; data.rows = result.data.data;
data.rows = rows;
} else { } else {
Object.assign(data, result.data); Object.assign(data, {
total: result.data.length,
rows: result.data,
});
} }
} }

View File

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

View File

@@ -110,78 +110,52 @@ export async function getKPITitle(neType: string) {
} }
/** /**
* Todo 废弃 * 查询黄金指标数据kpi.id转换title
* 查询UPF上下行速率数据 * @param neType 网元类型
* @param query 查询参数
* @returns object * @returns object
*/ */
export async function listUPFData(timeArr: any) { export async function getKPITitleList(query: Record<string, any>) {
const initTime: Date = new Date(); return request({
const twentyFourHoursAgo: Date = new Date( url: '/neData/kpi/title/list',
initTime.getTime() - 10 * 60 * 1000 method: 'get',
); params: query,
});
return await Promise.allSettled([ }
// 获取参数规则
request({ /**
url: `/api/rest/databaseManagement/v1/select/omc_db/gold_kpi`, * 修改指标标题
method: 'get', * @param data 指标标题对象
params: { * @returns object
SQL: `SELECT gold_kpi.*,kpi_title.en_title FROM gold_kpi LEFT JOIN kpi_title on gold_kpi.kpi_id=kpi_title.kpi_id where 1=1 and gold_kpi.kpi_id ='UPF.03' AND timestamp BETWEEN DATE_SUB(NOW(), INTERVAL 10 MINUTE) AND NOW()`, */
}, export function updateKPITitle(data: Record<string, any>) {
timeout: 60_000, return request({
}), url: '/neData/kpi/title',
// 获取对应信息 method: 'put',
request({ data: data,
url: `/api/rest/databaseManagement/v1/select/omc_db/gold_kpi`, });
method: 'get', }
params: { //忙时呼叫
SQL: `SELECT gold_kpi.*,kpi_title.en_title FROM gold_kpi LEFT JOIN kpi_title on gold_kpi.kpi_id=kpi_title.kpi_id where 1=1 and gold_kpi.kpi_id ='UPF.06' AND timestamp BETWEEN DATE_SUB(NOW(), INTERVAL 10 MINUTE) AND NOW()`, export async function getbusyhour(query: Record<string, any>) {
}, return request({
timeout: 60_000, url: '/neData/ims/kpi/busy-hour',
}), method: 'get',
]).then(resArr => { params: query,
let upData: any = []; });
let downData: any = []; }
//MOS指标
// 规则数据 export async function getMosHour(query: Record<string, any>) {
if (resArr[0].status === 'fulfilled') { return request({
const itemV: any = resArr[0].value; url: '/neData/ims/cdr/mos-hour',
// 解析数据 method: 'get',
if ( params: query,
itemV.code === RESULT_CODE_SUCCESS && });
Array.isArray(itemV.data?.data) }
) {
let itemData = itemV.data.data; //CCT指标
let data = itemData[0]['gold_kpi']; export async function getCctHour(query: Record<string, any>) {
if (Array.isArray(data)) { return request({
try { url: '/neData/ims/cdr/cct-hour',
upData = data.map(v => parseObjLineToHump(v)); method: 'get',
} catch (error) { params: query,
console.error(error);
}
}
}
}
if (resArr[1].status === 'fulfilled') {
const itemV = resArr[1].value;
// 解析数据
if (
itemV.code === RESULT_CODE_SUCCESS &&
Array.isArray(itemV.data?.data)
) {
let itemData = itemV.data.data;
const data = itemData[0]['gold_kpi'];
if (Array.isArray(data)) {
try {
downData = data.map(v => parseObjLineToHump(v));
} catch (error) {
console.error(error);
}
}
}
}
return { upData, downData };
}); });
} }

View File

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

View File

@@ -0,0 +1,64 @@
import { request } from '@/plugins/http-fetch';
/**
* 查询登录源列表
* @param query 查询参数
* @returns object
*/
export function listLoginSource(query: Record<string, any>) {
return request({
url: '/system/login-source/list',
method: 'GET',
params: query,
});
}
/**
* 查询登录源详细
* @param id 登录源ID
* @returns object
*/
export function getLoginSource(id: string | number) {
return request({
url: `/system/login-source/${id}`,
method: 'GET',
});
}
/**
* 新增登录源
* @param data 登录源对象
* @returns object
*/
export function addLoginSource(data: Record<string, any>) {
return request({
url: '/system/login-source',
method: 'POST',
data: data,
});
}
/**
* 修改登录源
* @param data 登录源对象
* @returns object
*/
export function updateLoginSource(data: Record<string, any>) {
return request({
url: '/system/login-source',
method: 'PUT',
data: data,
});
}
/**
* 认证源删除
* @param id 登录源ID
* @returns object
*/
export function delLoginSource(id: string | number) {
return request({
url: `/system/login-source/${id}`,
method: 'DELETE',
});
}

99
src/api/system/tenant.ts Normal file
View File

@@ -0,0 +1,99 @@
import { request } from '@/plugins/http-fetch';
/**
* 查询部门列表
* @param query 查询参数
* @returns object
*/
export function listTenant(query: Record<string, any>) {
return request({
url: '/system/tenant/list',
method: 'get',
params: query,
});
}
/**
* 查询部门列表(排除节点)
* @param tenantId 部门ID
* @returns object
*/
export function listTenantExcludeChild(tenantId: string | number) {
return request({
url: `/system/tenant/list/exclude/${tenantId}`,
method: 'get',
});
}
/**
* 查询部门详细
* @param tenantId 部门ID
* @returns object
*/
export function getTenant(tenantId: string | number) {
return request({
url: `/system/tenant/${tenantId}`,
method: 'get',
});
}
/**
* 新增部门
* @param data 部门对象
* @returns object
*/
export function addTenant(data: Record<string, any>) {
return request({
url: '/system/tenant',
method: 'post',
data: data,
});
}
/**
* 修改部门
* @param data 部门对象
* @returns object
*/
export function updateTenant(data: Record<string, any>) {
return request({
url: '/system/tenant',
method: 'put',
data: data,
});
}
/**
* 删除部门
* @param TenantId 部门ID
* @returns object
*/
export function delTenant(TenantId: string | number) {
return request({
url: `/system/tenant/${TenantId}`,
method: 'delete',
});
}
/**
* 查询部门下拉树结构
* @returns object
*/
export function tenantTreeSelect() {
return request({
url: '/system/tenant/treeSelect',
method: 'get',
});
}
/**
* 部门树结构列表(指定角色)
* @param roleId 角色ID
* @returns object
*/
export function roleTenantTreeSelect(roleId: string | number) {
return request({
url: `/system/tenant/roleDeptTreeSelect/${roleId}`,
method: 'get',
});
}

View File

@@ -15,7 +15,7 @@ import { encode } from 'js-base64';
export async function downloadFile(filePath: string, range?: string) { export async function downloadFile(filePath: string, range?: string) {
return request({ return request({
url: `/file/download/${encode(filePath)}`, url: `/file/download/${encode(filePath)}`,
method: 'get', method: 'GET',
headers: range ? { range } : {}, headers: range ? { range } : {},
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 60_000,
@@ -76,8 +76,8 @@ export async function downloadFileChunk(
*/ */
export function uploadFile(data: FormData) { export function uploadFile(data: FormData) {
return request({ return request({
url: '/file/upload', url: '/file/upload2',
method: 'post', method: 'POST',
data, data,
dataType: 'form-data', dataType: 'form-data',
timeout: 180_000, timeout: 180_000,
@@ -169,7 +169,7 @@ export async function uploadFileChunk(
export function chunkCheck(identifier: string, fileName: string) { export function chunkCheck(identifier: string, fileName: string) {
return request({ return request({
url: '/file/chunkCheck', url: '/file/chunkCheck',
method: 'post', method: 'POST',
data: { identifier, fileName }, data: { identifier, fileName },
timeout: 60_000, timeout: 60_000,
}); });
@@ -189,7 +189,7 @@ export function chunkMerge(
) { ) {
return request({ return request({
url: '/file/chunkMerge', url: '/file/chunkMerge',
method: 'post', method: 'POST',
data: { identifier, fileName, subPath }, data: { identifier, fileName, subPath },
timeout: 60_000, timeout: 60_000,
}); });
@@ -203,13 +203,57 @@ export function chunkMerge(
export function chunkUpload(data: FormData) { export function chunkUpload(data: FormData) {
return request({ return request({
url: '/file/chunkUpload', url: '/file/chunkUpload',
method: 'post', method: 'POST',
data, data,
dataType: 'form-data', dataType: 'form-data',
timeout: 60_000, timeout: 60_000,
}); });
} }
/**
* 本地文件列表
* @param path 文件路径
* @param search search prefix
* @returns object
*/
export async function listFile(query: Record<string, any>) {
return request({
url: `/file/list`,
method: 'GET',
params: query,
});
}
/**
* 本地文件获取下载
* @param path 文件路径
* @param fileName 文件名
* @returns object
*/
export async function getFile(path: string, fileName: string) {
return request({
url: `/file`,
method: 'GET',
params: { path, fileName },
responseType: 'blob',
timeout: 60_000,
});
}
/**
* 本地文件删除
* @param path 文件路径
* @param fileName 文件名
* @returns object
*/
export async function delFile(path: string, fileName: string) {
return request({
url: `/file`,
method: 'DELETE',
params: { path, fileName },
});
}
/** /**
* 转存上传文件到静态资源 * 转存上传文件到静态资源
* @returns object * @returns object
@@ -217,7 +261,7 @@ export function chunkUpload(data: FormData) {
export function transferStaticFile(data: Record<string, any>) { export function transferStaticFile(data: Record<string, any>) {
return request({ return request({
url: `/file/transferStaticFile`, url: `/file/transferStaticFile`,
method: 'post', method: 'POST',
data, data,
timeout: 60_000, timeout: 60_000,
}); });
@@ -241,11 +285,12 @@ export async function uploadFileToNE(
if (uploadChunkRes.code === RESULT_CODE_SUCCESS) { if (uploadChunkRes.code === RESULT_CODE_SUCCESS) {
const transferToNeFileRes = await request({ const transferToNeFileRes = await request({
url: `/ne/action/pushFile`, url: `/ne/action/pushFile`,
method: 'post', method: 'POST',
data: { data: {
uploadPath: uploadChunkRes.data.fileName, uploadPath: uploadChunkRes.data.fileName,
neType, neType,
neId, neId,
delTemp: true,
}, },
timeout: 60_000, timeout: 60_000,
}); });

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,
}); });
} }
@@ -45,6 +45,6 @@ export function getNeViewFile(data: Record<string, any>) {
url: '/ne/action/viewFile', url: '/ne/action/viewFile',
method: 'get', method: 'get',
params: data, params: data,
timeout: 60_000, timeout: 600_000,
}); });
} }

View File

@@ -8,3 +8,12 @@ export function pingV(data: Record<string, string>) {
params: data, params: data,
}); });
} }
// ping RTT 时延抖动
export function pingRTT(data: Record<string, string>) {
return request({
url: '/tool/ping/rtt',
method: 'POST',
data: data,
});
}

View File

@@ -1,71 +0,0 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
/**
* 查询信令列表
* @param query 查询参数
* @returns object
*/
export async function listTraceData(query: Record<string, any>) {
let totalSQL = 'select count(*) as total from trace_data where 1=1 ';
let rowsSQL = 'select * from trace_data where 1=1 ';
// 查询
let querySQL = '';
if (query.imsi) {
querySQL += ` and imsi like '%${query.imsi}%' `;
}
if (query.msisdn) {
querySQL += ` and msisdn like '%${query.msisdn}%' `;
}
// 分页
const pageNum = (query.pageNum - 1) * query.pageSize;
const limtSql = ` limit ${pageNum},${query.pageSize} `;
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/omc_db/trace_data`,
method: 'get',
params: {
totalSQL: totalSQL + querySQL,
rowsSQL: rowsSQL + querySQL + limtSql,
},
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
const data: DataList = {
total: 0,
rows: [],
code: result.code,
msg: result.msg,
};
result.data.data.forEach((item: any) => {
const itemData = item['trace_data'];
if (Array.isArray(itemData)) {
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
data.total = itemData[0]['total'];
} else {
data.rows = itemData.map(v => parseObjLineToHump(v));
}
}
});
return data;
}
return result;
}
/**
* 信令数据解析HTML
* @param id 任务ID
* @returns
*/
export function getTraceRawInfo(id: Record<string, string>) {
return request({
url: `/api/rest/traceManagement/v1/decMessage/${id}`,
method: 'get',
responseType: 'text',
});
}

View File

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

View File

@@ -1,6 +1,4 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch'; import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
/** /**
* 查询跟踪任务列表 * 查询跟踪任务列表
@@ -76,29 +74,31 @@ export function filePullTask(traceId: string) {
method: 'get', method: 'get',
params: { traceId }, params: { traceId },
responseType: 'blob', responseType: 'blob',
timeout: 60_000, timeout: 600_000,
}); });
} }
/** /**
* 获取网元跟踪接口列表 * 跟踪任务数据列表
* @param query 查询参数
* @returns object * @returns object
*/ */
export async function getNeTraceInterfaceAll() { export async function listTraceData(query: Record<string, any>) {
// 发起请求 return request({
const result = await request({ url: '/trace/data/list',
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/ne_info`, method: 'get',
params: query,
});
}
/**
* 查询跟踪任务数据信息
* @param id ID
* @returns object
*/
export async function getTraceData(id: string | number) {
return request({
url: `/trace/data/${id}`,
method: 'get', method: 'get',
params: {
SQL: `SELECT ne_type,interface FROM trace_info GROUP BY ne_type,interface`,
},
}); });
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
let data = result.data.data[0];
return Object.assign(result, {
data: parseObjLineToHump(data['trace_info']),
});
}
return result;
} }

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1721721606045" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7339" width="200" height="200"><path d="M610.6 234.24L627 294.6c90.88-24.73 184.58 28.77 209.45 119.65l60.36-16.4C862.92 273.5 734.67 200.37 610.6 234.24z m-34.42-126.22l16.4 60.36c160.51-43.83 326.13 50.81 369.96 211.33l60.36-16.4C969.94 169.45 770.03 55.18 576.18 108.02zM179.47 922.27V827.5H0v-84.83l179.47-261.74h92.22v274.24h62.38v72.33h-62.38v94.77h-92.22z m0-167.09V598.02L72.33 755.18h107.14z m0 0" p-id="7340" fill="#ffffff"></path><path d="M695.69 752.62h-89.8v-77.29h186.99v231.9c-33.34 9.95-67.35 14.92-102.17 14.92-26.62 3.37-61.57 4.97-104.72 4.97-141.29-8.33-213.62-86.44-216.97-234.31 3.36-151.24 75.69-231.9 216.97-241.85 144.65 0 216.03 49.88 214.42 149.63H695.69c0-51.49-30.79-77.3-92.23-77.3-84.83 1.62-127.98 58.21-129.59 169.52 1.61 109.7 44.9 165.49 129.59 167.1 24.87 0 54.05-3.36 87.25-9.95-1.61 1.61 0 1.61 4.98 0v-97.34z m0 0" p-id="7341" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1721721630123" class="icon" viewBox="0 0 1216 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="54141" width="200" height="200"><path d="M747.264 190.912l18.368 67.328a190.272 190.272 0 0 1 233.728 133.44l67.264-18.368a260.032 260.032 0 0 0-319.36-182.4z m-38.4-140.736l18.368 67.328a336.192 336.192 0 0 1 412.8 235.776l67.328-18.368A405.952 405.952 0 0 0 708.864 50.176z m133.376 719.232h-100.16v-86.272h208.64v258.752c-37.12 11.136-75.072 16.704-114.048 16.704a984.96 984.96 0 0 1-116.864 5.568c-157.632-9.28-238.336-96.448-242.048-261.504 3.712-168.768 84.416-258.752 242.048-269.888 161.344 0 241.088 55.68 239.296 166.912H842.24c0-57.472-34.368-86.208-102.912-86.208-94.592 1.856-142.848 64.896-144.64 189.184 1.792 122.368 50.048 184.576 144.64 186.368 27.776 0 60.16-3.712 97.344-11.136-1.92 1.92 0 1.92 5.568 0v-108.48zM234.24 969.216c-86.016 0-148.992-30.72-201.216-80.64l71.424-85.248c40.704 36.864 80.64 58.368 128.256 58.368 55.296 0 89.856-26.88 89.856-74.496v-1.536c0-46.08-39.168-72.96-95.232-72.96-33.792 0-64.512 9.216-89.856 19.968L69.12 687.36l15.36-264.96h330.24v103.68H185.856l-6.144 92.928a262.976 262.976 0 0 1 70.656-9.216c104.448 0 188.16 50.688 188.16 172.032v1.536c0 113.664-80.64 185.856-204.288 185.856z" p-id="54142" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1720074329868" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4280" width="64" height="64"><path d="M616.5 151.3l-28.3 28.3c19.7 19.6 31.8 46.6 31.8 76.5 0 29.8-12.2 56.9-31.8 76.5l28.3 28.3c26.8-27 43.5-64 43.5-104.9s-16.7-77.9-43.5-104.7zM435.8 179.5l-28.3-28.3C380.7 178.1 364 215.1 364 256s16.7 77.9 43.5 104.7l28.3-28.3C416.2 312.9 404 285.8 404 256c0-29.9 12.2-56.9 31.8-76.5zM346.6 134.3L318.3 106c-38.5 38.4-62.3 91.5-62.3 150s23.8 111.6 62.3 150l28.3-28.3C315.3 346.6 296 303.5 296 256s19.3-90.6 50.6-121.7zM705.7 106l-28.3 28.3C708.7 165.4 728 208.5 728 256s-19.3 90.6-50.6 121.7l28.3 28.3c38.5-38.4 62.3-91.5 62.3-150s-23.8-111.6-62.3-150zM815.2 60.8l-0.1-0.1L786.7 89c0.1 0 0.1 0.1 0.2 0.1C831.5 133.7 856 193 856 256s-24.5 122.3-69.1 166.9c0 0-0.1 0.1-0.2 0.1l28.3 28.3 0.1-0.1C867.3 399 896 329.7 896 256s-28.7-143-80.8-195.2zM237.3 89L209 60.7l-0.1 0.1C156.7 113 128 182.3 128 256s28.7 143 80.8 195.2l0.1 0.1 28.3-28.3c-0.1 0-0.1-0.1-0.2-0.1-44.5-44.6-69-103.9-69-166.9s24.5-122.3 69.1-166.9c0.1 0 0.1-0.1 0.2-0.1zM511.712 256.002l0.283-0.283 0.283 0.283-0.283 0.283z" p-id="4281" fill="#ffffff"></path><path d="M511.719 256.021l0.282-0.282 0.283 0.282-0.283 0.283z" p-id="4282" fill="#ffffff"></path><path d="M708.4 616l-14.2-26.1-7.6-13.9-11.6-21.2L512 256 337.5 576l-21.8 40L128 960h74l33.8-64 28.9-53h494.6l28.9 53 33.8 64h74L708.4 616zM512 389.6L613.7 576H410.3L512 389.6zM388.5 616H569L305.6 768.1 388.5 616z m-63.4 187l313.6-181.1L737.5 803H325.1z" p-id="4283" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1729046804258" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1511" width="200" height="200"><path d="M958.359001 286.941198c0-64.308655-52.132334-116.440989-116.440989-116.440989L181.453678 170.500209c-64.308655 0-116.440989 52.132334-116.440989 116.440989l0 448.63995c0 64.308655 52.132334 116.440989 116.440989 116.440989l660.464333 0c64.308655 0 116.440989-52.132334 116.440989-116.440989L958.359001 286.941198zM843.814198 212.455763c12.059664 0 23.444968 3.465938 33.519418 8.598842L530.749016 549.347606c-1.227967 1.161453-1.156336 0.916882-1.752924 1.868557-1.426489 1.067308-6.055926 3.922333-15.449877 3.922333-9.397021 0-14.025435-2.75474-15.4509-3.818979-0.676405-1.077541-0.662079-0.716314-1.995447-1.940189l-353.122503-324.412624c11.609409-7.557116 25.445532-12.509918 40.300868-12.509918L843.814198 212.456786zM916.403446 736.484727c0 40.857547-31.730679 73.582879-72.589248 73.582879L183.27721 810.067606c-40.85857 0-75.28566-32.725332-75.28566-73.582879L107.99155 287.657512c0-10.630105 2.860141-20.735253 6.909363-29.882588l351.486236 322.096882c4.91187 5.302773 19.479657 17.676591 47.009663 17.676591 27.799136 0 42.303478-12.618389 47.076178-17.830087l346.800517-328.899822c6.251378 10.860349 9.129938 23.433712 9.129938 36.838L916.403446 736.484727z" fill="#ffffff" p-id="1512"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,479 @@
<template>
<a-modal
v-model:open="visible"
:title="t('common.exportCustom')"
width="750px"
:confirm-loading="confirmLoading"
@ok="handleOk"
@cancel="handleCancel"
>
<div class="export-custom-container">
<!-- 列配置区域 -->
<div class="columns-config">
<div class="config-header">
<h4>{{ t('common.exportColumns') }}</h4>
<a-button type="link" @click="resetToDefault">
{{ t('common.resetToDefault') }}
</a-button>
</div>
<div class="columns-list">
<Container
@drop="onDrop"
:get-child-payload="getChildPayload"
drag-class="drag-ghost"
drop-class="drop-ghost"
drag-handle-selector=".drag-handle"
:non-drag-area-selector="'.column-name, .ant-checkbox-wrapper'"
>
<Draggable
v-for="(column, index) in customColumns"
:key="column.key"
class="column-item"
>
<div class="column-controls">
<a-checkbox
v-model:checked="column.visible"
@change="updateColumnVisibility(column)"
/>
<div class="drag-handle">
<HolderOutlined />
</div>
</div>
<div class="column-info">
<div class="column-name">
<a-input
v-model:value="column.title"
:placeholder="t('common.columnName')"
size="small"
/>
</div>
<!-- 隐藏col_数字标签不影响用户使用 -->
<!-- <div class="column-key">
<a-tag size="small" color="blue">{{ column.key }}</a-tag>
</div> -->
</div>
</Draggable>
</Container>
</div>
</div>
<!-- 预览区域 -->
<div class="preview-section">
<h4>{{ t('common.preview') }}</h4>
<div class="preview-table">
<a-table
:columns="previewColumns"
:data-source="previewData"
:pagination="false"
size="small"
:scroll="{ x: 400 }"
/>
</div>
</div>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue';
import { Modal, message } from 'ant-design-vue/es';
import { Container, Draggable } from 'vue3-smooth-dnd';
import useI18n from '@/hooks/useI18n';
import { ColumnsType } from 'ant-design-vue/es/table';
interface CustomColumn {
key: string;
title: string;
visible: boolean;
originalTitle: string;
dataIndex?: string;
customRender?: any;
columnIndex?: number; // Excel列索引
}
interface Props {
open: boolean;
originalColumns: ColumnsType;
sampleData: any[];
}
interface Emits {
(e: 'update:open', value: boolean): void;
(e: 'confirm', config: CustomColumn[]): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const { t } = useI18n();
const visible = ref(false);
const confirmLoading = ref(false);
const customColumns = ref<CustomColumn[]>([]);
// 监听props变化
watch(() => props.open, (newVal) => {
visible.value = newVal;
if (newVal) {
initCustomColumns();
}
});
watch(visible, (newVal) => {
emit('update:open', newVal);
});
// 初始化自定义列配置
function initCustomColumns() {
// 如果传入的是完整的列配置包含columnIndex直接使用
if (props.originalColumns.length > 0 && (props.originalColumns[0] as any).columnIndex !== undefined) {
const columns: CustomColumn[] = props.originalColumns.map(col => ({
key: (col as any).key,
title: (col as any).title,
visible: true,
originalTitle: (col as any).originalTitle || (col as any).title,
dataIndex: (col as any).dataIndex,
columnIndex: (col as any).columnIndex
}));
// 尝试从本地存储加载配置
const savedConfig = localStorage.getItem('sgwc-cdr-export-config');
if (savedConfig) {
try {
const saved = JSON.parse(savedConfig);
console.log('Loaded saved config:', saved);
// 基于originalTitle匹配保存的配置
const mergedColumns = columns.map(col => {
const savedCol = saved.find((s: any) => s.originalTitle === col.originalTitle);
if (savedCol) {
console.log(`Matched column: ${col.originalTitle}, visible: ${savedCol.visible}, title: ${savedCol.title}`);
return {
...col,
visible: savedCol.visible !== undefined ? savedCol.visible : true,
title: savedCol.title || col.title
};
}
return col;
});
// 按照保存的顺序排列(如果存在)
const sortedColumns = sortColumnsByConfig(mergedColumns, saved);
console.log('Final columns after merge and sort:', sortedColumns);
customColumns.value = sortedColumns;
} catch (error) {
console.error('Failed to load saved export config:', error);
customColumns.value = columns;
}
} else {
console.log('No saved config found, using default columns');
customColumns.value = columns;
}
return;
}
// 兼容旧的表格列配置
const columns: CustomColumn[] = props.originalColumns
.filter(col => col.key !== 'id' && (col as any).dataIndex) // 过滤掉操作列
.map(col => ({
key: col.key as string,
title: col.title as string,
visible: true,
originalTitle: col.title as string,
dataIndex: (col as any).dataIndex as string,
customRender: (col as any).customRender
}));
// 尝试从本地存储加载配置
const savedConfig = localStorage.getItem('sgwc-cdr-export-config');
if (savedConfig) {
try {
const saved = JSON.parse(savedConfig);
// 合并保存的配置和当前列,只合并特定字段
const mergedColumns = columns.map(col => {
const savedCol = saved.find((s: CustomColumn) => s.key === col.key);
if (savedCol) {
return {
...col,
visible: savedCol.visible, // 只合并可见性
title: savedCol.title !== savedCol.originalTitle ? savedCol.title : col.title // 只合并自定义的标题
};
}
return col;
});
customColumns.value = mergedColumns;
} catch (error) {
console.error('Failed to load saved export config:', error);
customColumns.value = columns;
}
} else {
customColumns.value = columns;
}
}
// 按照保存的配置排序列
function sortColumnsByConfig(columns: CustomColumn[], savedConfig: CustomColumn[]): CustomColumn[] {
if (!savedConfig || savedConfig.length === 0) {
return columns;
}
// 创建一个映射originalTitle -> savedOrder
const orderMap = new Map<string, number>();
savedConfig.forEach((col, index) => {
orderMap.set(col.originalTitle, index);
});
// 排序:已保存的列按保存顺序,新列放在最后
return [...columns].sort((a, b) => {
const orderA = orderMap.has(a.originalTitle) ? orderMap.get(a.originalTitle)! : 9999;
const orderB = orderMap.has(b.originalTitle) ? orderMap.get(b.originalTitle)! : 9999;
return orderA - orderB;
});
}
// 预览列配置
const previewColumns = computed(() => {
return customColumns.value
.filter(col => col.visible)
.map(col => ({
title: col.title,
dataIndex: col.dataIndex || col.key,
key: col.key,
customRender: col.customRender
}));
});
// 预览数据只显示前2条降低预览高度
const previewData = computed(() => {
return props.sampleData.slice(0, 1);
});
// 更新列可见性
function updateColumnVisibility(column: CustomColumn) {
// 确保至少有一列可见
const visibleCount = customColumns.value.filter(col => col.visible).length;
if (visibleCount === 0) {
column.visible = true;
message.warning(t('common.atLeastOneColumn'));
}
}
// 拖拽相关函数
function getChildPayload(index: number) {
return customColumns.value[index];
}
function onDrop(dropResult: any) {
const { removedIndex, addedIndex } = dropResult;
if (removedIndex !== null && addedIndex !== null) {
const item = customColumns.value[removedIndex];
customColumns.value.splice(removedIndex, 1);
customColumns.value.splice(addedIndex, 0, item);
}
}
// 重置为默认配置
function resetToDefault() {
Modal.confirm({
title: t('common.confirm'),
content: t('common.resetConfirm'),
onOk() {
// 清除本地存储的配置
localStorage.removeItem('sgwc-cdr-export-config');
// 重新初始化
initCustomColumns();
message.success(t('common.resetSuccess'));
}
});
}
// 确认导出
function handleOk() {
const visibleColumns = customColumns.value.filter(col => col.visible);
if (visibleColumns.length === 0) {
message.error(t('common.selectAtLeastOneColumn'));
return;
}
confirmLoading.value = true;
// 保存配置到本地存储
try {
// 保存时只保存必要的字段,用于下次加载时恢复配置
const configToSave = customColumns.value.map((col, index) => ({
originalTitle: col.originalTitle || col.title, // 用于匹配列
title: col.title, // 自定义标题
visible: col.visible, // 可见性
order: index // 顺序
}));
localStorage.setItem('sgwc-cdr-export-config', JSON.stringify(configToSave));
console.log('Export config saved:', configToSave);
} catch (error) {
console.error('Failed to save export config:', error);
}
// 延迟一下让用户看到loading状态
setTimeout(() => {
emit('confirm', customColumns.value);
confirmLoading.value = false;
visible.value = false;
}, 500);
}
// 取消
function handleCancel() {
visible.value = false;
}
onMounted(() => {
if (props.open) {
initCustomColumns();
}
});
// 调试功能:清除有问题的缓存
function clearProblematicCache() {
localStorage.removeItem('sgwc-cdr-export-config');
console.log('Cleared problematic cache');
}
</script>
<style lang="less" scoped>
.export-custom-container {
.columns-config {
margin-bottom: 12px;
.config-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
h4 {
margin: 0;
font-size: 14px;
font-weight: 600;
}
}
.columns-list {
border: 1px solid #d9d9d9;
border-radius: 6px;
max-height: 200px;
overflow-y: auto;
.column-item {
display: flex;
align-items: center;
padding: 6px 10px;
border-bottom: 1px solid #f0f0f0;
transition: background-color 0.2s;
&:hover {
background-color: #fafafa;
}
&:last-child {
border-bottom: none;
}
.column-controls {
display: flex;
align-items: center;
margin-right: 12px;
.drag-handle {
margin-left: 8px;
cursor: grab;
color: #999;
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
user-select: none;
&:hover {
color: #1890ff;
background-color: #f0f0f0;
}
&:active {
cursor: grabbing;
color: #0050b3;
background-color: #e6f7ff;
}
}
}
.column-info {
flex: 1;
display: flex;
align-items: center;
pointer-events: auto;
cursor: default;
.column-name {
flex: 1;
}
.column-key {
display: none; // 隐藏标签
}
}
// 禁止复选框触发拖拽
:deep(.ant-checkbox-wrapper) {
pointer-events: auto;
cursor: pointer;
}
}
}
}
.preview-section {
h4 {
margin: 0 0 10px 0;
font-size: 14px;
font-weight: 600;
}
.preview-table {
border: 1px solid #d9d9d9;
border-radius: 6px;
overflow: hidden;
max-height: 180px;
:deep(.ant-table-wrapper) {
.ant-table {
font-size: 12px;
}
.ant-table-thead > tr > th {
padding: 8px 8px;
font-size: 12px;
}
.ant-table-tbody > tr > td {
padding: 6px 8px;
font-size: 12px;
}
.ant-table-body {
max-height: 120px;
overflow-y: auto;
}
}
}
}
}
// 拖拽样式
.drag-ghost {
opacity: 0.5;
background: #f0f0f0;
}
.drop-ghost {
background: #e6f7ff;
}
</style>

View File

@@ -3,3 +3,6 @@ export const ADMIN_ROLE_KEY = 'system';
/**系统管理员-系统指定权限 */ /**系统管理员-系统指定权限 */
export const ADMIN_PERMISSION = '*:*:*'; export const ADMIN_PERMISSION = '*:*:*';
/**次级系统管理员 */
export const TENANTADMIN_ROLE_KEY = 'admin';

View File

@@ -0,0 +1,47 @@
import dayjs from 'dayjs';
/**日期快捷选择 */
export function dayjsRanges() {
return [
{
label: 'Today',
value: [dayjs().startOf('day'), dayjs()],
},
{
label: 'Last 1 hour',
value: [
dayjs().subtract(1, 'hour').startOf('hour'),
dayjs().subtract(1, 'hour').endOf('hour'),
],
},
// {
// label: 'Last 3 hour',
// value: [dayjs().subtract(3, 'hours'), dayjs()],
// },
// {
// label: 'Last 6 hour',
// value: [dayjs().subtract(6, 'hours'), dayjs()],
// },
{
label: 'Last 1 day',
value: [
dayjs().subtract(1, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'),
],
},
{
label: 'Last 7 day',
value: [
dayjs().subtract(7, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'),
],
},
{
label: 'Last 15 day',
value: [
dayjs().subtract(15, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'),
],
},
];
}

View File

@@ -10,13 +10,14 @@ export default {
desc: 'Core Network Management Platform', desc: 'Core Network Management Platform',
loading: 'Please wait...', loading: 'Please wait...',
inputPlease: 'Please input', inputPlease: 'Please input',
searchPlease: 'Search menus...',
searchTip: 'Enter keywords to search menus',
selectPlease: 'please select', selectPlease: 'please select',
tipTitle: 'Prompt', tipTitle: 'Prompt',
msgSuccess: 'Success {msg}', msgSuccess: 'Success {msg}',
errorFields: 'Please fill in the required information in {num} correctly!', errorFields: 'Please fill in the required information in {num} correctly!',
tablePaginationTotal: 'Total {total} items', tablePaginationTotal: 'Total {total} items',
noData: "No Data", noData: "No Data",
zebra:'Tabular zebra pattern',
ok: 'Ok', ok: 'Ok',
cancel: 'Cancel', cancel: 'Cancel',
close: 'Close', close: 'Close',
@@ -29,6 +30,7 @@ export default {
editText: 'Edit', editText: 'Edit',
deleteText: 'Delete', deleteText: 'Delete',
downloadText: 'Download', downloadText: 'Download',
CloudServerText:'Sync',
import:'Import', import:'Import',
export:'Export', export:'Export',
uploadText: 'Upload', uploadText: 'Upload',
@@ -39,6 +41,11 @@ export default {
columnSetText: 'Column Setting', columnSetText: 'Column Setting',
columnSetTitle: 'Column Display / Sorting', columnSetTitle: 'Column Display / Sorting',
sizeText: 'Density', sizeText: 'Density',
preview:'Preview',
exportCustom:'Custom Export',
exportColumns:'Column Definition',
resetToDefault:'Reset to default columns',
exportDefault:'Export',
size: { size: {
default: 'Default', default: 'Default',
middle: 'Medium', middle: 'Medium',
@@ -131,7 +138,7 @@ export default {
}, },
LockScreen: { LockScreen: {
inputPlacePwd:'Lock Screen Password', inputPlacePwd:'Lock Screen Password',
validSucc:'Validation Passed', enter:'Enter',
validError:'Validation Failure', validError:'Validation Failure',
backLogin:'Logout to Relogin', backLogin:'Logout to Relogin',
backReload:'Restarting now, please wait...', backReload:'Restarting now, please wait...',
@@ -143,7 +150,7 @@ export default {
// 静态路由 // 静态路由
router: { router: {
index: "Home", index: "Overview",
login: "Sign In", login: "Sign In",
register: 'Registrations', register: 'Registrations',
page403: 'No Access', page403: 'No Access',
@@ -254,12 +261,16 @@ export default {
login: { login: {
tabPane1: 'Account password login', tabPane1: 'Account password login',
tabPane2: 'Login with phone number', tabPane2: 'Login with phone number',
registerBtn: 'Register an account', registerBtn: 'Register Account',
loginBtn: 'Sign In', loginBtn: 'Sign In',
loginSuccess: 'Login Successful', loginSuccess: 'Login Successful',
loginMethod: 'Other login methods:', otherMethod: 'Other Methods',
loginMethodWX: 'WeChat Scan Login', backBtn: 'Back',
loginMethodQQ: 'QQ Scan Code Login', backBtnLogin: 'Return Login',
authorizedNotfound: 'Authorized Not Found',
authorizedFailed: 'Authorized Failed',
authorizedSuccess: 'Authorized Successful',
redirectHome: 'Redirect to home page in {i} seconds',
}, },
register: { register: {
registerBtn: 'Register', registerBtn: 'Register',
@@ -353,18 +364,36 @@ export default {
dashboard: { dashboard: {
overview:{ overview:{
title: "Core Network Dashboard", title: "Core Network Dashboard",
fullscreen: "Click on the full-screen display", fullscreen: "Full Screen Display",
toRouter: "Click to jump to the detail page", toRouter: "Click to jump to the detail page",
Users:"UDM Subscription Data",
VoNR:"IMS Online Users",
sessions:"PDU Sessions in SMF",
FivegNodeN:"Online gNB in AMF",
Fiveusers:"Active users in AMF",
FourgNodeN:"Online eNB in MME",
Fourusers:"Active users in MME",
UPFjump:"Key Performance Indicators",
Networkjump:"5GC System Architecture",
Alarmjump:"Active Alarms",
IMSUsers:"UDM IMS User Data",
VoIPUsers:"UDM VoIP User Data",
skim: { skim: {
users: "Users", users: "Total Subscriber Base",
userTitle:'User Information', userTitle:'Subscriber Information',
imsUeNum: "VoNR/VoLTE", imsUeNum: "VoNR/VoLTE",
smfUeNum: "Data Sessions", smfUeNum: "User Sessions",
gnbBase: "Online gNodeB", gnbBase: "Online gNodeB",
gnbSumBase: "Total gNodeB",
enbBase: "Online eNodeB", enbBase: "Online eNodeB",
enbSumBase: "Total eNodeB",
gnbUeNum:'5G Active Users', gnbUeNum:'5G Active Users',
enbUeNum:'4G Active Users', enbUeNum:'4G Active Users',
baseTitle:'Online Information', baseTitle:'Online Information',
nodeBInfo: 'NodeB Information',
onlineinfo:'UE Online Information',
ims:'VoLTE',
voip:'VoIP'
}, },
upfFlow:{ upfFlow:{
title: "UPF Throughput", title: "UPF Throughput",
@@ -394,6 +423,7 @@ export default {
}, },
userActivity: { userActivity: {
title: "User Activity", title: "User Activity",
imsTitle: "IMS Activity",
type: "Type", type: "Type",
duration: "Duration", duration: "Duration",
caller: "Caller", caller: "Caller",
@@ -412,15 +442,20 @@ export default {
rowInfo: "Info", rowInfo: "Info",
type: "Type", type: "Type",
duration: "Duration", duration: "Duration",
mosAverage: "MOS",
callConnectionTime: "Call Connection Time",
seizureTime: "Call Start Time", seizureTime: "Call Start Time",
releaseTime: "Hangup Time", releaseTime: "Hangup Time",
caller: "Caller", caller: "Caller",
called: "Called", called: "Called",
result: "Result", result: "Result",
resultCode: "Result Code",
resultCause: "Result Cause",
resultOk: "Success", resultOk: "Success",
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.)", tenantName: "Tenant Name",
exportTip: "Do you confirm to export the current query conditions of the CDR data?",
chargingID: 'Charging ID', chargingID: 'Charging ID',
smfSubscriptionIDData: 'Subscription ID Data', smfSubscriptionIDData: 'Subscription ID Data',
smfSubscriptionIDType: 'Subscription ID Type', smfSubscriptionIDType: 'Subscription ID Type',
@@ -444,7 +479,7 @@ export default {
result: "Result", result: "Result",
resultOk: "Successes", resultOk: "Successes",
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 event data of the current query condition? (Maximum 10,000 items can be exported.)", exportTip: "Do you confirm to export the event data of the current query condition?",
}, },
}, },
ne: { ne: {
@@ -467,6 +502,8 @@ export default {
portTip: "Network element port default:33030", portTip: "Network element port default:33030",
serialNum: 'Serial Number', serialNum: 'Serial Number',
expiryDate: 'Expiry Date', expiryDate: 'Expiry Date',
ueNumber: 'UE Number',
nbNumber: 'Radio Number',
normalcy: 'Normal', normalcy: 'Normal',
exceptions: 'Abnormal', exceptions: 'Abnormal',
restart: 'Restart', restart: 'Restart',
@@ -504,7 +541,7 @@ export default {
delTip: 'Confirm deletion of network element information data items?', delTip: 'Confirm deletion of network element information data items?',
oam: { oam: {
title: 'OAM Configuration', title: 'OAM Configuration',
sync: 'Sync to NE', restart: 'Restart NE',
oamEnable: 'Service', oamEnable: 'Service',
oamPort: 'Port', oamPort: 'Port',
snmpEnable: 'Service', snmpEnable: 'Service',
@@ -607,14 +644,15 @@ 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",
change: "Change License", change: "Change License",
reload: "Refresh Info", reload: "Refresh Info",
reloadTip: "Confirmed to refresh license information?", reloadTip: "Confirmed to refresh license information?",
reloadBatch: "Batch Refresh", reloadBatch: "Refresh License Status",
reloadBatchTip: "Do you do an information refresh on checked records?", reloadBatchTip: "Do you perform a license status information refresh for the current list of NE?",
updateTtile: "Update License", updateTtile: "Update License",
downCodeTop: "Confirmed to save the license activation code to a file?", downCodeTop: "Confirmed to save the license activation code to a file?",
activationRequestCode: "License Activation Code", activationRequestCode: "License Activation Code",
@@ -656,6 +694,19 @@ export default {
name: "Name", name: "Name",
downTip: 'Confirmed to download the backup file [{txt}]?', downTip: 'Confirmed to download the backup file [{txt}]?',
title: "Modify Backup {txt}", title: "Modify Backup {txt}",
backupModal: {
pushFileOper: "Send Current File To Remote Backup",
title: "Setting Remote Backup Service",
enable: "Enable",
toIp: "Service IP",
toIpPleace: "Please input the remote backup server IP address",
toPort: "Service Port",
username: "UserName",
usernamePleace: 'Please enter the service login username',
password: "Password",
dir: "Save Dir",
dirPleace: 'Please enter the service address target file directory',
}
}, },
neQuickSetup: { neQuickSetup: {
reloadPara5G: 'Reload', reloadPara5G: 'Reload',
@@ -693,7 +744,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",
@@ -707,10 +758,32 @@ export default {
}, },
}, },
neData: { neData: {
common: {
startIMSI: 'Starting IMSI',
imsi: 'IMSI',
imsiTip: 'IMSI=MCC+MNC+MSIN',
imsiTip1: 'MCC=Mobile Country Code, consisting of three digits.',
imsiTip2: 'MNC = Mobile Network Number, consisting of two digits',
imsiTip3: 'MSIN = Mobile Subscriber Identification Number, consisting of 10 equal digits.',
imsiPlease: "Please enter IMSI correctly",
msisdn: 'Mobile Customer Identification Number',
msisdnPlease: "Please enter the Mobile Customer Identification Number correctly",
loadDataConfirm: 'Confirmed to reload 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!!!!!.',
batchOper: 'Batch Operation',
batchAddText: 'Batch Addition',
batchDelText: 'Batch Deletion',
batchUpdateText: 'Batch Update',
batchNum: 'Number of releases',
checkDel:'Check Delete',
importTemplate: 'Download Template',
},
baseStation: { baseStation: {
list: "List", list: "List",
topology: "Topology", topology: "Topology",
nbName: "RanNodeName", nbName: "RAN Node Name",
nbId: "RAN Node ID",
ueNum: "UE Number", ueNum: "UE Number",
topologyTitle: "Radio State Graph", topologyTitle: "Radio State Graph",
name: "Name", name: "Name",
@@ -729,10 +802,16 @@ export default {
exportTip: "Confirm exporting xlsx table files based on search criteria?", exportTip: "Confirm exporting xlsx table files based on search criteria?",
importDataEmpty: "Imported data is empty", importDataEmpty: "Imported data is empty",
}, },
backupData: {
auth: "UDM Authentication",
sub: "UDM Subscribers",
voip: "VoIP Authentication",
volte: "IMS Subscribers",
}
}, },
neUser: { neUser: {
auth: { auth: {
authInfo:' Authentication Info', authInfo:' Authentication of Info',
neTypePlease: 'Query network element Object', neTypePlease: 'Query network element Object',
neType: 'UDM Object', neType: 'UDM Object',
export: 'Export', export: 'Export',
@@ -747,7 +826,7 @@ export default {
startIMSI: 'Start IMSI', startIMSI: 'Start IMSI',
batchAddText: 'Batch Add', batchAddText: 'Batch Add',
batchDelText: 'Batch Delete', batchDelText: 'Batch Delete',
numAdd: 'Number of releases', numAdd: 'Number of authentication profiles',
numDel: 'Number of deleted', numDel: 'Number of deleted',
checkDel: 'Check Delete', checkDel: 'Check Delete',
imsiTip: 'IMSI=MCC+MNC+MSIN', imsiTip: 'IMSI=MCC+MNC+MSIN',
@@ -760,10 +839,12 @@ export default {
opcTip: 'The authentication key, OPC, is calculated from Ki and OP, OP is the root key of the operator, ki is the authentication key, and the maximum length is 32.', opcTip: 'The authentication key, OPC, is calculated from Ki and OP, OP is the root key of the operator, ki is the authentication key, and the maximum length is 32.',
delSure:'Are you sure you want to delete the user with IMSI number: {imsi} ?', delSure:'Are you sure you want to delete the user with IMSI number: {imsi} ?',
imsiConfirm:'The length of the IMSI must be 15', imsiConfirm:'The length of the IMSI must be 15',
startUserName:'Start username',
}, },
sub: { sub: {
subInfo:' Subscription Info', subInfo:' Subscription Info',
neType: 'UDM Object', neType: 'UDM Object',
imsType:'IMS Object',
export: 'Export', export: 'Export',
exportConfirm: 'Are you sure to export all signed user data?', exportConfirm: 'Are you sure to export all signed user data?',
checkExport : 'Check Export', checkExport : 'Check Export',
@@ -773,7 +854,7 @@ export default {
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!!!!!.',
numAdd: 'Number of releases', numAdd: 'Number of subscriber profiles',
numDel: 'Number of deleted', numDel: 'Number of deleted',
checkDel: 'Check Delete', checkDel: 'Check Delete',
batchAddText: 'Batch Add', batchAddText: 'Batch Add',
@@ -793,6 +874,10 @@ export default {
rfspTip:'RFSP index, in NG-RAN, the index of a specific RRM configuration, parameter between 0 and 127', rfspTip:'RFSP index, in NG-RAN, the index of a specific RRM configuration, parameter between 0 and 127',
ueTypeTip: 'Operator-defined subscriber UE Usage Type, integer, parameter between 0 and 127', ueTypeTip: 'Operator-defined subscriber UE Usage Type, integer, parameter between 0 and 127',
cnFlag: 'Whether to enable 5G Core Network service', cnFlag: 'Whether to enable 5G Core Network service',
cnFlag0: 'No Access Allowed',
cnFlag1: 'Access Only 5G',
cnFlag2: 'Access Only 4G',
cnFlag3: 'Access 4G/5G',
epsFlagTip: 'Whether to enable 4G EPS service', epsFlagTip: 'Whether to enable 4G EPS service',
contextIdTip: 'To sign up for an APN Context ID, you must select it from the APN Context list.', contextIdTip: 'To sign up for an APN Context ID, you must select it from the APN Context list.',
apnContextTip: 'The list of APNs available to the phone, up to six, is defined in the HSS.', apnContextTip: 'The list of APNs available to the phone, up to six, is defined in the HSS.',
@@ -802,6 +887,12 @@ export default {
ardTip:'Access-Restriction-Data (Access-Restriction-Data), can be used to distinguish between 2G/3G/LTE users, to facilitate the coexistence of 2G/3G/LTE network for different types of users to distinguish between the service', ardTip:'Access-Restriction-Data (Access-Restriction-Data), can be used to distinguish between 2G/3G/LTE users, to facilitate the coexistence of 2G/3G/LTE network for different types of users to distinguish between the service',
smDataTip:'The IP in sm_data=1-000001&internet-1.2.3.4&ims-1.2.3.5: 1.2.3.4 is the static IP assigned to the APN of 5G user internet, and 1.2.3.5 is the static IP assigned to the APN of 5G user ims. If it is dynamic allocation, just remove the IP and the previous connector. Need to support multiple dnn uses & connections', smDataTip:'The IP in sm_data=1-000001&internet-1.2.3.4&ims-1.2.3.5: 1.2.3.4 is the static IP assigned to the APN of 5G user internet, and 1.2.3.5 is the static IP assigned to the APN of 5G user ims. If it is dynamic allocation, just remove the IP and the previous connector. Need to support multiple dnn uses & connections',
smDataArrTip:'SST,DNN/APN is required', smDataArrTip:'SST,DNN/APN is required',
tenantName:'Tenant Name',
imsiMode:'IMSI Matching Mode',
fuzzyMatch:'Fuzzy Match',
prefixMatch:'Prefix Match',
fullMatch:'Full Match',
suffixMatch:'Suffix Match',
}, },
pcf: { pcf: {
neType: 'PCF Object', neType: 'PCF Object',
@@ -835,7 +926,9 @@ export default {
rfsfTip:'RAT Frequency Selection Priority', rfsfTip:'RAT Frequency Selection Priority',
}, },
base5G: { base5G: {
neType: 'NE Object', neType: 'Radio Type',
gnb:'5G Radios',
enb:'4G Radios',
}, },
n3iwf: { n3iwf: {
neType: 'N3IWF Object', neType: 'N3IWF Object',
@@ -947,8 +1040,13 @@ export default {
expression:'Expression', expression:'Expression',
description:' Description', description:' Description',
kpiSet:' Statistical Settings', kpiSet:' Statistical Settings',
sixHoursAgo:'Six hours ago', ago1Hour:'Last 1 hour',
threeHoursAgo:'Three hours ago.', ago3Hour:'Last 3 hour',
ago6Hour:'Last 6 hour',
toDay:'Today',
ago1Day:'Last 1 day',
ago7Day:'Last 7 day',
ago15Day:'Last 15 day',
delCustomTip:'Confirm deletion of data item with record Custom Indicator {num}?', delCustomTip:'Confirm deletion of data item with record Custom Indicator {num}?',
delCustom:' Successfully delete record number {num} custom indicator', delCustom:' Successfully delete record number {num} custom indicator',
addCustom:' Add custom indicator', addCustom:' Add custom indicator',
@@ -964,6 +1062,12 @@ 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:',
avg:'(average)',
total:'(total)',
ago1:'Past 24 hrs value',
ago7:'Past 7 days value',
ago30:'Past 30 days value',
}, },
kpiKeyTarget:{ kpiKeyTarget:{
"time":"Time", "time":"Time",
@@ -995,28 +1099,25 @@ export default {
"changeBar":"Change to Bar Charts", "changeBar":"Change to Bar Charts",
"chooseShowMetrics":"Select the metric you want to display", "chooseShowMetrics":"Select the metric you want to display",
"chooseMetrics":"Select an indicator", "chooseMetrics":"Select an indicator",
"tips":"The percentages and rates are averages,the counts are statistical values.",
},
voiceOverView:{
"voiceTitle":"Voice Calls Dashboard",
"tips":"Voice Call Statistics Per Minute",
"ne":"NE",
"now":"Now",
"last":"Past",
"calls":"Calls",
"activeCall":"Active Calls",
"callMOMT":"MO/MT Call Success Rate",
"failedcall":"Failed Calls",
"registration":"Registrations",
"activeregistration":"Active Registrations",
"registrationsuccess":"Registration Success Rate",
"failedregistration":"Failed Registrations",
}, },
}, },
traceManage: { traceManage: {
analysis: {
imsi: 'IMSI',
imsiPlease: 'Please enter IMSI',
msisdn: 'MSISDN',
msisdnPlease: 'Please enter MSISDN',
trackTaskId: 'Task ID',
srcIp: 'Source IP Address',
dstIp: 'Destination IP Address',
signalType: 'Signaling Type',
msgDirect: 'Message Direction',
msgType: 'Message Type',
rowTime: 'Record Time',
signalData: 'Signaling Data',
signalDetail: 'Signaling Details',
noData: 'No information content',
taskTitle: 'Task {num}',
taskDownText: 'Download HTML',
taskDownTip: 'Confirm downloading the signaling details HTML file?',
},
pcap: { pcap: {
capArgPlease: 'Please enter tcpdump -i any support parameter', capArgPlease: 'Please enter tcpdump -i any support parameter',
cmd: 'Command', cmd: 'Command',
@@ -1051,6 +1152,7 @@ export default {
}, },
task: { task: {
traceId: 'Tracing No', traceId: 'Tracing No',
title: 'Tracing Title',
trackType: 'Tracing Type', trackType: 'Tracing Type',
trackTypePlease: 'Please select a tracing type', trackTypePlease: 'Please select a tracing type',
creater: 'Created by', creater: 'Created by',
@@ -1067,30 +1169,35 @@ export default {
imsiTip: 'Mobile communication IMSI number', imsiTip: 'Mobile communication IMSI number',
srcIp: 'Source IP Address', srcIp: 'Source IP Address',
srcIpPlease: 'Please enter the IP address', srcIpPlease: 'Please enter the IP address',
srcIpTip: 'Current sender IPv4 address', srcIpTip: 'sending IPv4 address',
dstIp: 'Destination IP Address', dstIp: 'Destination IP Address',
dstIpPlease: 'Please enter the IP address', dstIpPlease: 'Please enter the IP address',
dstIpTip: 'IPv4 address of the receiving end of the other party', dstIpTip: 'receiving end IPv4 address',
interfaces: 'Signaling Interface', interfaces: 'Signaling Interface',
interfacesPlease: 'Please enter the signaling interface', interfacesPlease: 'Please enter the signaling interface',
signalPort: 'Signal Port', rangePicker: 'Task Time',
signalPortPlease: 'Please enter the signaling port',
signalPortTip: 'Port of the side corresponding to the destination IP address or source IP address',
rangePicker: 'Start/End Time',
rangePickerPlease: 'Please select the start and end time of the task', rangePickerPlease: 'Please select the start and end time of the task',
remark: 'Remark', remark: 'Remark',
remarkPlease: 'Task description can be entered', remarkPlease: 'Task description can be entered',
addTask: 'Add Task', addTask: 'Add Task',
editTask: 'Modify Task',
viewTask: 'View Task', viewTask: 'View Task',
errorTaskInfo: 'Failed to obtain task information', errorTaskInfo: 'Failed to obtain task information',
delTaskTip: 'Are you sure to delete the data item with record ID {id} ?', delTaskTip: 'Are you sure to delete the data item with record ID {id} ?',
stopTask: 'Successful cessation of tasks {id}', stopTask: 'Successful cessation of tasks {id}',
stopTaskTip: 'Confirm stopping the task with record ID {id} ?', stopTaskTip: 'Confirm stopping the task with record ID {id} ?',
pcapView: "Tracking Data Analysis", pcapView: "Track Data Analysis",
traceFile: "Tracking File", traceFile: "Track File",
errMsg: "Error Message", errMsg: "Error Message",
imsiORmsisdn: "imsi or msisdn is null, cannot start task", imsiORmsisdn: "imsi or msisdn is null, cannot start task",
dataView: "Track Data",
protocolOrInterface: "Protocol/Interface",
msgNe: 'Network Element',
msgEvent: 'Event',
msgType: 'Type',
msgDirect: 'Direction',
msgLen: 'Length',
rowTime: 'Time',
taskInfo: 'Task information',
}, },
}, },
faultManage: { faultManage: {
@@ -1110,7 +1217,7 @@ export default {
clear: 'Clear', clear: 'Clear',
mySelf: 'Personalization', mySelf: 'Personalization',
exportAll: 'Export All', exportAll: 'Export All',
disPlayFilfter: 'Display Filters', disPlayFilfter: 'Filter out Alerts',
alarmId:'ID', alarmId:'ID',
alarmTitle:'Title', alarmTitle:'Title',
clearUser:'Clear User', clearUser:'Clear User',
@@ -1200,22 +1307,30 @@ 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',
}, },
exportFile:{ exportFile:{
fileName:'File Source', fileSource:'File Source',
fileSourcePlease:'Please select the source of the document',
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",
deleteTip: "Confirm the delete file name is [{fileName}] File?", deleteTip: "Confirm the delete file name is [{fileName}] File?",
deleteTipErr: "Failed to delete file", deleteTipErr: "Failed to delete file",
selectTip:"Please select File Name", sysloginLog:'System Login Log',
sysOperateLog:'System Operation Log',
neLog:'NE Log',
cdrIMS:'CDR Voice',
cdrSMF:'CDR Data',
cdrSMSC:'CDR SMS',
cdrSGWC:'CDR Roaming Data',
} }
}, },
monitor: { monitor: {
@@ -1589,6 +1704,8 @@ export default {
userName: 'Nick Name', userName: 'Nick Name',
permission: 'Role', permission: 'Role',
className: 'Department', className: 'Department',
userType: 'User Type',
tenntName:'Tenant Name',
loginIp: 'Login Address', loginIp: 'Login Address',
loginTime: 'Login Time', loginTime: 'Login Time',
status: 'Status', status: 'Status',
@@ -1614,6 +1731,7 @@ export default {
sex:'User Gender', sex:'User Gender',
email:'E-mail', email:'E-mail',
fromClass:'Department', fromClass:'Department',
fromTenant:'Tenant',
userWork:'User position', userWork:'User position',
userWorkPlease: 'Please select user post', userWorkPlease: 'Please select user post',
userTip:'User Description', userTip:'User Description',
@@ -1627,6 +1745,7 @@ export default {
lock:'Lock', lock:'Lock',
inactive:'Inactive', inactive:'Inactive',
active:'Active', active:'Active',
permissionTip:'If want to select a tenant role, make sure there is at least one tenant in the tenant management.',
}, },
config: { config: {
configName: "Config Name", configName: "Config Name",
@@ -1651,6 +1770,55 @@ export default {
refreshCacheTip: "Are you sure you want to refresh the parameter configuration cache?", refreshCacheTip: "Are you sure you want to refresh the parameter configuration cache?",
refreshCacheOk: "Refresh Cache Successful", refreshCacheOk: "Refresh Cache Successful",
}, },
loginSource: {
uid: "UID",
name: "Name",
namePlease: 'Please enter the authentication source name correctly',
icon: "Icon",
iconPlease: 'You can enter the image link or upload the image path address',
type: "Type",
activeFlag: "Status",
remark: "Remark",
createTime: "Create Time",
updateTime: "Update Time",
ldapUrl: "Server Address",
ldapUrlPlease: 'Please enter the LDAP server address correctly',
ldapBaseDN: "Base DN",
baseDnPlease: 'Please enter the LDAP base DN correctly',
ldapUserFilter: "User Filter",
userFilterPlease: 'Please enter the LDAP user filter correctly',
ldapBindDN: "Bind DN",
ldapBindPassword: "Bind Password",
smtpHost: 'Server Address',
smtpHostPlease: 'Please enter the SMTP server address correctly',
smtpPort: 'Port Number',
smtpPortPlease: 'Please enter the SMTP port number correctly',
oauth2ClientID: 'Client ID',
oauth2ClientIDPlease: 'Please enter the OAuth2 client ID correctly',
oauth2ClientSecret: 'Client Secret',
oauth2ClientSecretPlease: 'Please enter the OAuth2 client secret correctly',
oauth2AuthURL: 'Authorization URL',
oauth2AuthURLPlease: 'Please enter the OAuth2 authorization URL correctly',
oauth2TokenURL: 'Token URL',
oauth2TokenURLPlease: 'Please enter the OAuth2 token URL correctly',
oauth2UserURL: 'User Info URL',
oauth2UserURLPlease: 'Please enter the OAuth2 user info URL correctly',
oauth2AccountField: 'Account Field',
oauth2AccountFieldPlease: 'Please enter the OAuth2 account field correctly',
oauth2Scopes: 'Scopes',
oauth2ScopesPlease: 'Please enter the OAuth2 scopes correctly',
oauth2RedirectURL: 'Redirect URL',
oauth2RedirectURLPlease: 'Please enter the OAuth2 redirect URL correctly',
oauth2RedirectURLTip: 'Please jump to the specified path (omchost/#/login/oauth2), redirect with code and state address parameters',
uploadFileTip: 'Confirm to upload the authentication source icon?',
uploadFileOk: 'Authentication source icon upload successful',
uploadFileErr: 'Authentication source icon upload failed',
viewInfoErr: "Failed to get authentication source information",
addInfo: "Add Authentication Source",
editInfo: "Modify Authentication Source",
delTip: "Confirm deleting the authentication source number [{num}] data item?",
delOk: "Deleted Successfully",
},
setting: { setting: {
charMaxLen: 'characters length', charMaxLen: 'characters length',
saveSubmit: 'Submit&Save', saveSubmit: 'Submit&Save',
@@ -1710,6 +1878,10 @@ export default {
home: 'Home Page', home: 'Home Page',
homeTip:'Do you want to submit the current interface as the system interface?', homeTip:'Do you want to submit the current interface as the system interface?',
homeSet:'Home Page Settings', homeSet:'Home Page Settings',
backup: 'System Backup',
backupInstruction: 'System backup will back up the net element information records and configuration files running on the current system, and can restore the system to the previous state!',
backupExportTip: 'Confirm to export system backup?',
backupImportTip: 'Confirm to import system backup?',
}, },
role:{ role:{
allScopeOptions:'All data permissions', allScopeOptions:'All data permissions',
@@ -1754,6 +1926,7 @@ export default {
cancelGive:'Cancel authorization', cancelGive:'Cancel authorization',
cancelSure:'Confirm to cancel the authorization of the data item with user number [{userId}]?', cancelSure:'Confirm to cancel the authorization of the data item with user number [{userId}]?',
batchCancel:'Batch cancellation of authorization', batchCancel:'Batch cancellation of authorization',
selectUser:'Assign Users',
}, },
dept:{ dept:{
classInfo:' Department Information', classInfo:' Department Information',
@@ -1774,6 +1947,37 @@ export default {
phone:'Contact Number', phone:'Contact Number',
email:'Mail', email:'Mail',
}, },
tenant:{
classInfo:' Tenant Information',
className:'Name',
classId:'Number',
classSort:'Sorting',
status:'Status',
type:' Tenancy Asset',
createTime:'Creation Time',
highClass:'Root Level',
key:'Asset Key',
emailTip:'Please input the correct email address',
phoneTip:'Please enter the correct phone number',
node:'Root Node',
delSure:'Are you sure to delete the data item with TenantName: {title}?',
delTypeSure:'Are you sure to delete the data item with Tenancy Asset: {tenancyAsset}, Asset Key: {tenancyKey}?',
open:'Exhibition',
close:'Fold',
addClass:'Add Tenant',
admin:'Principal',
phone:'Contact Number',
email:'Mail',
SDSST:'Network Slice',
APN:'Access Point Name',
IMSI:'SIM Card',
treeSelectTip:'Please select a tenant from the tenant list on the left for configuration',
upfTip:'Please select from the drop-down list of UPF Resource UIDs',
imsiTip:'Please select the matching type and fill in the SIM card number fragment',
radioTip:'Please select from the drop-down list of radio type and radio ID',
defaultTip:'Please select from the drop-down list of Tenancy Asset, and then fill the Asset Key',
patternTip:'The Asset Key cannot contain special characters',
},
post:{ post:{
positionInfo:'Position Information', positionInfo:'Position Information',
positionId:'Position Number', positionId:'Position Number',
@@ -1796,7 +2000,7 @@ export default {
requestMe:'Request Method', requestMe:'Request Method',
host:'Request Host', host:'Request Host',
operStatus:'Status', operStatus:'Status',
operDate:'Time', operDate:'Time Stamp',
useTime:'Time Lap', useTime:'Time Lap',
logInfo:'Operation Log Information', logInfo:'Operation Log Information',
delSure:'Are you sure to delete the data item with access number [{ids}]?', delSure:'Are you sure to delete the data item with access number [{ids}]?',

View File

@@ -10,13 +10,14 @@ export default {
desc: '核心网管理平台', desc: '核心网管理平台',
loading: '请稍等...', loading: '请稍等...',
inputPlease: '请输入', inputPlease: '请输入',
searchPlease: '搜索菜单...',
searchTip: '输入关键词搜索菜单',
selectPlease: '请选择', selectPlease: '请选择',
tipTitle: '提示', tipTitle: '提示',
msgSuccess: '{msg} 成功', msgSuccess: '{msg} 成功',
errorFields: '请正确填写 {num} 处必填信息!', errorFields: '请正确填写 {num} 处必填信息!',
tablePaginationTotal: '总共 {total} 条', tablePaginationTotal: '总共 {total} 条',
noData: "暂无数据", noData: "暂无数据",
zebra:'表格斑马纹',
ok: '确定', ok: '确定',
cancel: '取消', cancel: '取消',
close: '关闭', close: '关闭',
@@ -29,6 +30,7 @@ export default {
editText: '编辑', editText: '编辑',
deleteText: '删除', deleteText: '删除',
downloadText: '下载', downloadText: '下载',
CloudServerText:'同步',
import:'导入', import:'导入',
export:'导出', export:'导出',
uploadText: '上传', uploadText: '上传',
@@ -39,6 +41,10 @@ export default {
columnSetText: '列设置', columnSetText: '列设置',
columnSetTitle: '列展示/排序', columnSetTitle: '列展示/排序',
sizeText: '密度', sizeText: '密度',
exportCustom:'自定义导出',
exportColumns:'列定义',
resetToDefault:'重置为默认列',
exportDefault:'全部导出',
size: { size: {
default: '默认', default: '默认',
middle: '中等', middle: '中等',
@@ -131,7 +137,7 @@ export default {
}, },
LockScreen: { LockScreen: {
inputPlacePwd:'请输入锁屏密码', inputPlacePwd:'请输入锁屏密码',
validSucc:'校验通过', enter:'进入',
validError:'校验失败', validError:'校验失败',
backLogin:'退出并重新登录', backLogin:'退出并重新登录',
backReload:'正在重启,请稍等...', backReload:'正在重启,请稍等...',
@@ -143,7 +149,7 @@ export default {
// 静态路由 // 静态路由
router: { router: {
index: "主页", index: "概览",
login: "登录", login: "登录",
register: '注册', register: '注册',
page403: '没有访问权限', page403: '没有访问权限',
@@ -257,9 +263,13 @@ export default {
registerBtn: '注册账号', registerBtn: '注册账号',
loginBtn: '登录', loginBtn: '登录',
loginSuccess: '登录成功', loginSuccess: '登录成功',
loginMethod: '其他登录方式', otherMethod: '其他方式',
loginMethodWX: '微信扫一扫登录', backBtn: '返回',
loginMethodQQ: 'QQ扫码登录', backBtnLogin: '返回登录',
authorizedNotfound: '授权无效',
authorizedFailed: '授权失败',
authorizedSuccess: '授权成功',
redirectHome: '{i} 秒后跳转主页',
}, },
register: { register: {
registerBtn: '注册', registerBtn: '注册',
@@ -355,16 +365,34 @@ export default {
title: "核心网系统看板", title: "核心网系统看板",
fullscreen: "点击全屏显示", fullscreen: "点击全屏显示",
toRouter: "点击跳转详情页面", toRouter: "点击跳转详情页面",
Users:"UDM 订阅数据",
VoNR:"IMS在线用户",
sessions:"SMF的PDU会话",
FivegNodeN:"5G在线基站数",
Fiveusers:"AMF在线用户数",
FourgNodeN:"4G在线基站数",
Fourusers:"MME在线用户数",
UPFjump:"点击跳转到UPF黄金指标界面",
Networkjump:"点击跳转到网元系统拓扑图",
Alarmjump:"点击跳转到活动告警",
IMSUsers:"点击跳转到 IMS 用户",
VoIPUsers:"点击跳转到 VoIP 用户",
skim: { skim: {
users: "用户数", users: "用户数",
userTitle:'用户信息', userTitle:'用户信息',
imsUeNum: "IMS 会话数", imsUeNum: "IMS 会话数",
smfUeNum: "Data 会话数", smfUeNum: "Data 会话数",
gnbBase: "5G 基站数", gnbBase: "5G 基站数",
gnbSumBase: "5G 基站总数",
gnbUeNum:'5G 用户数', gnbUeNum:'5G 用户数',
enbBase: "4G 基站数", enbBase: "4G 基站数",
enbSumBase: "4G 基站总数",
enbUeNum:'4G 用户数', enbUeNum:'4G 用户数',
baseTitle:'在线信息', baseTitle:'在线信息',
nodeBInfo: '基站信息',
onlineinfo:'User Online Infomation',
ims:'IMS',
voip:'VoIP'
}, },
upfFlow:{ upfFlow:{
title: "用户面吞吐量", title: "用户面吞吐量",
@@ -394,6 +422,7 @@ export default {
}, },
userActivity: { userActivity: {
title: "用户活动", title: "用户活动",
imsTitle: "IMS 活动",
type: "类型", type: "类型",
duration: "时长", duration: "时长",
caller: "主叫", caller: "主叫",
@@ -412,15 +441,20 @@ export default {
rowInfo: "记录信息", rowInfo: "记录信息",
type: "记录类型", type: "记录类型",
duration: "通话时长", duration: "通话时长",
mosAverage: "MOS",
callConnectionTime: "Call Connection Time",
seizureTime: "呼叫开始时间", seizureTime: "呼叫开始时间",
releaseTime: "挂断结束时间", releaseTime: "挂断结束时间",
caller: "主叫", caller: "主叫",
called: "被叫", called: "被叫",
result: "结果", result: "结果",
resultCode: "结果码",
resultCause: "结果原因",
resultOk: "成功", resultOk: "成功",
resultFail: "失败", resultFail: "失败",
delTip: "确认删除编号为【{msg}】的数据项?", delTip: "确认删除编号为【{msg}】的数据项?",
exportTip: "确认导出当前查询条件的话单数据吗?(导出最大支持一万条)", tenantName: "租户名称",
exportTip: "确认导出当前查询条件的话单数据吗?",
chargingID: '计费ID', chargingID: '计费ID',
smfSubscriptionIDData: '订阅 ID 数据', smfSubscriptionIDData: '订阅 ID 数据',
smfSubscriptionIDType: '订阅 ID 类型', smfSubscriptionIDType: '订阅 ID 类型',
@@ -444,7 +478,7 @@ export default {
result: "结果", result: "结果",
resultOk: "成功", resultOk: "成功",
delTip: "确认删除编号为【{msg}】的数据项?", delTip: "确认删除编号为【{msg}】的数据项?",
exportTip: "确认导出当前查询条件的事件数据吗?(导出最大支持一万条)", exportTip: "确认导出当前查询条件的事件数据吗?",
}, },
}, },
ne: { ne: {
@@ -467,6 +501,8 @@ export default {
portTip: "网元服务端口,默认:33030", portTip: "网元服务端口,默认:33030",
serialNum: '序列号', serialNum: '序列号',
expiryDate: '许可证到期日期', expiryDate: '许可证到期日期',
ueNumber: '用户数',
nbNumber: '基站数',
normalcy: '正常', normalcy: '正常',
exceptions: '异常', exceptions: '异常',
restart: '重启', restart: '重启',
@@ -504,7 +540,7 @@ export default {
delTip: '确认删除网元信息数据项吗?', delTip: '确认删除网元信息数据项吗?',
oam: { oam: {
title: 'OAM配置', title: 'OAM配置',
sync: '同步到网元', restart: '下发后重启网元',
oamEnable: '服务', oamEnable: '服务',
oamPort: '端口', oamPort: '端口',
snmpEnable: '服务', snmpEnable: '服务',
@@ -607,14 +643,15 @@ export default {
upgradeDone: '更新完成,服务正在重载', upgradeDone: '更新完成,服务正在重载',
upgradeFail: '更新失败,请检查软件文件是否存在且服务终端环境是否可用!', upgradeFail: '更新失败,请检查软件文件是否存在且服务终端环境是否可用!',
upgradeModal: '网元版本更新', upgradeModal: '网元版本更新',
noPath: '软件包文件未发现',
}, },
neLicense: { neLicense: {
status: "许可证状态", status: "许可证状态",
change: "变更许可证", change: "变更许可证",
reload: "刷新信息", reload: "刷新信息",
reloadTip: "确认要刷新许可证信息吗?", reloadTip: "确认要刷新许可证信息吗?",
reloadBatch: "批量刷新", reloadBatch: "刷新许可证状态",
reloadBatchTip: "对勾选的记录进行信息刷新吗?", reloadBatchTip: "对当前列表网元进行许可证状态信息刷新吗?",
updateTtile: "更新许可证", updateTtile: "更新许可证",
downCodeTop: "确认要将许可激活码保存到文件吗?", downCodeTop: "确认要将许可激活码保存到文件吗?",
activationRequestCode: "许可激活码", activationRequestCode: "许可激活码",
@@ -656,6 +693,19 @@ export default {
name: "名称", name: "名称",
downTip: '确认要下载备份文件【{txt}】吗?', downTip: '确认要下载备份文件【{txt}】吗?',
title: "修改备份信息 {txt}", title: "修改备份信息 {txt}",
backupModal: {
pushFileOper: "将当前文件发送到远程备份",
title: "设置远程备份服务",
enable: "启用",
toIp: "服务IP",
toIpPleace: "请输入远程备份服务器 IP 地址",
toPort: "服务端口",
username: "登录用户名",
usernamePleace: '请输入服务登录用户名',
password: "登录密码",
dir: "保存目录",
dirPleace: '请输入服务地址目标文件目录',
}
}, },
neQuickSetup: { neQuickSetup: {
reloadPara5G: '刷新', reloadPara5G: '刷新',
@@ -707,10 +757,32 @@ export default {
}, },
}, },
neData: { neData: {
common: {
startIMSI: '起始IMSI',
imsi: 'IMSI',
imsiTip: 'IMSI=MCC+MNC+MSIN',
imsiTip1: 'MCC=移动国家号码, 由三位数字组成',
imsiTip2: 'MNC=移动网络号,由两位数字组成',
imsiTip3: 'MSIN=移动客户识别码采用等长10位数字构成',
imsiPlease: "请正确输入IMSI",
msisdn: '移动客户识别码',
msisdnPlease: "请正确输入移动客户识别码",
loadDataConfirm: '确认要重新加载数据吗?',
loadData: '加载数据',
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!',
batchOper: '批量操作',
batchAddText: '批量新增',
batchDelText: '批量删除',
batchUpdateText: '批量更新',
batchNum: '批量个数',
checkDel:'勾选删除',
importTemplate: '导入模板',
},
baseStation: { baseStation: {
list: "列表", list: "列表",
topology: "拓扑图", topology: "拓扑图",
nbName: "设备名称", nbName: "设备名称",
nbId: "设备ID",
ueNum: "在线用户数", ueNum: "在线用户数",
topologyTitle: "基站状态关系图", topologyTitle: "基站状态关系图",
name: "基站名称", name: "基站名称",
@@ -729,6 +801,12 @@ export default {
exportTip: "确认根据搜索条件导出xlsx表格文件吗?", exportTip: "确认根据搜索条件导出xlsx表格文件吗?",
importDataEmpty: "导入数据为空", importDataEmpty: "导入数据为空",
}, },
backupData: {
auth: "UDM鉴权用户",
sub: "UDM签约用户",
voip: "VOIP鉴权用户",
volte: "IMS签约用户",
}
}, },
neUser: { neUser: {
auth: { auth: {
@@ -760,10 +838,12 @@ export default {
opcTip: '鉴权秘钥OPC是由Ki和OP经过计算得来的OP为运营商的根秘钥ki是鉴权秘钥,最大长度为32', opcTip: '鉴权秘钥OPC是由Ki和OP经过计算得来的OP为运营商的根秘钥ki是鉴权秘钥,最大长度为32',
delSure:'确认删除IMSI编号为: {imsi} 的用户吗?', delSure:'确认删除IMSI编号为: {imsi} 的用户吗?',
imsiConfirm:'IMSI的长度必须为15', imsiConfirm:'IMSI的长度必须为15',
startUserName:'起始用户名',
}, },
sub: { sub: {
subInfo:'签约信息', subInfo:'签约信息',
neType: 'UDM网元类型', neType: 'UDM网元类型',
imsType:'IMS网元类型',
export: '导出', export: '导出',
exportConfirm: '确认导出全部签约用户数据吗?', exportConfirm: '确认导出全部签约用户数据吗?',
checkExport : '勾选导出', checkExport : '勾选导出',
@@ -793,6 +873,10 @@ export default {
rfspTip:'RFSP 索引,在 NG-RAN 中,特定 RRM 配置的索引,参数介于0到127之间', rfspTip:'RFSP 索引,在 NG-RAN 中,特定 RRM 配置的索引,参数介于0到127之间',
ueTypeTip: '运营商定义的用户 UE Usage Type整型参数介于0到127之间', ueTypeTip: '运营商定义的用户 UE Usage Type整型参数介于0到127之间',
cnFlag: '是否开启 5G Core Network 服务', cnFlag: '是否开启 5G Core Network 服务',
cnFlag0: '不允许接入',
cnFlag1: '只能接入 5G',
cnFlag2: '只能接入 4G',
cnFlag3: '允许接入 4G/5G',
epsFlagTip: '是否开启 4G EPS 服务', epsFlagTip: '是否开启 4G EPS 服务',
contextIdTip: '签约APN 上下文ID必须从APN Context list 中选择。', contextIdTip: '签约APN 上下文ID必须从APN Context list 中选择。',
apnContextTip: '手机可用的APN列表最多六个在HSS中定义。', apnContextTip: '手机可用的APN列表最多六个在HSS中定义。',
@@ -802,6 +886,12 @@ export default {
ardTip:'接入控制标志(Access-Restriction-Data),可用于区分2G/3G/LTE用户,便于为2G/3G/LTE网络共存时,对不同类型用户进行区分服务', ardTip:'接入控制标志(Access-Restriction-Data),可用于区分2G/3G/LTE用户,便于为2G/3G/LTE网络共存时,对不同类型用户进行区分服务',
smDataTip:'sm_data=1-000001&internet-1.2.3.4&ims-1.2.3.5中的IP1.2.3.4为5G用户internet这个APN分配的静态IP1.2.3.5为5G用户ims这个APN分配的静态IP。如果是动态分配把IP以及前面一个连接符去掉即可。需支持多个dnn用&连接', smDataTip:'sm_data=1-000001&internet-1.2.3.4&ims-1.2.3.5中的IP1.2.3.4为5G用户internet这个APN分配的静态IP1.2.3.5为5G用户ims这个APN分配的静态IP。如果是动态分配把IP以及前面一个连接符去掉即可。需支持多个dnn用&连接',
smDataArrTip:'SST,DNN/APN为必填项', smDataArrTip:'SST,DNN/APN为必填项',
tenantName:'租户名称',
imsiMode:'IMSI匹配模式',
fuzzyMatch:'模糊匹配',
prefixMatch:'前缀匹配',
fullMatch:'全匹配',
suffixMatch:'后缀匹配',
}, },
pcf: { pcf: {
neType: 'PCF网元对象', neType: 'PCF网元对象',
@@ -836,6 +926,8 @@ export default {
}, },
base5G: { base5G: {
neType: '网元对象', neType: '网元对象',
gnb:'5G 基站',
enb:'4G 基站',
}, },
n3iwf: { n3iwf: {
neType: 'N3IWF网元对象', neType: 'N3IWF网元对象',
@@ -848,7 +940,7 @@ export default {
}, },
nssf:{ nssf:{
neType: 'NSSF网元对象', neType: 'NSSF网元对象',
}, }
}, },
perfManage: { perfManage: {
taskManage:{ taskManage:{
@@ -947,8 +1039,13 @@ export default {
expression:'计算公式', expression:'计算公式',
description:'描述', description:'描述',
kpiSet:'统计设置', kpiSet:'统计设置',
sixHoursAgo:'6小时', ago1Hour:'过去1小时',
threeHoursAgo:'3小时', ago3Hour:'过去3小时',
ago6Hour:'过去6小时',
toDay:'今天',
ago1Day:'过去1天',
ago7Day:'过去7天',
ago15Day:'过去15天',
delCustomTip:'确认删除自定义指标项为 {num} 的数据项?', delCustomTip:'确认删除自定义指标项为 {num} 的数据项?',
delCustom:'成功删除记录编号为 {num} 自定义指标', delCustom:'成功删除记录编号为 {num} 自定义指标',
addCustom:'添加自定义指标', addCustom:'添加自定义指标',
@@ -964,6 +1061,12 @@ export default {
expressionModal:'表达式模块', expressionModal:'表达式模块',
expressionErrorTip:'请检查表达式,错误的指标为{kpiId}', expressionErrorTip:'请检查表达式,错误的指标为{kpiId}',
expressionNoIdTip:'请检查表达式,没有找到任何有效的指标', expressionNoIdTip:'请检查表达式,没有找到任何有效的指标',
unitSelect:'为更好展示图需选择相同单位,当前单位为:',
avg:'(平均)',
total:'(累计)',
ago1:'近1天值',
ago7:'近7天值',
ago30:'近30天值',
}, },
kpiKeyTarget:{ kpiKeyTarget:{
"time":"时间", "time":"时间",
@@ -995,28 +1098,25 @@ export default {
"changeBar":"切换为柱状图", "changeBar":"切换为柱状图",
"chooseShowMetrics":"选择需要显示的指标", "chooseShowMetrics":"选择需要显示的指标",
"chooseMetrics":"选择指标", "chooseMetrics":"选择指标",
"tips":"百分比和比率是平均值,计数是统计值。",
},
voiceOverView:{
"voiceTitle":"语音通话仪表盘",
"tips":"每分钟数据语音统计",
"ne":"网元",
"now":"现在",
"last":"过去",
"calls":"呼叫",
"activeCall":"正在通话",
"callMOMT":"呼叫 主叫接通率/被叫接通率",
"failedcall":"失败呼叫",
"registration":"注册",
"activeregistration":"主动注册",
"registrationsuccess":"注册成功率",
"failedregistration":"失败注册",
}, },
}, },
traceManage: { traceManage: {
analysis: {
imsi: 'IMSI',
imsiPlease: '请输入IMSI',
msisdn: 'MSISDN',
msisdnPlease: '请输入MSISDN',
trackTaskId: '跟踪任务标记',
srcIp: '源IP地址',
dstIp: '目标IP地址',
signalType: '信令类型',
msgDirect: '消息元',
msgType: '消息类型',
rowTime: '记录时间',
signalData: '信令数据',
signalDetail: '信令详情',
noData: '无信息内容',
taskTitle: '任务 {num}',
taskDownText: '下载HTML',
taskDownTip: '确认下载信令详情HTML文件?',
},
pcap: { pcap: {
capArgPlease: '请输入tcpdump -i any支持参数', capArgPlease: '请输入tcpdump -i any支持参数',
cmd: '命令', cmd: '命令',
@@ -1051,6 +1151,7 @@ export default {
}, },
task: { task: {
traceId: '跟踪编号', traceId: '跟踪编号',
title: '跟踪标题',
trackType: '跟踪类型', trackType: '跟踪类型',
trackTypePlease: '请选择跟踪类型', trackTypePlease: '请选择跟踪类型',
creater: '创建人', creater: '创建人',
@@ -1067,21 +1168,17 @@ export default {
imsiTip: '移动通信IMSI编号', imsiTip: '移动通信IMSI编号',
srcIp: '源IP地址', srcIp: '源IP地址',
srcIpPlease: '请输入源IP地址', srcIpPlease: '请输入源IP地址',
srcIpTip: '当前发送端IPv4地址', srcIpTip: '发送端IPv4地址',
dstIp: '目标IP地址', dstIp: '目标IP地址',
dstIpPlease: '请输入目标IP地址', dstIpPlease: '请输入目标IP地址',
dstIpTip: '对方接收端IPv4地址', dstIpTip: '接收端IPv4地址',
interfaces: '信令接口', interfaces: '信令接口',
interfacesPlease: '请输入信令接口', interfacesPlease: '请输入信令接口',
signalPort: '信令端口', rangePicker: '任务时间',
signalPortPlease: '请输入信令端口',
signalPortTip: '目标IP地址或源IP地址对应一方的端口',
rangePicker: '开始结束时间',
rangePickerPlease: '请选择任务时间开始结束时间', rangePickerPlease: '请选择任务时间开始结束时间',
remark: '说明', remark: '说明',
remarkPlease: '可输入任务说明', remarkPlease: '可输入任务说明',
addTask: '添加任务', addTask: '添加任务',
editTask: '修改任务',
viewTask: '查看任务', viewTask: '查看任务',
errorTaskInfo: '获取任务信息失败', errorTaskInfo: '获取任务信息失败',
delTaskTip: '确认删除记录编号为 {id} 的数据项?', delTaskTip: '确认删除记录编号为 {id} 的数据项?',
@@ -1091,6 +1188,15 @@ export default {
traceFile: "跟踪文件", traceFile: "跟踪文件",
errMsg: "错误信息", errMsg: "错误信息",
imsiORmsisdn: "imsi 或 msisdn 是空值,不能开始任务", imsiORmsisdn: "imsi 或 msisdn 是空值,不能开始任务",
dataView: "跟踪数据",
protocolOrInterface: "协议/接口",
msgNe: '消息网元',
msgEvent: '消息事件',
msgType: '消息类型',
msgDirect: '消息方向',
msgLen: '消息长度',
rowTime: '消息时间',
taskInfo: '任务信息',
}, },
}, },
faultManage: { faultManage: {
@@ -1200,6 +1306,7 @@ export default {
size: "文件大小", size: "文件大小",
modifiedTime: "修改时间", modifiedTime: "修改时间",
fileName: "文件名称", fileName: "文件名称",
downTipZip: "确认将目录 【{fileName}】 下载为ZIP文件?",
downTip: "确认下载文件名为 【{fileName}】 文件?", downTip: "确认下载文件名为 【{fileName}】 文件?",
downTipErr: "文件获取失败", downTipErr: "文件获取失败",
dirCd: "进入目录", dirCd: "进入目录",
@@ -1210,12 +1317,19 @@ export default {
tailLines: '末尾行数', tailLines: '末尾行数',
}, },
exportFile:{ exportFile:{
fileName:'文件来源', fileSource:'文件来源',
fileSourcePlease:'请选择文件来源',
downTip: "确认下载文件名为 【{fileName}】 文件?", downTip: "确认下载文件名为 【{fileName}】 文件?",
downTipErr: "文件获取失败", downTipErr: "文件获取失败",
deleteTip: "确认删除文件名为 【{fileName}】 文件?", deleteTip: "确认删除文件名为 【{fileName}】 文件?",
deleteTipErr: "文件删除失败", deleteTipErr: "文件删除失败",
selectTip:"请选择文件名", sysloginLog:'系统登录日志',
sysOperateLog:'系统操作日志',
neLog:'网元日志',
cdrIMS:'语音话单',
cdrSMF:'数据话单',
cdrSMSC:'短信话单',
cdrSGWC:'漫游数据话单',
} }
}, },
monitor: { monitor: {
@@ -1587,8 +1701,10 @@ export default {
userNum: '用户编号', userNum: '用户编号',
account: '登录账号', account: '登录账号',
userName: '用户昵称', userName: '用户昵称',
permission: '用户权限', permission: '用户角色',
className: '部门名称', className: '部门名称',
userType: '用户类型',
tenntName:'租户名称',
loginIp: '登录地址', loginIp: '登录地址',
loginTime: '登录时间', loginTime: '登录时间',
status: '用户状态', status: '用户状态',
@@ -1614,6 +1730,7 @@ export default {
sex:'用户性别', sex:'用户性别',
email:'电子邮箱', email:'电子邮箱',
fromClass:'所属部门', fromClass:'所属部门',
fromTenant:'所属租户',
userWork:'用户岗位', userWork:'用户岗位',
userWorkPlease: '请选择用户岗位', userWorkPlease: '请选择用户岗位',
userTip:'用户说明', userTip:'用户说明',
@@ -1627,6 +1744,7 @@ export default {
lock:'锁定', lock:'锁定',
inactive:'未激活', inactive:'未激活',
active:'激活', active:'激活',
permissionTip:'若要选择租户角色,需保证起码租户管理中有一个租户',
}, },
config: { config: {
configName: "参数名称", configName: "参数名称",
@@ -1651,6 +1769,55 @@ export default {
refreshCacheTip: "确定要刷新参数配置缓存吗?", refreshCacheTip: "确定要刷新参数配置缓存吗?",
refreshCacheOk: "刷新缓存成功", refreshCacheOk: "刷新缓存成功",
}, },
loginSource: {
uid: "唯一标识",
name: "名称",
namePlease: '请正确输入认证源名称',
icon: "图标",
iconPlease: '可填入图片链接或上传图片路径地址',
type: "类型",
activeFlag: "状态",
remark: "备注说明",
createTime: "创建时间",
updateTime: "更新时间",
ldapUrl: "服务器地址",
ldapUrlPlease: '请正确输入LDAP 服务器地址',
ldapBaseDN: "基础DN",
baseDnPlease: '请正确输入LDAP 基础DN',
ldapUserFilter: "用户过滤",
userFilterPlease: '请正确输入LDAP 用户过滤',
ldapBindDN: "绑定DN",
ldapBindPassword: "绑定密码",
smtpHost: '服务器地址',
smtpHostPlease: '请正确输入SMTP 服务器地址',
smtpPort: '端口号',
smtpPortPlease: '请正确输入SMTP 端口号',
oauth2ClientID: '客户端ID',
oauth2ClientIDPlease: '请正确输入OAuth2 客户端ID',
oauth2ClientSecret: '客户端密钥',
oauth2ClientSecretPlease: '请正确输入OAuth2 客户端密钥',
oauth2AuthURL: '授权URL',
oauth2AuthURLPlease: '请正确输入OAuth2 授权URL',
oauth2TokenURL: '令牌URL',
oauth2TokenURLPlease: '请正确输入OAuth2 令牌URL',
oauth2UserURL: '用户信息URL',
oauth2UserURLPlease: '请正确输入OAuth2 用户信息URL',
oauth2AccountField: '账号字段',
oauth2AccountFieldPlease: '请正确输入OAuth2 账号字段',
oauth2Scopes: '作用域',
oauth2ScopesPlease: '请正确输入OAuth2 作用域',
oauth2RedirectURL: '重定向URL',
oauth2RedirectURLPlease: '请正确输入OAuth2 重定向URL',
oauth2RedirectURLTip: '请跳转指定路径(omchost/#/login/oauth2), 重定向携带code和state地址参数',
uploadFileTip: '确认要上传认证源图标吗?',
uploadFileOk: '认证源图标上传成功',
uploadFileErr: '认证源图标上传失败',
viewInfoErr: "获取认证源信息失败",
addInfo: "添加认证源",
editInfo: "修改认证源",
delTip: "确认删除认证源编号为 【{num}】 的数据项?",
delOk: "删除成功",
},
setting: { setting: {
charMaxLen: '位字符长度', charMaxLen: '位字符长度',
saveSubmit: '提交保存', saveSubmit: '提交保存',
@@ -1710,6 +1877,10 @@ export default {
home: '系统首页', home: '系统首页',
homeTip:'确认要提交当前界面为系统界面吗?', homeTip:'确认要提交当前界面为系统界面吗?',
homeSet:'系统首页设置', homeSet:'系统首页设置',
backup: '系统备份',
backupInstruction: '系统备份将会对当前系统上运行的网元信息记录及配置文件进行备份,可进行系统的恢复操作!',
backupExportTip: '确认要导出系统备份吗?',
backupImportTip: '确认要导入系统备份吗?',
}, },
role:{ role:{
allScopeOptions:'全部数据权限', allScopeOptions:'全部数据权限',
@@ -1754,6 +1925,7 @@ export default {
cancelGive:'取消授权', cancelGive:'取消授权',
cancelSure:'确认取消用户编号为 【{userId}】 的数据项授权?', cancelSure:'确认取消用户编号为 【{userId}】 的数据项授权?',
batchCancel:'批量取消授权', batchCancel:'批量取消授权',
selectUser:'分配用户',
}, },
dept:{ dept:{
classInfo:'部门信息', classInfo:'部门信息',
@@ -1774,6 +1946,37 @@ export default {
phone:'联系电话', phone:'联系电话',
email:'邮箱', email:'邮箱',
}, },
tenant:{
classInfo:'租户信息',
className:'名称',
classId:'编号',
classSort:'排序',
status:'状态',
type:'租赁类型',
createTime:'创建时间',
highClass:'根级',
key:'租赁标识',
emailTip:'请输入正确的邮箱地址',
phoneTip:'请输入正确的手机号码',
node:'根节点',
delSure:'确认删除租户名为 【{title}】 的数据项?',
delTypeSure:'确认删除租赁类型为 【{tenancyAsset}】,租赁标识为【{tenancyKey}】 的数据项?',
open:'展',
close:'折',
addClass:'新增租户',
admin:'负责人',
phone:'联系电话',
email:'邮箱',
SDSST:'网络切片',
APN:'接入点',
IMSI:'SIM卡',
treeSelectTip:'请从左侧租户列表中选择租户进行配置',
upfTip:'请选择下拉列表UPF的资源唯一标识',
imsiTip:'请选择匹配类型和填入SIM卡号段',
radioTip:'请从下拉列表选择基站类型和基站ID',
defaultTip:'请选择租赁资产类别, 然后填写资产标识',
patternTip:'租赁标识不能包含特殊字符',
},
post:{ post:{
positionInfo:'岗位信息', positionInfo:'岗位信息',
positionId:'岗位编号', positionId:'岗位编号',

View File

@@ -20,6 +20,7 @@ import {
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import useLayoutStore from '@/store/modules/layout'; import useLayoutStore from '@/store/modules/layout';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import useUserStore from '@/store/modules/user';
import useRouterStore from '@/store/modules/router'; import useRouterStore from '@/store/modules/router';
import useTabsStore from '@/store/modules/tabs'; import useTabsStore from '@/store/modules/tabs';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
@@ -27,7 +28,7 @@ import useAlarmStore from '@/store/modules/alarm';
import { getServerTime } from '@/api'; import { getServerTime } from '@/api';
import { MENU_PATH_INLINE } from '@/constants/menu-constants'; 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, YYYY_MM_DD_HH_MM_SSZ } from '@/utils/date-utils';
import { parseUrlPath } from '@/plugins/file-static-url'; import { parseUrlPath } from '@/plugins/file-static-url';
const { proConfig, waterMarkContent } = useLayoutStore(); const { proConfig, waterMarkContent } = useLayoutStore();
const { t, currentLocale } = useI18n(); const { t, currentLocale } = useI18n();
@@ -64,19 +65,28 @@ watch(
{ immediate: true } { immediate: true }
); );
// 动态路由添加到菜单面板 const menuData = computed(() => {
const rootRoute = router.getRoutes().find(r => r.name === 'Root'); // 动态路由添加到菜单面板
if (rootRoute) { const rootRoute = router.getRoutes().find(r => r.name === 'Root');
const children = routerStore.setRootRouterData(rootRoute.children); if (rootRoute) {
const buildRouterData = routerStore.buildRouterData; const children = routerStore.setRootRouterData(rootRoute.children);
if (buildRouterData.length > 0) { const buildRouterData = routerStore.buildRouterData;
rootRoute.children = children.concat(buildRouterData); if (buildRouterData.length > 0) {
} else { rootRoute.children = children.concat(buildRouterData);
rootRoute.children = children; } else {
rootRoute.children = children;
}
// console.log(JSON.parse(JSON.stringify(rootRoute.children)));
if (!useUserStore().roles.includes('tenant')) {
rootRoute.children = rootRoute.children.filter(
item => item.name !== 'Index'
);
}
} }
}
const { menuData } = getMenuData(clearMenuItem(router.getRoutes())); const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
return menuData;
});
/**面包屑数据对象,排除根节点和首页不显示 */ /**面包屑数据对象,排除根节点和首页不显示 */
const breadcrumb = computed(() => { const breadcrumb = computed(() => {
@@ -198,6 +208,7 @@ let serverTime = reactive({
zone: 'UTC', // 时区 UTC zone: 'UTC', // 时区 UTC
interval: null as any, // 定时器 interval: null as any, // 定时器
}); });
let activeAlarmRefresh = 0;
// 获取服务器时间 // 获取服务器时间
function fnGetServerTime() { function fnGetServerTime() {
@@ -211,10 +222,18 @@ function fnGetServerTime() {
serverTime.timestamp = parseInt(res.data.timestamp); serverTime.timestamp = parseInt(res.data.timestamp);
serverTime.interval = setInterval(() => { serverTime.interval = setInterval(() => {
serverTime.timestamp += 1000; serverTime.timestamp += 1000;
activeAlarmRefresh += 1;
// serverTimeStr.value = parseDateToStr(serverTime.timestamp); // serverTimeStr.value = parseDateToStr(serverTime.timestamp);
// 用DOM直接修改 // 用DOM直接修改
if (serverTimeDom) { if (serverTimeDom) {
serverTimeDom.innerText = parseDateToStr(serverTime.timestamp); serverTimeDom.innerText = parseDateToStr(
serverTime.timestamp,
YYYY_MM_DD_HH_MM_SSZ
);
}
if (activeAlarmRefresh === 5) {
useAlarmStore().fnGetActiveAlarmInfo();
activeAlarmRefresh = 0;
} }
}, 1000); }, 1000);
} }
@@ -255,6 +274,7 @@ onUnmounted(() => {
v-bind="proConfig" v-bind="proConfig"
:iconfont-url="scriptUrl" :iconfont-url="scriptUrl"
:locale="fnLocale" :locale="fnLocale"
:sider-width="256"
> >
<!--插槽-菜单头--> <!--插槽-菜单头-->
<template #menuHeaderRender> <template #menuHeaderRender>

View File

@@ -4,6 +4,7 @@ import svgDark from '@/assets/svg/dark.svg';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface'; import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { viewTransitionTheme } from 'antdv-pro-layout'; import { viewTransitionTheme } from 'antdv-pro-layout';
import { ProModal } from 'antdv-pro-modal'; import { ProModal } from 'antdv-pro-modal';
import SearchMenu from './SearchMenu.vue';
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useFullscreen } from '@vueuse/core'; import { useFullscreen } from '@vueuse/core';
@@ -14,6 +15,9 @@ import useAppStore from '@/store/modules/app';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import useAlarmStore from '@/store/modules/alarm'; import useAlarmStore from '@/store/modules/alarm';
import useMaskStore from '@/store/modules/mask'; import useMaskStore from '@/store/modules/mask';
import { dbClear } from '@/utils/cache-db-utils';
import { CACHE_DB_TABLE_DND } from '@/constants/cache-keys-constants';
import { TENANTADMIN_ROLE_KEY } from '@/constants/admin-constants';
const { isFullscreen, toggle } = useFullscreen(); const { isFullscreen, toggle } = useFullscreen();
const { t, changeLocale, optionsLocale } = useI18n(); const { t, changeLocale, optionsLocale } = useI18n();
const layoutStore = useLayoutStore(); const layoutStore = useLayoutStore();
@@ -33,7 +37,10 @@ function fnClick({ key }: MenuInfo) {
router.push({ name: 'Profile' }); router.push({ name: 'Profile' });
break; break;
case 'logout': case 'logout':
userStore.fnLogOut().finally(() => router.push({ name: 'Login' })); userStore.fnLogOut().finally(() => {
dbClear(CACHE_DB_TABLE_DND);
router.push({ name: 'Login' });
});
break; break;
} }
} }
@@ -78,21 +85,33 @@ function fnChangeLocale(e: any) {
<template> <template>
<a-space :size="12" align="center"> <a-space :size="12" align="center">
<a-tooltip placement="bottomRight"> <!-- 搜索功能 -->
<template #title>{{ t('loayouts.rightContent.alarm') }}</template> <span >
<a-button type="text" style="color: inherit" @click="fnClickAlarm"> <SearchMenu />
<template #icon> </span>
<a-badge
:count="useAlarmStore().activeAlarmTotal" <span v-roles:has="[TENANTADMIN_ROLE_KEY]">
:overflow-count="99" <a-tooltip placement="bottom">
status="warning" <template #title>{{ t('loayouts.rightContent.alarm') }}</template>
style="color: inherit" <a-button
> type="text"
<BellOutlined /> style="color: inherit"
</a-badge> @click="fnClickAlarm"
</template> v-perms:has="['faultManage:active-alarm:index']"
</a-button> >
</a-tooltip> <template #icon>
<a-badge
:count="useAlarmStore().activeAlarmTotal"
:overflow-count="99"
status="warning"
style="color: inherit"
>
<BellOutlined />
</a-badge>
</template>
</a-button>
</a-tooltip>
</span>
<!-- 锁屏操作 --> <!-- 锁屏操作 -->
<span v-perms:has="['system:setting:lock']"> <span v-perms:has="['system:setting:lock']">

View File

@@ -0,0 +1,380 @@
<template>
<!-- 搜索按钮 -->
<a-tooltip placement="bottom">
<template #title>{{ t('common.search') }}</template>
<a-button type="text" style="color: inherit" @click="fnClickSearch">
<template #icon>
<SearchOutlined />
</template>
</a-button>
</a-tooltip>
<!-- 搜索弹窗 -->
<ProModal
:drag="false"
:center-y="true"
:width="600"
:minHeight="400"
:mask-closable="true"
v-model:open="searchModalOpen"
:title="t('common.search')"
:footer="null"
@cancel="fnCloseSearch"
>
<div class="search-modal-content">
<a-input
ref="searchInputRef"
v-model:value="searchKeyword"
:placeholder="t('common.searchPlease')"
size="large"
style="margin-bottom: 16px"
@keydown="fnHandleKeydown"
>
<template #prefix>
<SearchOutlined style="color: #bfbfbf" />
</template>
</a-input>
<div class="search-results">
<div v-if="filteredMenus.length === 0" class="no-results">
<a-empty
:description="
searchKeyword ? t('common.noData') : t('common.searchTip')
"
:image="false"
/>
</div>
<div v-else class="menu-list">
<div
v-for="menu in filteredMenus"
:key="menu.key"
class="menu-item"
@click="fnSelectMenu(menu)"
>
<div class="menu-icon">
<!-- 处理自定义图标字体 -->
<IconFont
v-if="menu.icon && menu.icon.startsWith('icon-')"
:type="menu.icon"
class="icon"
/>
<!-- 处理Ant Design图标组件 -->
<component
:is="menu.icon"
v-else-if="menu.icon && !menu.icon.startsWith('icon-')"
class="icon"
/>
<!-- 默认图标 -->
<FolderOutlined v-else class="icon" />
</div>
<div class="menu-info">
<div class="menu-title">{{ menu.title }}</div>
</div>
<div class="menu-action">
<RightOutlined />
</div>
</div>
</div>
</div>
</div>
</ProModal>
</template>
<script setup lang="ts">
import { getMenuData, clearMenuItem } from 'antdv-pro-layout';
import { ProModal } from 'antdv-pro-modal';
import IconFont from '@/components/IconFont/index.vue';
import { ref, computed, nextTick } from 'vue';
import { useRouter } from 'vue-router';
import useI18n from '@/hooks/useI18n';
import useUserStore from '@/store/modules/user';
import useRouterStore from '@/store/modules/router';
const { t } = useI18n();
const userStore = useUserStore();
const routerStore = useRouterStore();
const router = useRouter();
// 搜索相关状态
const searchModalOpen = ref<boolean>(false);
const searchKeyword = ref<string>('');
const searchInputRef = ref();
// 获取所有可搜索的菜单项
const searchableMenus = computed(() => {
const menus: Array<{
title: string;
path: string;
icon?: string;
key: string;
routeName?: string;
}> = [];
// 使用和BasicLayout完全相同的菜单数据获取逻辑
const getMenuDataForSearch = () => {
// 动态路由添加到菜单面板
const rootRoute = router.getRoutes().find(r => r.name === 'Root');
if (rootRoute) {
const children = routerStore.setRootRouterData(rootRoute.children);
const buildRouterData = routerStore.buildRouterData;
if (buildRouterData.length > 0) {
rootRoute.children = children.concat(buildRouterData);
} else {
rootRoute.children = children;
}
// 根据用户角色过滤
if (!userStore.roles.includes('tenant')) {
rootRoute.children = rootRoute.children.filter(
item => item.name !== 'Index'
);
}
}
// 使用和BasicLayout相同的处理方法
const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
return menuData;
};
// 递归获取所有路由项
const getRouteItems = (routes: any[], parentPath = '') => {
routes.forEach((route: any) => {
// 跳过根路径和不显示的菜单
if (route.path === '/' || route.path === '' || route.meta?.hideInMenu) {
// 继续处理子路由
if (route.children && route.children.length > 0) {
getRouteItems(route.children, route.path === '/' ? '' : route.path);
}
return;
}
// 构建完整路径
let fullPath = route.path;
if (!route.path.startsWith('/') && parentPath) {
fullPath = `${parentPath}/${route.path}`;
}
// 只添加有meta.title的路由项
if (route.meta && route.meta.title) {
try {
const title = t(route.meta.title);
// 避免重复添加已存在的路由
const exists = menus.find(
m => m.routeName === route.name || m.path === fullPath
);
if (!exists) {
menus.push({
title: title,
path: fullPath,
icon: route.meta.icon,
key: route.name || fullPath,
routeName: route.name,
});
}
} catch (error) {
// 如果翻译失败,使用原始标题
const exists = menus.find(
m => m.routeName === route.name || m.path === fullPath
);
if (!exists) {
menus.push({
title: route.meta.title,
path: fullPath,
icon: route.meta.icon,
key: route.name || fullPath,
routeName: route.name,
});
}
}
}
// 处理子路由
if (route.children && route.children.length > 0) {
getRouteItems(route.children, fullPath);
}
});
};
// 使用和菜单面板相同的数据源
const menuRoutes = getMenuDataForSearch();
if (menuRoutes && menuRoutes.length > 0) {
getRouteItems(menuRoutes);
}
return menus;
});
// 过滤的菜单项
const filteredMenus = computed(() => {
if (!searchKeyword.value.trim()) {
return searchableMenus.value.slice(0, 10); // 默认显示前10个
}
// return searchableMenus.value.filter(menu =>
// menu.title.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
// menu.path.toLowerCase().includes(searchKeyword.value.toLowerCase())
// ).slice(0, 10);
const value = searchKeyword.value.toLowerCase();
return searchableMenus.value
.filter(menu => menu.title.toLowerCase().includes(value))
.slice(0, 10);
});
/**打开搜索弹窗 */
function fnClickSearch() {
searchModalOpen.value = true;
searchKeyword.value = '';
nextTick(() => {
searchInputRef.value?.focus();
});
}
/**关闭搜索弹窗 */
function fnCloseSearch() {
searchModalOpen.value = false;
searchKeyword.value = '';
}
/**选择菜单项并跳转 */
function fnSelectMenu(menu: any) {
try {
// 优先使用路由名称跳转
if (menu.routeName) {
router.push({ name: menu.routeName });
} else {
router.push(menu.path);
}
fnCloseSearch();
} catch (error) {
// 如果跳转失败,尝试直接使用路径
try {
router.push(menu.path);
fnCloseSearch();
} catch (secondError) {
// 可以在这里添加错误提示
}
}
}
/**键盘事件处理 */
function fnHandleKeydown(e: KeyboardEvent) {
if (e.key === 'Escape') {
fnCloseSearch();
}
}
</script>
<style lang="less" scoped>
.search-modal-content {
.search-results {
max-height: 400px;
overflow-y: auto;
.no-results {
text-align: center;
padding: 40px 0;
}
.menu-list {
.menu-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
margin-bottom: 4px;
&:hover {
background-color: #f5f5f5;
.menu-action {
opacity: 1;
}
}
.menu-icon {
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background-color: #f0f2f5;
border-radius: 6px;
.icon {
font-size: 16px;
color: #666;
}
}
.menu-info {
flex: 1;
min-width: 0;
.menu-title {
font-size: 14px;
font-weight: 500;
color: #262626;
margin-bottom: 2px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.menu-path {
font-size: 12px;
color: #8c8c8c;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.menu-action {
opacity: 0;
transition: opacity 0.2s ease;
color: #bfbfbf;
font-size: 12px;
}
}
}
}
}
// 暗黑主题支持
[data-theme='dark'] {
.search-modal-content {
.search-results {
.menu-list {
.menu-item {
&:hover {
background-color: #303030;
}
.menu-icon {
background-color: #262626;
.icon {
color: #bfbfbf;
}
}
.menu-info {
.menu-title {
color: #f0f0f0;
}
.menu-path {
color: #8c8c8c;
}
}
}
}
}
}
}
</style>

View File

@@ -61,7 +61,7 @@ type OptionsType = {
/**请求地址 */ /**请求地址 */
url: string; url: string;
/**请求方法 */ /**请求方法 */
method: 'get' | 'post' | 'put' | 'delete' | 'PATCH'; method: 'get' | 'post' | 'put' | 'delete' | 'PATCH' | 'GET' | 'POST' | 'PUT' | 'DELETE';
/**请求头 */ /**请求头 */
headers?: HeadersInit; headers?: HeadersInit;
/**地址栏参数 */ /**地址栏参数 */

View File

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

View File

@@ -13,6 +13,8 @@ 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';
import useNeListStore from '@/store/modules/ne_list';
// NProgress Configuration // NProgress Configuration
NProgress.configure({ showSpinner: false }); NProgress.configure({ showSpinner: false });
@@ -30,12 +32,15 @@ const constantRoutes: RouteRecordRaw[] = [
name: 'Root', name: 'Root',
component: BasicLayout, component: BasicLayout,
redirect: '/index', redirect: '/index',
// redirect: '/monitor/dashboard',
children: [ children: [
{ {
path: '/index', path: '/index',
name: 'Index', name: 'Index',
meta: { title: 'router.index', icon: 'icon-pcduan' }, meta: { title: 'router.index', icon: 'icon-pcduan' },
component: () => import('@/views/index.vue'), component: () => import('@/views/index.vue'),
// meta: { title: 'router.index', icon: 'icon-pcduan', hideInMenu: true },
// redirect: '/monitor/dashboard',
}, },
{ {
path: '/account', path: '/account',
@@ -63,11 +68,16 @@ const constantRoutes: RouteRecordRaw[] = [
}, },
], ],
}, },
{
path: '/login/oauth2',
name: 'LoginOAuth2', // 第三方认证重定向
component: () => import('@/views/login/oauth2.vue'),
},
{ {
path: '/login', path: '/login',
name: 'Login', name: 'Login',
meta: { title: 'router.login' }, meta: { title: 'router.login' },
component: () => import('@/views/login.vue'), component: () => import('@/views/login/index.vue'),
}, },
{ {
path: '/register', path: '/register',
@@ -156,7 +166,7 @@ router.afterEach((to, from, failure) => {
/**无Token可访问页面地址白名单 */ /**无Token可访问页面地址白名单 */
const WHITE_LIST: string[] = [ const WHITE_LIST: string[] = [
'/login', '/login',
'/auth-redirect', '/login/oauth2',
'/help', '/help',
'/register', '/register',
'/quick-start', '/quick-start',
@@ -210,6 +220,9 @@ router.beforeEach(async (to, from, next) => {
const user = useUserStore(); const user = useUserStore();
if (user.roles && user.roles.length === 0) { if (user.roles && user.roles.length === 0) {
try { try {
useNeInfoStore().fnRefreshNelist();
useNeListStore().fnNelistRefresh();
// 获取用户信息 // 获取用户信息
await user.fnGetInfo(); await user.fnGetInfo();
// 获取路由信息 // 获取路由信息

View File

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

View File

@@ -0,0 +1,123 @@
import { defineStore } from 'pinia';
import {
RESULT_CODE_SUCCESS,
RESULT_MSG_SUCCESS,
} from '@/constants/result-constants';
import { listAllNeInfo } from '@/api/ne/neInfo';
import { parseDataToOptions } from '@/utils/parse-tree-utils';
/**网元列表信息类型 */
type NeList = {
/**网元列表 */
neList: Record<string, any>[];
/**级联options树结构 */
neCascaderOptions: Record<string, any>[];
/**选择器单级父类型 */
neSelectOtions: Record<string, any>[];
};
const useNeListStore = defineStore('ne_list', {
state: (): NeList => ({
neList: [],
neCascaderOptions: [],
neSelectOtions: [],
}),
getters: {
/**
* 网元列表
* @param state 内部属性不用传入
* @returns 级联options
*/
getNeList(state) {
return state.neList;
},
/**
* 获取级联options树结构
* @param state 内部属性不用传入
* @returns 级联options
*/
getNeCascaderOptions(state) {
return state.neCascaderOptions;
},
/**
* 选择器单级父类型
* @param state 内部属性不用传入
* @returns 选择options
*/
getNeSelectOtions(state) {
return state.neSelectOtions;
},
},
actions: {
// 刷新网元列表
async fnNelistRefresh() {
this.neList = [];
return await this.fnNelist();
},
// 获取网元列表
async fnNelist() {
// 有数据不请求
if (this.neList.length > 0) {
return {
code: RESULT_CODE_SUCCESS,
msg: RESULT_MSG_SUCCESS['en_US'],
data: this.neList,
};
}
const res = await listAllNeInfo({
bandStatus: false,
bandHost: false,
});
if (res.code === RESULT_CODE_SUCCESS) {
// 原始列表
this.neList = JSON.parse(JSON.stringify(res.data));
// 转级联数据
const options = parseDataToOptions(
res.data,
'neType',
'neName',
'neId'
);
this.neCascaderOptions = options;
// 转选择器单级父类型
this.neSelectOtions = options.map(item => {
return {
label: item.label,
value: item.value,
};
});
}
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;
},
},
});
export default useNeListStore;

View File

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

View File

@@ -27,8 +27,8 @@ type UserInfo = {
email: string; email: string;
/**用户性别 */ /**用户性别 */
sex: string | undefined; sex: string | undefined;
/**其他信息 */ /**用户类型 */
profile: Record<string, any>; userType: string;
}; };
const useUserStore = defineStore('user', { const useUserStore = defineStore('user', {
@@ -42,7 +42,7 @@ const useUserStore = defineStore('user', {
phonenumber: '', phonenumber: '',
email: '', email: '',
sex: undefined, sex: undefined,
profile: {}, userType: 'System',
}), }),
getters: { getters: {
/** /**
@@ -122,6 +122,7 @@ const useUserStore = defineStore('user', {
this.phonenumber = user.phonenumber; this.phonenumber = user.phonenumber;
this.email = user.email; this.email = user.email;
this.sex = user.sex; this.sex = user.sex;
this.userType = user.userType;
// 验证返回的roles是否是一个非空数组 // 验证返回的roles是否是一个非空数组
if (Array.isArray(roles) && roles.length > 0) { if (Array.isArray(roles) && roles.length > 0) {

View File

@@ -37,6 +37,24 @@ export async function dbGet(storeName: string, key: string) {
return value; return value;
} }
/**数据级缓存全部移除 */
export async function dbClear(storeName: string) {
if (!storeName ) {
return false;
}
localforage.config({
name: import.meta.env.VITE_APP_CODE,
storeName: storeName,
});
try {
await localforage.clear();
return true;
} catch (error) {
console.error(error);
}
return false;
}
/**数据级缓存移除 */ /**数据级缓存移除 */
export async function dbRemove(storeName: string, key: string) { export async function dbRemove(storeName: string, key: string) {
if (!storeName || !key) { if (!storeName || !key) {

View File

@@ -20,6 +20,13 @@ 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';
/**特殊 列如2025-04-28 08:00:46 GMT+08:00 */
export const YYYY_MM_DD_HH_MM_SSZ = 'YYYY-MM-DD HH:mm:ss [GMT]Z';
/**国际时间 列如2022-12-30T01:01:59+08:00 */
export const RFC3339 = 'YYYY-MM-DDTHH:mm:ssZ';
/**国际时间 列如Thu, Nov 14 2024 10:19 GMT+08:00 */
export const RFC822Z = 'ddd, MMM DD YYYY HH:mm [GMT]Z';
/** /**
* 格式时间字符串 * 格式时间字符串
* @param dateStr 时间字符串 * @param dateStr 时间字符串
@@ -36,12 +43,12 @@ export function parseStrToDate(
/** /**
* 格式时间 * 格式时间
* @param date 可转的Date对象 * @param date 可转的Date对象
* @param formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss * @param formatStr 时间格式 默认YYYY-MM-DD HH:mm:ssZZ
* @returns 时间格式字符串 * @returns 时间格式字符串
*/ */
export function parseDateToStr( export function parseDateToStr(
date: string | number | Date, date: string | number | Date,
formatStr: string = YYYY_MM_DD_HH_MM_SS formatStr: string = YYYY_MM_DD_HH_MM_SSZ
): string { ): string {
return dayjs(date).format(formatStr); return dayjs(date).format(formatStr);
} }

View File

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

View File

@@ -7,7 +7,9 @@ import StyleLayout from './components/style-layout.vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import useUserStore from '@/store/modules/user';
const appStore = useAppStore(); const appStore = useAppStore();
const userStore = useUserStore();
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@@ -29,8 +31,7 @@ onActivated(() => {
// 调用时机为首次挂载 // 调用时机为首次挂载
// 以及每次从缓存中被重新插入时 // 以及每次从缓存中被重新插入时
fnLocale(); fnLocale();
}) });
</script> </script>
<template> <template>
@@ -43,12 +44,15 @@ onActivated(() => {
<a-tab-pane <a-tab-pane
key="reset-passwd" key="reset-passwd"
:tab="t('views.account.settings.resetPasswd')" :tab="t('views.account.settings.resetPasswd')"
v-if="userStore.userType === 'System'"
> >
<ResetPasswd></ResetPasswd> <ResetPasswd></ResetPasswd>
</a-tab-pane> </a-tab-pane>
<a-tab-pane <a-tab-pane
key="style-layout" key="style-layout"
:tab="t('views.account.settings.styleLayout')" :tab="t('views.account.settings.styleLayout')"
v-if="false"
> >
<StyleLayout></StyleLayout> <StyleLayout></StyleLayout>
</a-tab-pane> </a-tab-pane>

View File

@@ -149,14 +149,6 @@ async function fnGetList(reload: boolean = false) {
// 初始 // 初始
if (!reload) { if (!reload) {
// 选择第一个
if (tableState.data.length > 0) {
const id = tableState.data[0].id;
fnTableSelectedRowKeys([id]);
} else {
fnTableSelectedRowKeys(tableState.selectedRowKeys);
}
if (statusBar.value) { if (statusBar.value) {
fnDesign(statusBar.value, rightNum, errorNum); fnDesign(statusBar.value, rightNum, errorNum);
} }
@@ -216,44 +208,6 @@ function fnDesign(container: HTMLElement, rightNum: number, errorNum: number) {
observer.observe(container); observer.observe(container);
} }
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
if (keys.length <= 0) return;
const id = keys[0];
const row: any = tableState.data.find((item: any) => item.id === id);
if (!row) {
message.error(t('views.index.neStatus'), 2);
return;
}
const neState = row.serverState;
if (!neState?.online) {
message.error(t('views.index.neStatus'), 2);
return;
}
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 nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
const sysMemUsageInMB = Math.round((sysMemUsageInKB / 1024) * 100) / 100;
// CPU
const nfCpu = neState.cpu?.nfCpuUsage;
const sysCpu = neState.cpu?.sysCpuUsage;
const nfCpuP = Math.round(nfCpu) / 100;
const sysCpuP = Math.round(sysCpu) / 100;
serverState.value = Object.assign(
{
cpuUse: `NE:${nfCpuP}%; SYS:${sysCpuP}%`,
memoryUse: `Total: ${totalMemInMB}MB; NE: ${nfUsedMemInMB}MB; SYS: ${sysMemUsageInMB}MB`,
},
neState
);
}
/** /**
* 国际化翻译转换 * 国际化翻译转换
*/ */
@@ -277,6 +231,7 @@ let dict: {
}); });
let timer: any; let timer: any;
let timerFlag: boolean = false;
onMounted(() => { onMounted(() => {
// 初始字典数据 // 初始字典数据
Promise.allSettled([getDict('ne_info_status'), getDict('index_status')]) Promise.allSettled([getDict('ne_info_status'), getDict('index_status')])
@@ -292,7 +247,7 @@ onMounted(() => {
fnLocale(); fnLocale();
await fnGetList(false); await fnGetList(false);
timer = setInterval(() => { timer = setInterval(() => {
if (!timer) return; if (timerFlag) return;
fnGetList(true); fnGetList(true);
}, 10_000); // 每隔10秒执行一次 }, 10_000); // 每隔10秒执行一次
}); });
@@ -302,6 +257,7 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearInterval(timer); clearInterval(timer);
timer = null; timer = null;
timerFlag = true;
}); });
</script> </script>
@@ -319,12 +275,6 @@ onBeforeUnmount(() => {
:loading="tableState.loading" :loading="tableState.loading"
:pagination="false" :pagination="false"
:scroll="{ x: true }" :scroll="{ x: true }"
: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'">
@@ -341,46 +291,6 @@ 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
:loading="tableState.loading"
:title="`${t('views.index.mark')} - ${serverState.neName || 'OMC'}`"
style="margin-top: 16px"
size="small"
>
<a-descriptions
bordered
:column="1"
:label-style="{ width: '160px' }"
>
<a-descriptions-item :label="t('views.index.hostName')">
{{ serverState.hostname }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.osInfo')">
{{ serverState.os }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.ipAddress')">
{{ serverState.neIP }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.version')">
{{ serverState.version }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.capability')">
{{ serverState.capability }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.cpuUse')">
{{ serverState.cpuUse }}
</a-descriptions-item>
<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-card>
</a-col> </a-col>
</a-row> </a-row>
</PageContainer> </PageContainer>

View File

@@ -5,6 +5,7 @@ import { message, Modal } from 'ant-design-vue/es';
import { SizeType } from 'ant-design-vue/es/config-provider'; import { SizeType } from 'ant-design-vue/es/config-provider';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface'; import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/es/table'; import { ColumnsType } from 'ant-design-vue/es/table';
import TableColumnsDnd from '@/components/TableColumnsDnd/index.vue';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { import {
RESULT_CODE_ERROR, RESULT_CODE_ERROR,
@@ -18,6 +19,9 @@ import { OptionsType, WS } from '@/plugins/ws-websocket';
import dayjs, { Dayjs } from 'dayjs'; 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 useUserStore from '@/store/modules/user';
import { TENANTADMIN_ROLE_KEY } from '@/constants/admin-constants';
import { listTenant } from '@/api/system/tenant';
import { useClipboard } from '@vueuse/core'; import { useClipboard } from '@vueuse/core';
const { copy } = useClipboard({ legacy: true }); const { copy } = useClipboard({ legacy: true });
const { t } = useI18n(); const { t } = useI18n();
@@ -49,11 +53,13 @@ let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
/**查询参数 */ /**查询参数 */
let queryParams = reactive({ let queryParams = reactive({
tenantNameArr: <Record<string, any>[]>[],
/**网元类型 */ /**网元类型 */
neType: 'AMF', neType: 'AMF',
neId: '001', neId: '001',
eventType: '', eventType: '',
imsi: '', imsi: '',
tenantName: '',
sortField: 'timestamp', sortField: 'timestamp',
sortOrder: 'desc', sortOrder: 'desc',
/**开始时间 */ /**开始时间 */
@@ -72,8 +78,9 @@ function fnQueryReset() {
queryParams = Object.assign(queryParams, { queryParams = Object.assign(queryParams, {
eventType: '', eventType: '',
imsi: '', imsi: '',
startTime: '', tenantName: '',
endTime: '', startTime: 0,
endTime: 0,
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
}); });
@@ -116,14 +123,17 @@ let tableState: TabeStateType = reactive({
selectedRowKeys: [], selectedRowKeys: [],
}); });
/**表格字段列排序 */
let tableColumnsDnd = ref<ColumnsType>([]);
/**表格字段列 */ /**表格字段列 */
let tableColumns: ColumnsType = [ let tableColumns: ColumnsType = [
{ // {
title: t('common.rowId'), // title: t('common.rowId'),
dataIndex: 'id', // dataIndex: 'id',
align: 'left', // align: 'left',
width: 100, // width: 100,
}, // },
{ {
title: 'IMSI', title: 'IMSI',
dataIndex: 'eventJSON', dataIndex: 'eventJSON',
@@ -152,15 +162,26 @@ let tableColumns: ColumnsType = [
title: t('views.dashboard.ue.time'), title: t('views.dashboard.ue.time'),
dataIndex: 'eventJSON', dataIndex: 'eventJSON',
align: 'left', align: 'left',
width: 150, width: 250,
customRender(opt) { customRender(opt) {
const record = opt.value; const record = opt.value;
console.log(record);
if (record?.time) { if (record?.time) {
return record.time; return parseDateToStr(record.time);
} }
return parseDateToStr(+record.timestamp * 1000); if (record?.timestamp) {
return parseDateToStr(+record.timestamp * 1000);
}
return '';
}, },
}, },
{
title: t('views.dashboard.cdr.tenantName'),
dataIndex: 'tenantName',
align: 'center',
key: 'tenantName',
width: 150,
},
{ {
title: t('common.operate'), title: t('common.operate'),
key: 'id', key: 'id',
@@ -317,14 +338,18 @@ function fnGetList(pageNum?: number) {
/**列表导出 */ /**列表导出 */
function fnExportList() { function fnExportList() {
if (modalState.confirmLoading) return; if (modalState.confirmLoading || tablePagination.total === 0) return;
Modal.confirm({ Modal.confirm({
title: t('common.tipTitle'), title: t('common.tipTitle'),
content: t('views.dashboard.ue.exportTip'), content: t('views.dashboard.ue.exportTip'),
onOk() { onOk() {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0); const hide = message.loading(t('common.loading'), 0);
const querys = toRaw(queryParams); const querys = toRaw(queryParams);
querys.pageSize = 10000; querys.pageNum = 1;
querys.pageSize = tablePagination.total;
querys.startTime = Number(querys.startTime);
querys.endTime = Number(querys.endTime);
exportAMFDataUE(querys) exportAMFDataUE(querys)
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
@@ -472,8 +497,33 @@ onMounted(() => {
}) })
.finally(() => { .finally(() => {
// 获取列表数据 // 获取列表数据
if (useUserStore().roles.includes('tenant')) {
const operateColumnIndex = tableColumns.findIndex(
column => column.key === 'tenantName'
);
if (operateColumnIndex !== -1) {
tableColumns.splice(operateColumnIndex, 1);
}
}
fnGetList(); fnGetList();
}); });
//查询租户
listTenant({ parentId: 0 }).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
queryParams.tenantNameArr = []; //上面置为空数组时会报错 故在此
res.data.forEach((item: any) => {
if (item.parentId === '0') {
queryParams.tenantNameArr.push({
value: item.tenantName,
label: item.tenantName,
});
}
});
}
});
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -526,6 +576,17 @@ onBeforeUnmount(() => {
></a-input> ></a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="6" :md="12" :xs="24" v-roles:has="[TENANTADMIN_ROLE_KEY]">
<a-form-item
:label="t('views.neUser.sub.tenantName')"
name="tenantName "
>
<a-auto-complete
v-model:value="queryParams.tenantName"
:options="queryParams.tenantNameArr"
></a-auto-complete>
</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>
<a-space :size="8"> <a-space :size="8">
@@ -575,7 +636,11 @@ onBeforeUnmount(() => {
cancel-text="No" cancel-text="No"
@confirm="fnRealTime()" @confirm="fnRealTime()"
> >
<a-button type="primary" :danger="realTimeData"> <a-button
type="primary"
:danger="realTimeData"
v-roles:has="[TENANTADMIN_ROLE_KEY]"
>
<template #icon><FundOutlined /> </template> <template #icon><FundOutlined /> </template>
{{ {{
!realTimeData !realTimeData
@@ -623,6 +688,11 @@ onBeforeUnmount(() => {
<template #icon><ReloadOutlined /></template> <template #icon><ReloadOutlined /></template>
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<TableColumnsDnd
cache-id="amfUeData"
:columns="tableColumns"
v-model:columns-dnd="tableColumnsDnd"
></TableColumnsDnd>
<a-tooltip> <a-tooltip>
<template #title>{{ t('common.sizeText') }}</template> <template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click" placement="bottomRight"> <a-dropdown trigger="click" placement="bottomRight">
@@ -654,7 +724,7 @@ onBeforeUnmount(() => {
<a-table <a-table
class="table" class="table"
row-key="id" row-key="id"
:columns="tableColumns" :columns="tableColumnsDnd"
:loading="tableState.loading" :loading="tableState.loading"
:data-source="tableState.data" :data-source="tableState.data"
:size="tableState.size" :size="tableState.size"

View File

@@ -21,7 +21,10 @@ 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 { listTenant } from '@/api/system/tenant';
import { useClipboard } from '@vueuse/core'; import { useClipboard } from '@vueuse/core';
import dayjs, { type Dayjs } from 'dayjs';
import { dayjsRanges } from '@/hooks/useRangePicker';
const { copy } = useClipboard({ legacy: true }); const { copy } = useClipboard({ legacy: true });
const { t } = useI18n(); const { t } = useI18n();
const { getDict } = useDictStore(); const { getDict } = useDictStore();
@@ -34,31 +37,39 @@ let dict: {
cdrSipCode: DictType[]; cdrSipCode: DictType[];
/**CDR 呼叫类型 */ /**CDR 呼叫类型 */
cdrCallType: DictType[]; cdrCallType: DictType[];
/**CDR SIP响应代码类别类型 */
cdrSipCodeCause: DictType[];
} = reactive({ } = reactive({
cdrSipCode: [], cdrSipCode: [],
cdrCallType: [], cdrCallType: [],
cdrSipCodeCause: [],
}); });
/**网元可选 */ /**网元可选 */
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 queryParams = reactive({ let queryParams = reactive({
tenantNameArr: <Record<string, any>[]>[],
/**网元类型 */ /**网元类型 */
neType: 'IMS', neType: 'IMS',
neId: '001', neId: '001',
recordType: '', recordType: '',
callerParty: '', callerParty: '',
calledParty: '', calledParty: '',
tenantName: '',
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,12 +83,13 @@ function fnQueryReset() {
recordType: '', recordType: '',
callerParty: '', callerParty: '',
calledParty: '', calledParty: '',
startTime: '', tenantName: '',
endTime: '', startTime: undefined,
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();
@@ -118,12 +130,12 @@ let tableState: TabeStateType = reactive({
/**表格字段列 */ /**表格字段列 */
let tableColumns: ColumnsType = [ let tableColumns: ColumnsType = [
{ // {
title: t('common.rowId'), // title: t('common.rowId'),
dataIndex: 'id', // dataIndex: 'id',
align: 'left', // align: 'left',
width: 100, // width: 100,
}, // },
{ {
title: t('views.dashboard.cdr.recordType'), title: t('views.dashboard.cdr.recordType'),
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
@@ -163,13 +175,6 @@ let tableColumns: ColumnsType = [
return cdrJSON.calledParty; return cdrJSON.calledParty;
}, },
}, },
{
title: t('views.dashboard.cdr.result'),
dataIndex: 'cdrJSON',
key: 'cause',
align: 'left',
width: 150,
},
{ {
title: t('views.dashboard.cdr.duration'), title: t('views.dashboard.cdr.duration'),
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
@@ -183,32 +188,77 @@ let tableColumns: ColumnsType = [
: parseDuration(cdrJSON.callDuration); : parseDuration(cdrJSON.callDuration);
}, },
}, },
{
title: t('views.dashboard.cdr.resultCode'),
dataIndex: 'cdrJSON',
key: 'code',
align: 'left',
width: 150,
},
{
title: t('views.dashboard.cdr.resultCause'),
dataIndex: 'cdrJSON',
key: 'cause',
align: 'left',
width: 200,
},
{
title: t('views.dashboard.cdr.mosAverage'),
dataIndex: 'cdrJSON',
key: 'mosAverage',
align: 'left',
width: 120,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.mosAverage;
},
},
{
title: t('views.dashboard.cdr.callConnectionTime'),
dataIndex: 'cdrJSON',
key: 'callConnectionTime',
align: 'left',
width: 200,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.callType === 'sms'
? '-'
: parseDuration(cdrJSON.callConnectionTime);
},
},
{ {
title: t('views.dashboard.cdr.seizureTime'), title: t('views.dashboard.cdr.seizureTime'),
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
align: 'left', align: 'left',
width: 200, width: 250,
customRender(opt) { customRender(opt) {
const cdrJSON = opt.value; const cdrJSON = opt.value;
if (typeof cdrJSON.seizureTime === 'number') { if (typeof cdrJSON.seizureTime === 'number') {
return parseDateToStr(+cdrJSON.seizureTime * 1000); return parseDateToStr(+cdrJSON.seizureTime * 1000);
} }
return cdrJSON.seizureTime; return parseDateToStr(cdrJSON.seizureTime);
}, },
}, },
{ {
title: t('views.dashboard.cdr.releaseTime'), title: t('views.dashboard.cdr.releaseTime'),
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
align: 'left', align: 'left',
width: 200, width: 250,
customRender(opt) { customRender(opt) {
const cdrJSON = opt.value; const cdrJSON = opt.value;
if (typeof cdrJSON.releaseTime === 'number') { if (typeof cdrJSON.releaseTime === 'number') {
return parseDateToStr(+cdrJSON.releaseTime * 1000); return parseDateToStr(+cdrJSON.releaseTime * 1000);
} }
return cdrJSON.releaseTime; return parseDateToStr(cdrJSON.releaseTime);
}, },
}, },
{
title: t('views.dashboard.cdr.tenantName'),
dataIndex: 'tenantName',
align: 'left',
key: 'tenantName',
width: 150,
},
{ {
title: t('common.operate'), title: t('common.operate'),
key: 'id', key: 'id',
@@ -328,11 +378,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)) {
// 取消勾选 // 取消勾选
@@ -363,20 +419,25 @@ function fnGetList(pageNum?: number) {
modalState.maxId = Number(res.rows[0].id); modalState.maxId = Number(res.rows[0].id);
} }
} }
tableState.loading = false; tableState.loading = false;
}); });
} }
/**列表导出 */ /**列表导出 */
function fnExportList() { function fnExportList() {
if (modalState.confirmLoading) return; if (modalState.confirmLoading || tablePagination.total === 0) return;
Modal.confirm({ Modal.confirm({
title: t('common.tipTitle'), title: t('common.tipTitle'),
content: t('views.dashboard.cdr.exportTip'), content: t('views.dashboard.cdr.exportTip'),
onOk() { onOk() {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0); const hide = message.loading(t('common.loading'), 0);
const querys = toRaw(queryParams); const querys = toRaw(queryParams);
querys.pageSize = 10000; querys.pageNum = 1;
querys.pageSize = tablePagination.total;
querys.startTime = Number(querys.startTime);
querys.endTime = Number(querys.endTime);
exportIMSDataCDR(querys) exportIMSDataCDR(querys)
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
@@ -469,16 +530,21 @@ function wsMessage(res: Record<string, any>) {
onMounted(() => { onMounted(() => {
// 初始字典数据 // 初始字典数据
Promise.allSettled([getDict('cdr_sip_code'), getDict('cdr_call_type')]).then( Promise.allSettled([
resArr => { getDict('cdr_sip_code'),
if (resArr[0].status === 'fulfilled') { getDict('cdr_call_type'),
dict.cdrSipCode = resArr[0].value; getDict('cdr_sip_code_cause'),
} ]).then(resArr => {
if (resArr[1].status === 'fulfilled') { if (resArr[0].status === 'fulfilled') {
dict.cdrCallType = resArr[1].value; dict.cdrSipCode = resArr[0].value;
}
} }
); if (resArr[1].status === 'fulfilled') {
dict.cdrCallType = resArr[1].value;
}
if (resArr[2].status === 'fulfilled') {
dict.cdrSipCodeCause = resArr[2].value;
}
});
// 获取网元网元列表 // 获取网元网元列表
useNeInfoStore() useNeInfoStore()
.fnNelist() .fnNelist()
@@ -507,6 +573,21 @@ onMounted(() => {
// 获取列表数据 // 获取列表数据
fnGetList(); fnGetList();
}); });
//查询租户
listTenant({ parentId: 0 }).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
queryParams.tenantNameArr = []; //上面置为空数组时会报错 故在此
res.data.forEach((item: any) => {
if (item.parentId === '0') {
queryParams.tenantNameArr.push({
value: item.tenantName,
label: item.tenantName,
});
}
});
}
});
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -526,7 +607,7 @@ 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-col :lg="8" :md="12" :xs="24">
<a-form-item label="IMS" name="neId "> <a-form-item label="IMS" name="neId ">
<a-select <a-select
v-model:value="queryParams.neId" v-model:value="queryParams.neId"
@@ -536,7 +617,7 @@ onBeforeUnmount(() => {
/> />
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="8" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.dashboard.cdr.called')" :label="t('views.dashboard.cdr.called')"
name="calledParty" name="calledParty"
@@ -548,7 +629,7 @@ onBeforeUnmount(() => {
></a-input> ></a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="6" :md="12" :xs="24"> <a-col :lg="8" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.dashboard.cdr.caller')" :label="t('views.dashboard.cdr.caller')"
name="callerParty " name="callerParty "
@@ -560,20 +641,6 @@ onBeforeUnmount(() => {
></a-input> ></a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col :lg="4" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
<a-col :lg="8" :md="12" :xs="24"> <a-col :lg="8" :md="12" :xs="24">
<a-form-item <a-form-item
:label="t('views.dashboard.cdr.recordType')" :label="t('views.dashboard.cdr.recordType')"
@@ -595,15 +662,40 @@ onBeforeUnmount(() => {
> >
<a-range-picker <a-range-picker
v-model:value="queryRangePicker" v-model:value="queryRangePicker"
allow-clear :presets="dayjsRanges()"
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>
<a-col :lg="8" :md="12" :xs="24">
<a-form-item
:label="t('views.neUser.sub.tenantName')"
name="tenantName "
>
<a-auto-complete
v-model:value="queryParams.tenantName"
:options="queryParams.tenantNameArr"
></a-auto-complete>
</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-row> </a-row>
</a-form> </a-form>
</a-card> </a-card>
@@ -707,7 +799,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 * 160, y: 'calc(100vh - 480px)' }"
:row-selection="{ :row-selection="{
type: 'checkbox', type: 'checkbox',
columnWidth: '48px', columnWidth: '48px',
@@ -722,7 +814,7 @@ onBeforeUnmount(() => {
:value="record.cdrJSON.callType" :value="record.cdrJSON.callType"
/> />
</template> </template>
<template v-if="column.key === 'cause'"> <template v-if="column.key === 'code'">
<span v-if="record.cdrJSON.callType !== 'sms'"> <span v-if="record.cdrJSON.callType !== 'sms'">
<DictTag <DictTag
:options="dict.cdrSipCode" :options="dict.cdrSipCode"
@@ -734,6 +826,16 @@ onBeforeUnmount(() => {
{{ t('views.dashboard.cdr.resultOk') }} {{ t('views.dashboard.cdr.resultOk') }}
</span> </span>
</template> </template>
<template v-if="column.key === 'cause'">
<span v-if="record.cdrJSON.callType !== 'sms'">
<DictTag
:options="dict.cdrSipCodeCause"
:value="record.cdrJSON.cause"
value-default="0"
/>
</span>
<span v-else> Call failure for other reason </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> <a-tooltip>
@@ -762,93 +864,6 @@ onBeforeUnmount(() => {
</a-space> </a-space>
</template> </template>
</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>
{{
typeof record.cdrJSON.releaseTime === 'number'
? parseDateToStr(+record.cdrJSON.releaseTime * 1000)
: record.cdrJSON.releaseTime
}}
</span>
</div>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-divider orientation="left">
{{ t('views.dashboard.cdr.rowInfo') }}
</a-divider>
<div>
<span>{{ t('views.dashboard.cdr.type') }}: </span>
<DictTag
:options="dict.cdrCallType"
:value="record.cdrJSON.callType"
/>
</div>
<div>
<span>{{ t('views.dashboard.cdr.duration') }}: </span>
<span v-if="record.cdrJSON.callType !== 'sms'">
{{ parseDuration(record.cdrJSON.callDuration) }}
</span>
<span v-else> - </span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.caller') }}: </span>
<span>{{ record.cdrJSON.callerParty }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.called') }}: </span>
<span>{{ record.cdrJSON.calledParty }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.result') }}: </span>
<span v-if="record.cdrJSON.callType !== 'sms'">
<DictTag
:options="dict.cdrSipCode"
:value="record.cdrJSON.cause"
value-default="0"
/>
</span>
<span v-else>
{{ t('views.dashboard.cdr.resultOk') }}
</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.seizureTime') }}: </span>
<span>
{{
typeof record.cdrJSON.seizureTime === 'number'
? parseDateToStr(+record.cdrJSON.seizureTime * 1000)
: record.cdrJSON.seizureTime
}}
</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.releaseTime') }}: </span>
<span>
{{
typeof record.cdrJSON.releaseTime === 'number'
? parseDateToStr(+record.cdrJSON.releaseTime * 1000)
: record.cdrJSON.releaseTime
}}
</span>
</div>
</a-col>
</a-row>
</template>
</a-table> </a-table>
</a-card> </a-card>
</PageContainer> </PageContainer>

View File

@@ -18,6 +18,7 @@ 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 { listTenant } from '@/api/system/tenant';
import { useClipboard } from '@vueuse/core'; import { useClipboard } from '@vueuse/core';
const { copy } = useClipboard({ legacy: true }); const { copy } = useClipboard({ legacy: true });
const { t } = useI18n(); const { t } = useI18n();
@@ -50,11 +51,13 @@ let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
/**查询参数 */ /**查询参数 */
let queryParams = reactive({ let queryParams = reactive({
tenantNameArr: <Record<string, any>[]>[],
/**网元类型 */ /**网元类型 */
neType: 'MME', neType: 'MME',
neId: '001', neId: '001',
eventType: '', eventType: '',
imsi: '', imsi: '',
tenantName: '',
sortField: 'timestamp', sortField: 'timestamp',
sortOrder: 'desc', sortOrder: 'desc',
/**开始时间 */ /**开始时间 */
@@ -73,8 +76,9 @@ function fnQueryReset() {
queryParams = Object.assign(queryParams, { queryParams = Object.assign(queryParams, {
eventType: '', eventType: '',
imsi: '', imsi: '',
startTime: '', tenantName: '',
endTime: '', startTime: 0,
endTime: 0,
pageNum: 1, pageNum: 1,
pageSize: 20, pageSize: 20,
}); });
@@ -119,12 +123,12 @@ let tableState: TabeStateType = reactive({
/**表格字段列 */ /**表格字段列 */
let tableColumns: ColumnsType = [ let tableColumns: ColumnsType = [
{ // {
title: t('common.rowId'), // title: t('common.rowId'),
dataIndex: 'id', // dataIndex: 'id',
align: 'left', // align: 'left',
width: 100, // width: 100,
}, // },
{ {
title: 'IMSI', title: 'IMSI',
dataIndex: 'eventJSON', dataIndex: 'eventJSON',
@@ -153,15 +157,25 @@ let tableColumns: ColumnsType = [
title: t('views.dashboard.ue.time'), title: t('views.dashboard.ue.time'),
dataIndex: 'eventJSON', dataIndex: 'eventJSON',
align: 'left', align: 'left',
width: 150, width: 250,
customRender(opt) { customRender(opt) {
const record = opt.value; const record = opt.value;
if (record?.time) { if (record?.time) {
return record.time; return parseDateToStr(record.time);
} }
return parseDateToStr(+record.timestamp * 1000); if (record?.timestamp) {
return parseDateToStr(+record.timestamp * 1000);
}
return '';
}, },
}, },
{
title: t('views.dashboard.cdr.tenantName'),
dataIndex: 'tenantName',
align: 'center',
key: 'tenantName',
width: 150,
},
{ {
title: t('common.operate'), title: t('common.operate'),
key: 'id', key: 'id',
@@ -318,14 +332,18 @@ function fnGetList(pageNum?: number) {
/**列表导出 */ /**列表导出 */
function fnExportList() { function fnExportList() {
if (modalState.confirmLoading) return; if (modalState.confirmLoading || tablePagination.total === 0) return;
Modal.confirm({ Modal.confirm({
title: t('common.tipTitle'), title: t('common.tipTitle'),
content: t('views.dashboard.ue.exportTip'), content: t('views.dashboard.ue.exportTip'),
onOk() { onOk() {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0); const hide = message.loading(t('common.loading'), 0);
const querys = toRaw(queryParams); const querys = toRaw(queryParams);
querys.pageSize = 10000; querys.pageNum = 1;
querys.pageSize = tablePagination.total;
querys.startTime = Number(querys.startTime);
querys.endTime = Number(querys.endTime);
exportMMEDataUE(querys) exportMMEDataUE(querys)
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS) { if (res.code === RESULT_CODE_SUCCESS) {
@@ -481,6 +499,21 @@ onMounted(() => {
// 获取列表数据 // 获取列表数据
fnGetList(); fnGetList();
}); });
//查询租户
listTenant({ parentId: 0 }).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
queryParams.tenantNameArr = []; //上面置为空数组时会报错 故在此
res.data.forEach((item: any) => {
if (item.parentId === '0') {
queryParams.tenantNameArr.push({
value: item.tenantName,
label: item.tenantName,
});
}
});
}
});
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -533,6 +566,17 @@ 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
:label="t('views.neUser.sub.tenantName')"
name="tenantName "
>
<a-auto-complete
v-model:value="queryParams.tenantName"
:options="queryParams.tenantNameArr"
></a-auto-complete>
</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>
<a-space :size="8"> <a-space :size="8">

View File

@@ -236,8 +236,11 @@ function handleRanderChart(
} }
function fnChangeData(data: any[], itemID: string) { function fnChangeData(data: any[], itemID: string) {
let info = data.find((item: any) => item.id === itemID); const neType=itemID.split('_')[0];
if (!info || !info.neState.online) return; const neID=itemID.split('_')[1];
let info = data.find((item: any) => item.id === neType);
if (!info || !info.neStateMap[neID]?.online) return;
// if (!info.neState.online) { // if (!info.neState.online) {
// info = data.find((item: any) => item.id === itemID); // info = data.find((item: any) => item.id === itemID);
// graphNodeClickID.value = itemID; // graphNodeClickID.value = itemID;
@@ -249,16 +252,16 @@ function fnChangeData(data: any[], itemID: string) {
// console.log(info.neState.disk); // console.log(info.neState.disk);
let sysCpuUsage = 0; let sysCpuUsage = 0;
let nfCpuUsage = 0; let nfCpuUsage = 0;
if (info.neState.cpu) { if (info.neStateMap[neID].cpu) {
nfCpuUsage = info.neState.cpu.nfCpuUsage; nfCpuUsage = info.neStateMap[neID].cpu.nfCpuUsage;
const nfCpu = +(info.neState.cpu.nfCpuUsage / 100); const nfCpu = +(info.neStateMap[neID].cpu.nfCpuUsage / 100);
nfCpuUsage = +nfCpu.toFixed(2); nfCpuUsage = +nfCpu.toFixed(2);
if (nfCpuUsage > 100) { if (nfCpuUsage > 100) {
nfCpuUsage = 100; nfCpuUsage = 100;
} }
sysCpuUsage = info.neState.cpu.sysCpuUsage; sysCpuUsage = info.neStateMap[neID].cpu.sysCpuUsage;
let sysCpu = +(info.neState.cpu.sysCpuUsage / 100); let sysCpu = +(info.neStateMap[neID].cpu.sysCpuUsage / 100);
sysCpuUsage = +sysCpu.toFixed(2); sysCpuUsage = +sysCpu.toFixed(2);
if (sysCpuUsage > 100) { if (sysCpuUsage > 100) {
sysCpuUsage = 100; sysCpuUsage = 100;
@@ -266,8 +269,8 @@ function fnChangeData(data: any[], itemID: string) {
} }
let sysMemUsage = 0; let sysMemUsage = 0;
if (info.neState.mem) { if (info.neStateMap[neID].mem) {
const men = info.neState.mem.sysMemUsage; const men = info.neStateMap[neID].mem.sysMemUsage;
sysMemUsage = +(men / 100).toFixed(2); sysMemUsage = +(men / 100).toFixed(2);
if (sysMemUsage > 100) { if (sysMemUsage > 100) {
sysMemUsage = 100; sysMemUsage = 100;
@@ -275,8 +278,8 @@ function fnChangeData(data: any[], itemID: string) {
} }
let sysDiskUsage = 0; let sysDiskUsage = 0;
if (info.neState.disk && Array.isArray(info.neState.disk.partitionInfo)) { if (info.neStateMap[neID].disk && Array.isArray(info.neStateMap[neID].disk.partitionInfo)) {
let disks: any[] = info.neState.disk.partitionInfo; let disks: any[] = info.neStateMap[neID].disk.partitionInfo;
disks = disks.sort((a, b) => +b.used - +a.used); disks = disks.sort((a, b) => +b.used - +a.used);
if (disks.length > 0) { if (disks.length > 0) {
const { total, used } = disks[0]; const { total, used } = disks[0];

View File

@@ -22,18 +22,25 @@ const graphG6Dom = ref<HTMLElement | undefined>(undefined);
/**图节点展示 */ /**图节点展示 */
const graphNodeTooltip = new Tooltip({ const graphNodeTooltip = new Tooltip({
offsetX: 20, offsetX: 10,
offsetY: 20, offsetY: 20,
getContent(evt) { getContent(evt) {
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo'); if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
const { id, label, neState }: any = evt.item?.getModel(); const { id, label, neState, neInfoList, neStateMap }: any = evt.item?.getModel();
//console.log(neInfoList,neState,neInfoList);
if (notNeNodes.includes(id)) { if (notNeNodes.includes(id)) {
return `<div><span>${label || id}</span></div>`; return `<div><span>${label || id}</span></div>`;
} }
if (!neState) { if (!neState) {
return `<div><span>${label || id}</span></div>`; return `<div><span>${label || id}</span></div>`;
} }
return `
// 获取同类型网元列表
const sameTypeNes = neInfoList || [];
// 如果没有网元或只有一个网元,显示原来的信息
if (sameTypeNes.length <= 1) {
return `
<div <div
style=" style="
display: flex; display: flex;
@@ -56,7 +63,7 @@ const graphNodeTooltip = new Tooltip({
<div><strong>${t('views.monitor.topology.name')}</strong><span> <div><strong>${t('views.monitor.topology.name')}</strong><span>
${neState.neName ?? '--'} ${neState.neName ?? '--'}
</span></div> </span></div>
<div><strong>IP</strong><span>${neState.neIP}</span></div> <div><strong>IP</strong><span>${neState.neIP ?? '--'}</span></div>
<div><strong>${t('views.monitor.topology.version')}</strong><span> <div><strong>${t('views.monitor.topology.version')}</strong><span>
${neState.version ?? '--'} ${neState.version ?? '--'}
</span></div> </span></div>
@@ -65,24 +72,71 @@ const graphNodeTooltip = new Tooltip({
</span></div> </span></div>
<div><strong>${t('views.monitor.topology.expiryDate')}</strong><span> <div><strong>${t('views.monitor.topology.expiryDate')}</strong><span>
${neState.expire ?? '--'} ${neState.expire ?? '--'}
</span></div> </span></div>
</div> </div>
`; `;
}
// 如果有多个网元,聚合显示
let content = `
<div
style="
display: flex;
flex-direction: column;
width: 300px;
"
>
<div><strong>${t('views.monitor.topology.state')}</strong><span>
${
neState.online
? t('views.monitor.topology.normalcy')
: t('views.monitor.topology.exceptions')
}
</span></div>
<div><strong>${t('views.monitor.topology.refreshTime')}</strong><span>
${neState.refreshTime ?? '--'}
</span></div>
<div>========================</div>`;
// 为每个同类型网元添加信息
sameTypeNes.forEach((ne: any, index: number) => {
// 获取该网元的状态信息
const neStateInfo = neStateMap?.[ne.neId] ||
(ne.neId === neState.neId ? neState : {});
content += `
<div style="margin-top: 8px;"><strong>${t('views.monitor.topology.name')}${ne.neName || id + '_' + ne.neId}</strong></div>
<div><strong>ID</strong><span>${ne.neId || '--'}</span></div>
<div><strong>IP</strong><span>${neStateInfo.neIP || ne.neIP || '--'}</span></div>
<div><strong>${t('views.monitor.topology.version')}</strong><span>
${neStateInfo.version || ne.version || '--'}
</span></div>
<div><strong>${t('views.monitor.topology.serialNum')}</strong><span>
${neStateInfo.sn || ne.sn || '--'}
</span></div>
<div><strong>${t('views.monitor.topology.expiryDate')}</strong><span>
${neStateInfo.expire || ne.expire || '--'}
</span></div>
<div><strong>${t('views.monitor.topology.state')}</strong><span>
${
neStateInfo.online !== undefined
? (neStateInfo.online
? t('views.monitor.topology.normalcy')
: t('views.monitor.topology.exceptions'))
: 'undefined'
}
</span></div>
${index < sameTypeNes.length - 1 ? '<div>------------------------</div>' : ''}
`;
});
content += '</div>';
return content;
}, },
itemTypes: ['node'], itemTypes: ['node'],
}); });
/**图绑定事件 */
function fnGraphEvent(graph: Graph) {
// 节点点击
graph.on('node:click', evt => {
// 获得鼠标当前目标节点
const node = evt.item?.getModel();
if (node && node.id && !notNeNodes.includes(node.id)) {
graphNodeClickID.value = node.id;
}
});
}
/**图数据渲染 */ /**图数据渲染 */
function handleRanderGraph( function handleRanderGraph(
@@ -104,7 +158,7 @@ function handleRanderGraph(
fitViewPadding: [20], fitViewPadding: [20],
autoPaint: true, autoPaint: true,
modes: { modes: {
default: ['drag-canvas', 'zoom-canvas'], // default: ['drag-canvas', 'zoom-canvas'],
}, },
groupByTypes: false, groupByTypes: false,
nodeStateStyles: { nodeStateStyles: {
@@ -122,7 +176,6 @@ function handleRanderGraph(
graph.data(data); graph.data(data);
graph.render(); graph.render();
fnGraphEvent(graph);
graphG6.value = graph; graphG6.value = graph;
@@ -150,14 +203,14 @@ function handleRanderGraph(
* 获取图组数据渲染到画布 * 获取图组数据渲染到画布
* @param reload 是否重载数据 * @param reload 是否重载数据
*/ */
function fnGraphDataLoad(reload: boolean = false) { function fnGraphDataLoad(reload: boolean = false) {
Promise.all([ Promise.all([
getGraphData(graphState.group), getGraphData(graphState.group),
listAllNeInfo({ listAllNeInfo({
bandStatus: false, bandStatus: false,
}), }),
]) ])
.then(resArr => { .then(resArr => {
const graphRes = resArr[0]; const graphRes = resArr[0];
const neRes = resArr[1]; const neRes = resArr[1];
if ( if (
@@ -183,27 +236,45 @@ function fnGraphDataLoad(reload: boolean = false) {
if (!res) return; if (!res) return;
const { combos, edges, nodes } = res.graphData; const { combos, edges, nodes } = res.graphData;
// 按网元类型分组
const neTypeMap = new Map();
res.neList.forEach(ne => {
if (!ne.neType) return;
if (!neTypeMap.has(ne.neType)) {
neTypeMap.set(ne.neType, []);
}
neTypeMap.get(ne.neType).push(ne);
});
// 节点过滤 // 节点过滤
const nf: Record<string, any>[] = nodes.filter( const nf: Record<string, any>[] = nodes.filter(
(node: Record<string, any>) => { (node: Record<string, any>) => {
Reflect.set(node, 'neState', { online: false }); Reflect.set(node, 'neState', { online: false });
Reflect.set(node, 'neStateMap', {}); // 初始化状态映射
// 图片路径处理 // 图片路径处理
if (node.img) node.img = parseBasePath(node.img); if (node.img) node.img = parseBasePath(node.img);
if (node.icon.show && node.icon?.img) { if (node.icon.show && node.icon?.img){
node.icon.img = parseBasePath(node.icon.img); node.icon.img = parseBasePath(node.icon.img);
} }
// 遍历是否有网元数据 // 遍历是否有网元数据
const nodeID: string = node.id; const nodeID: string = node.id;
const hasNe = res.neList.some(ne => {
Reflect.set(node, 'neInfo', ne.neType === nodeID ? ne : {}); // 处理非网元节点
return ne.neType === nodeID;
});
if (hasNe) {
return true;
}
if (notNeNodes.includes(nodeID)) { if (notNeNodes.includes(nodeID)) {
return true; return true;
} }
//(neTypeMap.get(nodeID),nodeID,node.neState)
// 处理网元节点
if (neTypeMap.has(nodeID)) {
// all NeInfo
Reflect.set(node, 'neInfoList', neTypeMap.get(nodeID));
Reflect.set(node, 'neInfo', neTypeMap.get(nodeID)[0] || {});
return true;
}
return false; return false;
} }
); );
@@ -215,7 +286,6 @@ function fnGraphDataLoad(reload: boolean = false) {
const edgeTarget: string = edge.target; const edgeTarget: string = edge.target;
const hasNeS = nf.some(n => n.id === edgeSource); const hasNeS = nf.some(n => n.id === edgeSource);
const hasNeT = nf.some(n => n.id === edgeTarget); const hasNeT = nf.some(n => n.id === edgeTarget);
// console.log(hasNeS, edgeSource, hasNeT, edgeTarget);
if (hasNeS && hasNeT) { if (hasNeS && hasNeT) {
return true; return true;
} }

View File

@@ -15,9 +15,9 @@ import { UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers'; import { CanvasRenderer } from 'echarts/renderers';
import { markRaw } from 'vue'; import { markRaw } from 'vue';
import useI18n from '@/hooks/useI18n'; import useI18n from '@/hooks/useI18n';
import { parseDateToStr } from '@/utils/date-utils';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { upfFlowData, upfFlowParse } from '../../hooks/useUPFTotalFlow'; import { upfFlowData, upfFlowParse } from '../../hooks/useUPFTotalFlow';
import { upfWhoId } from '../../hooks/useWS';
const { t } = useI18n(); const { t } = useI18n();
@@ -151,7 +151,7 @@ function handleRanderChart() {
top: '14%', top: '14%',
left: '4%', left: '4%',
right: '4%', right: '4%',
bottom: '12%', bottom: '16%',
containLabel: true, containLabel: true,
}, },
xAxis: { xAxis: {
@@ -160,7 +160,7 @@ function handleRanderChart() {
data: lineXTime, data: lineXTime,
axisLabel: { axisLabel: {
formatter: function (params: any) { formatter: function (params: any) {
return params.split(' ')[1]; return params;
}, },
fontSize: 14, fontSize: 14,
}, },
@@ -208,9 +208,10 @@ function fnGetInitData() {
listKPIData({ listKPIData({
neType: 'UPF', neType: 'UPF',
neId: '001', neId: upfWhoId.value,
startTime: nowDate - 5 * 60 * 1000, startTime: nowDate - 5 * 60 * 1000,
endTime: nowDate, endTime: nowDate,
interval: 5, // 5秒 interval: 5, // 5秒
sortField: 'timeGroup', sortField: 'timeGroup',
sortOrder: 'asc', sortOrder: 'asc',

View File

@@ -19,12 +19,15 @@ let dict: {
ueEventType: DictType[]; ueEventType: DictType[];
/**UE 事件CM状态 */ /**UE 事件CM状态 */
ueEventCmState: DictType[]; ueEventCmState: DictType[];
/**CDR SIP响应代码类别类型 */
cdrSipCodeCause: DictType[];
} = reactive({ } = reactive({
cdrSipCode: [], cdrSipCode: [],
cdrCallType: [], cdrCallType: [],
ueAauthCode: [], ueAauthCode: [],
ueEventType: [], ueEventType: [],
ueEventCmState: [], ueEventCmState: [],
cdrSipCodeCause: [],
}); });
onMounted(() => { onMounted(() => {
@@ -35,6 +38,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'),
getDict('cdr_sip_code_cause'),
]).then(resArr => { ]).then(resArr => {
if (resArr[0].status === 'fulfilled') { if (resArr[0].status === 'fulfilled') {
dict.cdrSipCode = resArr[0].value; dict.cdrSipCode = resArr[0].value;
@@ -51,6 +55,9 @@ onMounted(() => {
if (resArr[4].status === 'fulfilled') { if (resArr[4].status === 'fulfilled') {
dict.ueEventCmState = resArr[4].value; dict.ueEventCmState = resArr[4].value;
} }
if (resArr[5].status === 'fulfilled') {
dict.cdrSipCodeCause = resArr[5].value;
}
}); });
}); });
</script> </script>
@@ -75,16 +82,7 @@ onMounted(() => {
</span> </span>
</div> </div>
<div></div> <div></div>
<div> <div></div>
{{ t('views.dashboard.overview.userActivity.time') }}:&nbsp;
<span :title="item.data.releaseTime">
{{
typeof item.data.releaseTime === 'number'
? parseDateToStr(+item.data.releaseTime * 1000)
: item.data.releaseTime
}}
</span>
</div>
</div> </div>
<div class="card-cdr-item"> <div class="card-cdr-item">
<div> <div>
@@ -105,6 +103,16 @@ onMounted(() => {
</div> </div>
<div v-else></div> <div v-else></div>
</div> </div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:&nbsp;
<span :title="item.data.releaseTime">
{{
typeof item.data.releaseTime === 'number'
? parseDateToStr(+item.data.releaseTime * 1000)
: parseDateToStr(item.data.releaseTime)
}}
</span>
</div>
<div> <div>
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp; {{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span v-if="item.data.callType !== 'sms'"> <span v-if="item.data.callType !== 'sms'">
@@ -112,6 +120,11 @@ onMounted(() => {
:options="dict.cdrSipCode" :options="dict.cdrSipCode"
:value="item.data.cause" :value="item.data.cause"
value-default="0" value-default="0"
/>&nbsp;&nbsp;
<DictTag
:options="dict.cdrSipCodeCause"
:value="item.data.cause"
value-default="0"
/> />
</span> </span>
<span v-else> <span v-else>
@@ -135,15 +148,7 @@ onMounted(() => {
<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></div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<template v-if="item.data?.time">
{{ item.data.time }}
</template>
<template v-else>
{{ parseDateToStr(+item.data.timestamp * 1000) }}
</template>
</div>
</div> </div>
<div class="card-ue-w33" v-if="item.type === 'auth-result'"> <div class="card-ue-w33" v-if="item.type === 'auth-result'">
@@ -157,7 +162,16 @@ onMounted(() => {
TAC ID: <span>{{ item.data.tacID }}</span> TAC ID: <span>{{ item.data.tacID }}</span>
</div> </div>
</div> </div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<template v-if="item.data?.time">
{{ parseDateToStr(item.data.time) }}
</template>
<template v-else-if="item.data?.timestamp">
{{ parseDateToStr(+item.data.timestamp * 1000) }}
</template>
<template v-else> - </template>
</div>
<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>
@@ -187,7 +201,7 @@ onMounted(() => {
<span v-if="item.type === 'cm-state'"> <span v-if="item.type === 'cm-state'">
{{ {{
dict.ueEventType dict.ueEventType
.find(s => s.value === item.type) .find((s: any) => s.value === item.type)
?.label.replace('CM', 'ECM') ?.label.replace('CM', 'ECM')
}} }}
</span> </span>
@@ -198,16 +212,7 @@ onMounted(() => {
<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></div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<span :title="item.data?.timestamp">
{{
typeof item.data?.timestamp === 'number'
? parseDateToStr(+item.data?.timestamp * 1000)
: item.data?.timestamp
}}
</span>
</div>
</div> </div>
<div class="card-ue-w33" v-if="item.type === 'auth-result'"> <div class="card-ue-w33" v-if="item.type === 'auth-result'">
@@ -221,6 +226,20 @@ onMounted(() => {
TAC ID: <span>{{ item.data.tacID }}</span> TAC ID: <span>{{ item.data.tacID }}</span>
</div> </div>
</div> </div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<template v-if="item.data?.time">
{{ parseDateToStr(item.data.time) }}
</template>
<template v-else-if="item.data?.timestamp">
{{
typeof item.data?.timestamp === 'number'
? parseDateToStr(+item.data?.timestamp * 1000)
: parseDateToStr(item.data?.timestamp)
}}
</template>
<template v-else> - </template>
</div>
<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>

View File

@@ -16,6 +16,8 @@
.column { .column {
flex: 3; flex: 3;
position: relative; position: relative;
display: flex;
flex-direction: column;
} }
/* 边框 */ /* 边框 */
@@ -86,6 +88,7 @@
.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;
@@ -125,6 +128,19 @@
align-items: baseline; align-items: baseline;
width: 33%; width: 33%;
} }
/* Subscriber Information部分的宽度调整 */
.skim .inner .data .item:nth-child(1) {
width: 38%; /* Total Subscriber Base - 稍微宽一点 */
}
.skim .inner .data .item:nth-child(2) {
width: 33%; /* VoLTE - 保持不变 */
}
.skim .inner .data .item:nth-child(3) {
width: 29%; /* VoIP - 稍微窄一点 */
}
.skim .inner .data .item div { .skim .inner .data .item div {
font-size: 1.467rem; font-size: 1.467rem;
color: #fff; color: #fff;
@@ -179,6 +195,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 +276,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%;

View File

@@ -35,16 +35,20 @@ export const graphState = reactive<Record<string, any>>({
export const graphG6 = ref<any>(null); export const graphG6 = ref<any>(null);
/**图点击选择 */ /**图点击选择 */
export const graphNodeClickID = ref<string>('UPF'); export const graphNodeClickID = ref<string>('UPF_001');
/**图节点网元信息状态 */ /**图节点网元信息状态 */
export const graphNodeState = computed(() => export const graphNodeState = computed(() =>{
graphState.data.nodes.map((item: any) => ({ return graphState.data.nodes.map((item: any) => ({
id: item.id, id: item.id,
label: item.label, label: item.label,
neInfo: item.neInfo, neInfo: item.neInfo,
neState: item.neState, neState: item.neState,
neInfoList:item.neInfoList,
neStateMap: item.neStateMap,
})) }))
}
); );
/**图节点网元状态数量 */ /**图节点网元状态数量 */
@@ -68,48 +72,69 @@ export const graphNodeStateNum = computed(() => {
export const neStateRequestMap = ref<Map<string, boolean>>(new Map()); export const neStateRequestMap = ref<Map<string, boolean>>(new Map());
/**neStateParse 网元状态 数据解析 */ /**neStateParse 网元状态 数据解析 */
export function neStateParse(neType: string, data: Record<string, any>) { export function neStateParse(neType: string, data: Record<string, any>,neId: string) {
// console.log('neStateParse',neType, data, neId);
const { combos, edges, nodes } = graphState.data; const { combos, edges, nodes } = graphState.data;
const node = nodes.find((item: Record<string, any>) => item.id === neType); const node = nodes.find((item: Record<string, any>) => item.id === neType);
//console.log('neStateParse',node);
if (!node) return;
// 初始化状态映射
if (!node.neStateMap) node.neStateMap = {};
// 更新网元状态 // 更新网元状态
const newNeState = Object.assign(node.neState, data, { const newNeState :any = {
...data, // 先展开data对象
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'), refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'),
online: !!data.cpu, online: !!data.cpu,
}); neId: neId
};
// 如果是001更新节点状态。neInfo为主要的网元信息
if (node.neInfo && node.neInfo.neId === neId) {
Object.assign(node.neState, newNeState);
}
// 通过 ID 查询节点实例 //console.log(node.neState)
// 无论是否为主要网元,都更新状态映射
node.neStateMap[neId] = {...newNeState};
// 通过 ID 查询节点实例
const item = graphG6.value.findById(node.id); const item = graphG6.value.findById(node.id);
if (item) { if (item) {
const stateColor = newNeState.online ? '#52c41a' : '#f5222d'; // 状态颜色 // 检查当前节点下所有网元的状态
// 图片类型不能填充 const allStates = Object.values(node.neStateMap);
if (node.type.startsWith('image')) { // 判断状态颜色
// 更新节点 let stateColor = '#52c41a'; // 默认绿色(所有网元都正常)
if (node.label !== newNeState.neName) { if (allStates.some((state: any) => !state.online)) {
graphG6.value.updateItem(item, { // 如果有任何一个网元不正常
label: newNeState.neName, stateColor = allStates.every((state: any) => !state.online) ? '#f5222d' : '#faad14'; // 红色(全部不正常)或黄色(部分不正常)
});
}
// 设置状态
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
} else {
// 更新节点
graphG6.value.updateItem(item, {
label: newNeState.neName,
// neState: newNeState,
style: {
fill: stateColor, // 填充色
stroke: stateColor, // 填充色
},
// labelCfg: {
// style: {
// fill: '#ffffff', // 标签文本色
// },
// },
});
// 设置状态
graphG6.value.setItemState(item, 'stroke', newNeState.online);
} }
// 图片类型不能填充
if (node.type && node.type.startsWith('image')) {
// 更新节点
if (node.label !== newNeState.neType) {
graphG6.value.updateItem(item, {
label: newNeState.neType,
});
}
// 设置状态
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
} else {
// 更新节点
graphG6.value.updateItem(item, {
label: newNeState.neType,
style: {
fill: stateColor, // 填充色
stroke: stateColor, // 填充色
},
});
// 设置状态
graphG6.value.setItemState(item, 'stroke', newNeState.online);
}
} }
// 设置边状态 // 设置边状态
@@ -167,6 +192,6 @@ export function topologyReset() {
nodes: [], nodes: [],
}; };
graphG6.value = null; graphG6.value = null;
graphNodeClickID.value = 'UPF'; graphNodeClickID.value = 'UPF_001';
neStateRequestMap.value = new Map(); neStateRequestMap.value = new Map();
} }

View File

@@ -1,5 +1,5 @@
import { parseDateToStr } from '@/utils/date-utils'; import { parseDateToStr } from '@/utils/date-utils';
import { parseSizeFromBits, parseSizeFromKbs } from '@/utils/parse-utils'; import { parseSizeFromByte, parseSizeFromKbs } from '@/utils/parse-utils';
import { ref } from 'vue'; import { ref } from 'vue';
type FDType = { type FDType = {
@@ -23,7 +23,7 @@ export const upfFlowData = ref<FDType>({
/**UPF-流量数据 数据解析 */ /**UPF-流量数据 数据解析 */
export function upfFlowParse(data: Record<string, string>) { export function upfFlowParse(data: Record<string, string>) {
upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup'])); upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup'], 'HH:mm:ss'));
const upN3 = parseSizeFromKbs(+data['UPF.03'], 5); const upN3 = parseSizeFromKbs(+data['UPF.03'], 5);
upfFlowData.value.lineYUp.push(upN3[0]); upfFlowData.value.lineYUp.push(upN3[0]);
const downN6 = parseSizeFromKbs(+data['UPF.06'], 5); const downN6 = parseSizeFromKbs(+data['UPF.06'], 5);
@@ -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 = {
@@ -84,9 +79,9 @@ export function upfTFParse(day: string, data: Record<string, number>) {
let { up, down } = data; let { up, down } = data;
upfTotalFlow.value[day] = { upfTotalFlow.value[day] = {
up: up, up: up,
upFrom: parseSizeFromBits(up), upFrom: parseSizeFromByte(up),
down: down, down: down,
downFrom: parseSizeFromBits(down), downFrom: parseSizeFromByte(down),
requestFlag: false, requestFlag: false,
}; };
} }

View File

@@ -96,11 +96,7 @@ export function eventListParse(
eventData.value.push(v); eventData.value.push(v);
} }
} }
// 激活选中
// 有数据进行排序
if (eventData.value.length > 5) {
eventData.value.sort((a, b) => b.eTime - a.eTime);
}
if (eventData.value.length > 0) { if (eventData.value.length > 0) {
eventId.value = eventData.value[0].eId; eventId.value = eventData.value[0].eId;
} }

View File

@@ -2,6 +2,7 @@ import { RESULT_CODE_ERROR } from '@/constants/result-constants';
import { OptionsType, WS } from '@/plugins/ws-websocket'; import { OptionsType, WS } from '@/plugins/ws-websocket';
import { onBeforeUnmount, ref } from 'vue'; import { onBeforeUnmount, ref } from 'vue';
import { import {
eventData,
eventListParse, eventListParse,
eventItemParseAndPush, eventItemParseAndPush,
userActivityReset, userActivityReset,
@@ -16,10 +17,7 @@ import { topologyReset, neStateParse, neStateRequestMap } from './useTopology';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
/**UPF-的Id */ /**UPF-的Id */
export const upfWhoId = ref<any>(''); export const upfWhoId = ref<any>('001');
/**UPF-的RmUid */
export const upfWhoRmUid = ref<any>('');
/**websocket连接 */ /**websocket连接 */
export default function useWS() { export default function useWS() {
@@ -33,7 +31,6 @@ export default function useWS() {
/**接收数据后回调 */ /**接收数据后回调 */
function wsMessage(res: Record<string, any>) { function wsMessage(res: Record<string, any>) {
//console.log(res);
const { code, requestId, data } = res; const { code, requestId, data } = res;
if (code === RESULT_CODE_ERROR) { if (code === RESULT_CODE_ERROR) {
console.warn(res.msg); console.warn(res.msg);
@@ -42,7 +39,8 @@ export default function useWS() {
// 网元状态 // 网元状态
if (requestId && requestId.startsWith('neState')) { if (requestId && requestId.startsWith('neState')) {
const neType = requestId.split('_')[1]; const neType = requestId.split('_')[1];
neStateParse(neType, data); const neId = requestId.split('_')[2];
neStateParse(neType, data, neId);
return; return;
} }
@@ -52,28 +50,31 @@ export default function useWS() {
case 'amf_1010_001': case 'amf_1010_001':
if (Array.isArray(data.rows)) { if (Array.isArray(data.rows)) {
eventListParse('amf_ue', data); eventListParse('amf_ue', data);
eventData.value.sort((a, b) => b.eTime - a.eTime);
} }
break; break;
// MME_UE会话事件 // MME_UE会话事件
case 'mme_1011_001': case 'mme_1011_001':
if (Array.isArray(data.rows)) { if (Array.isArray(data.rows)) {
eventListParse('mme_ue', data); eventListParse('mme_ue', data);
eventData.value.sort((a, b) => b.eTime - a.eTime);
} }
break; break;
// IMS_CDR会话事件 // IMS_CDR会话事件
case 'ims_1005_001': case 'ims_1005_001':
if (Array.isArray(data.rows)) { if (Array.isArray(data.rows)) {
eventListParse('ims_cdr', data); eventListParse('ims_cdr', data);
eventData.value.sort((a, b) => b.eTime - a.eTime);
} }
break; break;
//UPF-总流量数 //UPF-总流量数
case 'upf_001_0': case `upf_${upfWhoId.value}_0`:
upfTFParse('0', data); upfTFParse('0', data);
break; break;
case 'upf_001_7': case `upf_${upfWhoId.value}_7`:
upfTFParse('7', data); upfTFParse('7', data);
break; break;
case 'upf_001_30': case `upf_${upfWhoId.value}_30`:
upfTFParse('30', data); upfTFParse('30', data);
break; break;
} }
@@ -83,25 +84,25 @@ export default function useWS() {
} }
switch (data.groupId) { switch (data.groupId) {
// kpiEvent 指标UPF // kpiEvent 指标UPF
case '12_' + upfWhoId.value: 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));
} }
break; break;
// MME_UE会话事件 // MME_UE会话事件
case '1011': case '1011_001':
if (data.data) { if (data.data) {
queue.add(() => eventItemParseAndPush('mme_ue', data.data)); queue.add(() => eventItemParseAndPush('mme_ue', data.data));
} }
break; break;
// IMS_CDR会话事件 // IMS_CDR会话事件
case '1005': case '1005_001':
if (data.data) { if (data.data) {
queue.add(() => eventItemParseAndPush('ims_cdr', data.data)); queue.add(() => eventItemParseAndPush('ims_cdr', data.data));
} }
@@ -118,11 +119,11 @@ export default function useWS() {
upfTotalFlow.value[day].requestFlag = true; upfTotalFlow.value[day].requestFlag = true;
ws.send({ ws.send({
requestId: `upf_001_${day}`, requestId: `upf_${upfWhoId.value}_${day}`,
type: 'upf_tf', type: 'upf_tf',
data: { data: {
neType: 'UPF', neType: 'UPF',
neId: '001', neId: upfWhoId.value,
day: Number(day), day: Number(day),
}, },
}); });
@@ -172,13 +173,13 @@ export default function useWS() {
}); });
} }
/**重新发送至UPF 12_neId */ /**重新发送至UPF 10_UPF_neId */
function reSendUPF(neId: string) { function reSendUPF(neId: string) {
upfWhoId.value = neId; upfWhoId.value = neId;
//初始时时无需还原全部属性以及关闭 //初始时时无需还原全部属性以及关闭
if (ws.state() === WebSocket.OPEN) { if (ws.state() === WebSocket.OPEN) {
ws.close(); ws.close();
userActivityReset(); // userActivityReset();
upfTotalFlowReset(); upfTotalFlowReset();
neStateRequestMap.value = new Map(); neStateRequestMap.value = new Map();
//topologyReset(); //topologyReset();
@@ -188,12 +189,12 @@ export default function useWS() {
params: { params: {
/**订阅通道组 /**订阅通道组
* *
* 指标UPF (GroupID:12_neId) * 指标UPF (GroupID:10_neType_neId)
* AMF_UE会话事件(GroupID:1010_neId) * 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_' + neId + ',1010,1011,1005', subGroupID: `10_UPF_${neId},1010_001,1011_001,1005_001`,
}, },
onmessage: wsMessage, onmessage: wsMessage,
onerror: (ev: any) => { onerror: (ev: any) => {

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue'; import { onBeforeUnmount, onMounted, reactive, ref, nextTick } from 'vue';
import svgBase from '@/assets/svg/base.svg'; import svgBase from '@/assets/svg/base.svg';
import svgUserIMS from '@/assets/svg/userIMS.svg'; import svgUserIMS from '@/assets/svg/userIMS.svg';
import svgUserSMF from '@/assets/svg/userSMF.svg'; import svgUserSMF from '@/assets/svg/userSMF.svg';
@@ -13,6 +13,8 @@ import { listUDMSub } from '@/api/neData/udm_sub';
import { listUENumBySMF } from '@/api/neUser/smf'; import { listUENumBySMF } from '@/api/neUser/smf';
import { listUENumByIMS } from '@/api/neUser/ims'; import { listUENumByIMS } from '@/api/neUser/ims';
import { listBase5G } from '@/api/neUser/base5G'; import { listBase5G } from '@/api/neUser/base5G';
import { listIMSSub } from '@/api/neData/ims_sub';
import { listUDMAuth } from '@/api/neData/voip_auth';
import { import {
graphNodeClickID, graphNodeClickID,
graphState, graphState,
@@ -36,6 +38,43 @@ const appStore = useAppStore();
const { t } = useI18n(); const { t } = useI18n();
const { wsSend, userActivitySend, upfTFSend, reSendUPF } = useWS(); const { wsSend, userActivitySend, upfTFSend, reSendUPF } = useWS();
// const viewportDom = ref<HTMLElement | null>(null);
//
// // 定义 resize 处理函数,确保能正确移除事件监听器
// const handleResize = () => {
// setTimeout(calculateScale, 100);
// };
//
//
//
//
//
// // 计算缩放比例的函数
// const calculateScale = () => {
// const container = document.querySelector('.dashboard-container') as HTMLElement;
// const wrapper = document.querySelector('.dashboard-wrapper') as HTMLElement;
//
// if (!container || !wrapper) return;
//
// const containerWidth = 1400; // 设计宽度
// const containerHeight = 900; // 设计高度
//
// // 获取可用空间减去padding
// const availableWidth = wrapper.clientWidth - 40;
// const availableHeight = wrapper.clientHeight - 40;
//
// // 计算缩放比例
// const scaleX = availableWidth / containerWidth;
// const scaleY = availableHeight / containerHeight;
//
// // 选择较小的比例,确保内容完全可见
// const scale = Math.min(scaleX, scaleY);
//
//
// container.style.transform = `scale(${scale})`;
// container.style.transformOrigin = 'center center';
// };
/**概览状态类型 */ /**概览状态类型 */
type SkimStateType = { type SkimStateType = {
/**UDM签约用户数量 */ /**UDM签约用户数量 */
@@ -52,6 +91,10 @@ type SkimStateType = {
enbNum: number; enbNum: number;
/**4G在线用户数量 */ /**4G在线用户数量 */
enbUeNum: number; enbUeNum: number;
/**IMS用户数量 */
imsUserNum: number;
/**VOIP用户数量 */
voipUserNum: number;
}; };
/**概览状态信息 */ /**概览状态信息 */
@@ -63,6 +106,8 @@ let skimState: SkimStateType = reactive({
gnbUeNum: 0, gnbUeNum: 0,
enbNum: 0, enbNum: 0,
enbUeNum: 0, enbUeNum: 0,
imsUserNum: 0,
voipUserNum: 0,
}); });
/**网元参数 */ /**网元参数 */
@@ -72,6 +117,7 @@ let neCascaderOptions = ref<Record<string, any>[]>([]);
const viewportDom = ref<HTMLElement | null>(null); const viewportDom = ref<HTMLElement | null>(null);
const { isFullscreen, toggle } = useFullscreen(viewportDom); const { isFullscreen, toggle } = useFullscreen(viewportDom);
let initFlag = false;
/**10s调度器 */ /**10s调度器 */
const interval10s = ref<any>(null); const interval10s = ref<any>(null);
@@ -83,36 +129,38 @@ function fnGetNeState() {
// 获取节点状态 // 获取节点状态
for (const node of graphState.data.nodes) { for (const node of graphState.data.nodes) {
if (notNeNodes.includes(node.id)) continue; 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({ const neInfoList = node.neInfoList || [];
requestId: `neState_${neType}_${neId}`, if (neInfoList.length === 0) continue;
type: 'ne_state',
data: { for (const neInfo of neInfoList) {
neType: neType, if (!neInfo.neType || !neInfo.neId) continue;
neId: neId,
}, wsSend({
}); requestId: `neState_${neInfo.neType}_${neInfo.neId}`,
type: 'ne_state',
data: {
neType: neInfo.neType,
neId: neInfo.neId,
},
});
}
} }
} }
/**获取概览信息 */ /**获取概览信息 */
async function fnGetSkim() { async function fnGetSkim() {
const neHandlers = new Map([ const neHandlers = new Map([
[ // [
'UDM', // 'UDM',
{ // {
request: (neId: string) => // request: (neId: string) =>
listUDMSub({ neId: neId, pageNum: 1, pageSize: 1 }), // listUDMSub({ neId: neId, pageNum: 1, pageSize: 1 }),
process: (res: any) => // process: (res: any) =>
res.code === RESULT_CODE_SUCCESS && // res.code === RESULT_CODE_SUCCESS &&
(skimState.udmSubNum += res.total), // (skimState.udmSubNum += res.total),
}, // },
], // ],
[ [
'SMF', 'SMF',
{ {
@@ -168,9 +216,9 @@ async function fnGetSkim() {
const handler = neHandlers.get(child.neType); const handler = neHandlers.get(child.neType);
return handler return handler
? { ? {
promise: handler.request(child.neId), promise: handler.request(child.neId),
process: handler.process, process: handler.process,
} }
: null; : null;
}) })
.filter(Boolean) || [] .filter(Boolean) || []
@@ -180,17 +228,42 @@ async function fnGetSkim() {
// 重置 // 重置
Object.assign(skimState, { Object.assign(skimState, {
udmSubNum: 0, // udmSubNum: 0,
smfUeNum: 0, smfUeNum: 0,
imsUeNum: 0, imsUeNum: 0,
gnbNum: 0, gnbNum: 0,
gnbUeNum: 0, gnbUeNum: 0,
enbNum: 0, enbNum: 0,
enbUeNum: 0, enbUeNum: 0,
// imsUserNum: 0,
// voipUserNum: 0,
}); });
results.forEach((result, index) => { results.forEach((result: any, index: any) => {
if (result.status === 'fulfilled') { if (result.status === 'fulfilled') {
requests[index].process(result.value); requests[index].process(result.value);
} else {
requests[index].process(0);
}
});
// UDM
listUDMSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
skimState.udmSubNum = res.total;
}
});
// IMS用户数
listIMSSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
skimState.imsUserNum = res.total;
}
});
// VOIP用户数
listUDMAuth({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
skimState.voipUserNum = res.total;
} }
}); });
} }
@@ -207,23 +280,23 @@ function loadData() {
interval10s.value = setInterval(() => { interval10s.value = setInterval(() => {
if (!interval10s.value) return; if (!interval10s.value) return;
if (upfTFActive.value === '0') { if (upfTFActive.value === '0') {
upfTFSend('7');
upfTFActive.value = '7'; upfTFActive.value = '7';
} else if (upfTFActive.value === '7') { } else if (upfTFActive.value === '7') {
upfTFSend('30');
upfTFActive.value = '30'; upfTFActive.value = '30';
} else if (upfTFActive.value === '30') { } else if (upfTFActive.value === '30') {
upfTFSend('0');
upfTFActive.value = '0'; upfTFActive.value = '0';
} }
upfTFSend('0');
upfTFSend('7');
upfTFSend('30');
}, 10_000); }, 10_000);
clearInterval(interval5s.value); clearInterval(interval5s.value);
interval5s.value = setInterval(() => { interval5s.value = setInterval(() => {
if (!interval5s.value) return; if (!interval5s.value || !initFlag) return;
fnGetSkim(); // 获取概览信息 fnGetSkim(); // 获取概览信息
fnGetNeState(); // 获取网元状态 fnGetNeState(); // 获取网元状态
}, 5_000); }, 10_000);
} }
/**栏目信息跳转 */ /**栏目信息跳转 */
@@ -234,15 +307,8 @@ function fnToRouter(name: string, query?: any) {
/**网元参数 */ /**网元参数 */
let neOtions = ref<Record<string, any>[]>([]); let neOtions = ref<Record<string, any>[]>([]);
/**UPF网元Id */
let queryParams = reactive({
/**45G类型 */
neRealId: '',
});
// UPF实时流量下拉框选择 // UPF实时流量下拉框选择
function fnSelectNe(value: any, option: any) { function fnSelectNe(value: any, option: any) {
queryParams.neRealId = value;
upfWhoId.value = value; upfWhoId.value = value;
reSendUPF(value); reSendUPF(value);
// upfTotalFlow.value.map((item: any) => { // upfTotalFlow.value.map((item: any) => {
@@ -255,32 +321,118 @@ function fnSelectNe(value: any, option: any) {
// loadData(); // loadData();
} }
let udmNeId = ref<string>('001');
let udmOtions = ref<Record<string, any>[]>([]);
let onlineOtions = ref<Record<string, any>[]>([]);
/**用户数量-选择UDM */
function fnSelectUDM(e: any) {
udmNeId.value = e.key;
listUDMSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
skimState.udmSubNum = res.total;
}
});
// 更新IMS用户数
listIMSSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
skimState.imsUserNum = res.total;
}
});
// 更新VOIP用户数
listUDMAuth({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
skimState.voipUserNum = res.total;
}
});
}
/**资源控制-选择NE */
function fnSelectNeRe(e: any) {
graphNodeClickID.value = e.key;
}
//
// 定义一个方法返回 views 容器 // 定义一个方法返回 views 容器
const getPopupContainer = () => { const getPopupContainer = () => {
// 使用 ref 或其他方式来引用你的 views 容器 // 使用 ref 或其他方式来引用你的 views 容器
// 如果 views 容器直接在这个组件内部,你可以使用 ref // 如果 views 容器直接在这个组件内部,你可以使用 ref
// 但在这个例子中,我们假设它是通过类名来获取的 // 但在这个例子中,我们假设它是通过类名来获取的
// return document.querySelector('.dashboard-wrapper');
return document.querySelector('.viewport'); return document.querySelector('.viewport');
}; };
onMounted(() => { onMounted(() => {
// // 使用自定义缩放方案
// nextTick(() => {
// // 确保DOM完全渲染
// setTimeout(() => {
// calculateScale();
// }, 100);
//
// // 监听窗口大小变化
// window.addEventListener('resize', handleResize);
// });
neInfoStore neInfoStore
.fnNelist() .fnNelist()
.then(res => { .then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) { if (res.data.length > 0) {
// UPF
let arr: Record<string, any>[] = []; let arr: Record<string, any>[] = [];
res.data.forEach(i => { res.data.forEach(i => {
if (i.neType === 'UPF') { if (i.neType === 'UPF') {
arr.push({ value: i.neId, label: i.neName, rmUid: i.rmUid }); arr.push({ value: i.neId, label: i.neName, rmUid: i.rmUid });
} }
}); });
neOtions.value = arr; neOtions.value = arr;
if (arr.length > 0) { if (arr.length > 0) {
//queryParams.neRealId = arr[0].value; //queryParams.neRealId = arr[0].value;
fnSelectNe(arr[0].value, arr[0]); fnSelectNe(arr[0].value, arr[0]);
} }
//online Ne
let onlineArr: Record<string, any>[] = [];
// UDM
let arr1: Record<string, any>[] = [];
res.data.forEach((v: any) => {
if (
v.status &&
[
'UDM',
'UPF',
'AUSF',
'PCF',
'SMF',
'AMF',
'OMC',
'SMSC',
'IMS',
'MME',
].includes(v.neType)
) {
onlineArr.push({
value: v.neType + '_' + v.neId,
label: v.neName,
rmUid: v.rmUid,
});
}
if (v.neType === 'UDM') {
arr1.push({ value: v.neId, label: v.neName, rmUid: v.rmUid });
}
});
udmOtions.value = arr1;
onlineOtions.value = onlineArr;
if (arr1.length > 0) {
fnSelectUDM({ key: arr1[0].value });
}
if (onlineArr.length > 0) {
fnSelectNeRe({ key: onlineArr[0].value });
}
// 过滤不可用的网元 // 过滤不可用的网元
neCascaderOptions.value = neInfoStore.getNeCascaderOptions.filter( neCascaderOptions.value = neInfoStore.getNeCascaderOptions.filter(
@@ -304,6 +456,7 @@ onMounted(() => {
} }
}) })
.finally(() => { .finally(() => {
initFlag = true;
fnGetSkim().then(() => { fnGetSkim().then(() => {
loadData(); loadData();
}); });
@@ -315,79 +468,128 @@ onBeforeUnmount(() => {
interval10s.value = null; interval10s.value = null;
clearInterval(interval5s.value); clearInterval(interval5s.value);
interval5s.value = null; interval5s.value = null;
initFlag = false;
// // 清理事件监听和样式
// window.removeEventListener('resize', handleResize);
}); });
</script> </script>
<template> <template>
<div class="viewport" ref="viewportDom"> <div class="viewport" ref="viewportDom">
<div class="brand"> <div class="brand">
<div <div
class="brand-title" class="brand-title"
@click="toggle" @click="toggle"
:title="t('views.dashboard.overview.fullscreen')" :title="t('views.dashboard.overview.fullscreen')"
> >
{{ t('views.dashboard.overview.title') }} {{ t('views.dashboard.overview.title') }}
<FullscreenExitOutlined v-if="isFullscreen" /> <FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else /> <FullscreenOutlined v-else />
</div> </div>
<div class="brand-desc">{{ appStore.appName }}</div> <div class="brand-desc">{{ appStore.appName }}</div>
</div> </div>
<div class="column"> <div class="column">
<!--概览--> <!--概览-->
<div class="skim panel"> <div class="skim panel">
<div class="inner"> <div class="inner" style="padding: 0.8rem 1.5rem">
<h3> <h3 style="display: flex; align-items: center; justify-content: space-between; margin: 0 0 0.3rem 0; padding: 0; line-height: 1; height: 20px; font-size: 0.833rem">
<span style="display: flex; align-items: center">
<IdcardOutlined style="color: #68d8fe" />&nbsp;&nbsp; <IdcardOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.skim.userTitle') }} {{ t('views.dashboard.overview.skim.userTitle') }}
</span>
<a-dropdown
:trigger="['click']"
:get-Popup-Container="getPopupContainer"
style="margin-left: 8px"
>
<div class="toDeep-text" style="padding: 2px 6px; font-size: 0.7rem; line-height: 1; height: 16px; display: inline-flex; align-items: center">
{{ udmOtions.find(item => item.value === udmNeId)?.label || 'UDM' }}
<DownOutlined style="margin-left: 4px; font-size: 9px" />
</div>
<template #overlay>
<a-menu @click="fnSelectUDM">
<a-menu-item
v-for="v in udmOtions"
:key="v.value"
:disabled="udmNeId === v.value"
>
{{ v.label }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</h3> </h3>
<div class="data"> <div class="data">
<div <div
class="item toRouter" class="item toRouter"
@click="fnToRouter('Sub_2010')" :title="t('views.dashboard.overview.Users')"
:title="t('views.dashboard.overview.toRouter')"
> >
<div> <div @click="fnToRouter('Sub_2010')">
<UserOutlined <UserOutlined
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem" style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
/> />
{{ skimState.udmSubNum }} {{ skimState.udmSubNum }}
</div> </div>
<span> <span>{{ t('views.dashboard.overview.skim.users') }}
{{ t('views.dashboard.overview.skim.users') }} <!-- <a-dropdown-->
<!-- :trigger="['click']"-->
<!-- :get-Popup-Container="getPopupContainer"-->
<!-- >-->
<!-- <div class="toDeep-text">-->
<!-- {{ t('views.dashboard.overview.skim.users') }}-->
<!-- <DownOutlined style="margin-left: 12px; font-size: 12px" />-->
<!-- </div>-->
<!-- <template #overlay>-->
<!-- <a-menu @click="fnSelectUDM">-->
<!-- <a-menu-item-->
<!-- v-for="v in udmOtions"-->
<!-- :key="v.value"-->
<!-- :disabled="udmNeId === v.value"-->
<!-- >-->
<!-- {{ v.label }}-->
<!-- </a-menu-item>-->
<!-- </a-menu>-->
<!-- </template>-->
<!-- </a-dropdown>-->
</span> </span>
</div> </div>
<div <div
class="item toRouter" class="item toRouter"
@click="fnToRouter('Ims_2080')" @click="fnToRouter('ImsUDM_2012')"
:title="t('views.dashboard.overview.toRouter')" :title="t('views.dashboard.overview.IMSUsers')"
style="margin: 0 12px" style="margin: 0 12px"
> >
<div> <div>
<img :src="svgUserIMS" style="width: 18px; margin-right: 8px" /> <UserOutlined
{{ skimState.imsUeNum }} style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
/>
{{ skimState.imsUserNum }}
</div> </div>
<span> <span>
{{ t('views.dashboard.overview.skim.imsUeNum') }} {{t('views.dashboard.overview.skim.ims')}}
</span> </span>
</div> </div>
<div <div
class="item toRouter" class="item toRouter"
@click="fnToRouter('Ue_2081')" @click="fnToRouter('Voip_2011')"
:title="t('views.dashboard.overview.toRouter')" :title="t('views.dashboard.overview.VoIPUsers')"
> >
<div> <div>
<img :src="svgUserSMF" style="width: 18px; margin-right: 8px" /> <UserOutlined
{{ skimState.smfUeNum }} style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
/>
{{ skimState.voipUserNum }}
</div> </div>
<span> <span>
{{ t('views.dashboard.overview.skim.smfUeNum') }} {{t('views.dashboard.overview.skim.voip')}}
</span> </span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="skim panel base">
<div class="skim panel base" v-perms:has="['dashboard:overview:gnbBase']">
<div class="inner"> <div class="inner">
<h3> <h3>
<GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp; 5G <GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp; 5G
@@ -396,8 +598,8 @@ onBeforeUnmount(() => {
<div class="data"> <div class="data">
<div <div
class="item toRouter" class="item toRouter"
@click="fnToRouter('Base5G_2082', { neType: 'AMF' })" @click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
:title="t('views.dashboard.overview.toRouter')" :title="t('views.dashboard.overview.FivegNodeN')"
> >
<div style="align-items: flex-start"> <div style="align-items: flex-start">
<img <img
@@ -410,8 +612,8 @@ onBeforeUnmount(() => {
</div> </div>
<div <div
class="item toRouter" class="item toRouter"
@click="fnToRouter('Base5G_2082', { neType: 'AMF' })" @click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
:title="t('views.dashboard.overview.toRouter')" :title="t('views.dashboard.overview.Fiveusers')"
> >
<div style="align-items: flex-start"> <div style="align-items: flex-start">
<UserOutlined <UserOutlined
@@ -424,7 +626,7 @@ onBeforeUnmount(() => {
</div> </div>
</div> </div>
</div> </div>
<div class="skim panel base"> <div class="skim panel base" v-perms:has="['dashboard:overview:enbBase']">
<div class="inner"> <div class="inner">
<h3> <h3>
<GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp; 4G <GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp; 4G
@@ -433,8 +635,8 @@ onBeforeUnmount(() => {
<div class="data"> <div class="data">
<div <div
class="item toRouter" class="item toRouter"
@click="fnToRouter('Base5G_2082', { neType: 'MME' })" @click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
:title="t('views.dashboard.overview.toRouter')" :title="t('views.dashboard.overview.FourgNodeN')"
> >
<div style="align-items: flex-start"> <div style="align-items: flex-start">
<img <img
@@ -447,8 +649,8 @@ onBeforeUnmount(() => {
</div> </div>
<div <div
class="item toRouter" class="item toRouter"
@click="fnToRouter('Base5G_2082', { neType: 'MME' })" @click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
:title="t('views.dashboard.overview.toRouter')" :title="t('views.dashboard.overview.Fourusers')"
> >
<div style="align-items: flex-start"> <div style="align-items: flex-start">
<UserOutlined <UserOutlined
@@ -462,6 +664,41 @@ onBeforeUnmount(() => {
</div> </div>
</div> </div>
<!-- Online Information -->
<div class="skim panel base" v-perms:has="['dashboard:overview:onlineInfo']">
<div class="inner">
<h3>
<IdcardOutlined style="color: #68d8fe" />&nbsp;&nbsp; {{ t('views.dashboard.overview.skim.onlineinfo') }}
</h3>
<div class="data">
<div
class="item toRouter"
@click="fnToRouter('Ims_2080')"
:title="t('views.dashboard.overview.VoNR')"
v-perms:has="['dashboard:overview:imsUeNum']"
>
<div style="align-items: flex-start">
<img :src="svgUserIMS" style="width: 18px; margin-right: 8px; height: 2rem" />
{{ 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.sessions')"
v-perms:has="['dashboard:overview:smfUeNum']"
>
<div style="align-items: flex-start">
<img :src="svgUserSMF" style="width: 18px; margin-right: 8px; height: 2rem" />
{{ skimState.smfUeNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.smfUeNum') }}</span>
</div>
</div>
</div>
</div>
<!-- 用户行为 --> <!-- 用户行为 -->
<div class="userActivity panel"> <div class="userActivity panel">
<div class="inner"> <div class="inner">
@@ -474,6 +711,33 @@ onBeforeUnmount(() => {
</div> </div>
</div> </div>
</div> </div>
<!-- <div class="userActivity panel">-->
<!-- <div class="inner">-->
<!-- <h3 style="display: flex; align-items: center">-->
<!-- <DashboardOutlined style="color: #68d8fe" />&nbsp;&nbsp;-->
<!-- {{ t('views.dashboard.overview.resources.title') }}-->
<!-- <a-dropdown-->
<!-- :trigger="['click']"-->
<!-- :get-Popup-Container="getPopupContainer"-->
<!-- >-->
<!-- <div class="toDeep-text">-->
<!-- {{ graphNodeClickID }}-->
<!-- <DownOutlined style="margin-left: 12px; font-size: 12px" />-->
<!-- </div>-->
<!-- <template #overlay>-->
<!-- <a-menu @click="fnSelectNeRe">-->
<!-- <a-menu-item v-for="v in onlineOtions" :key="v.value">-->
<!-- {{ v.label }}-->
<!-- </a-menu-item>-->
<!-- </a-menu>-->
<!-- </template>-->
<!-- </a-dropdown>-->
<!-- </h3>-->
<!-- <div class="chart">-->
<!-- <NeResources />-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
</div> </div>
<div class="column" style="flex: 4; margin: 1.333rem 0.833rem 0"> <div class="column" style="flex: 4; margin: 1.333rem 0.833rem 0">
<!-- 实时流量 --> <!-- 实时流量 -->
@@ -481,15 +745,15 @@ onBeforeUnmount(() => {
<div class="inner"> <div class="inner">
<h3 <h3
class="toRouter" class="toRouter"
:title="t('views.dashboard.overview.toRouter')" :title="t('views.dashboard.overview.UPFjump')"
style="display: flex; align-items: center" style="display: flex; align-items: center"
> >
<AreaChartOutlined style="color: #68d8fe" />&nbsp;&nbsp; <AreaChartOutlined style="color: #68d8fe" />&nbsp;&nbsp;
<span @click="fnToRouter('GoldTarget_2104')">{{ <span @click="fnToRouter('GoldTarget_2104')">{{
t('views.dashboard.overview.upfFlow.title') t('views.dashboard.overview.upfFlow.title')
}}</span> }}</span>
<a-select <a-select
v-model:value="queryParams.neRealId" v-model:value="upfWhoId"
:options="neOtions" :options="neOtions"
:get-Popup-Container="getPopupContainer" :get-Popup-Container="getPopupContainer"
class="toDeep" class="toDeep"
@@ -509,7 +773,7 @@ onBeforeUnmount(() => {
<h3 <h3
class="toRouter" class="toRouter"
@click="fnToRouter('TopologyArchitecture_2128')" @click="fnToRouter('TopologyArchitecture_2128')"
:title="t('views.dashboard.overview.toRouter')" :title="t('views.dashboard.overview.Networkjump')"
> >
<span> <span>
<ApartmentOutlined style="color: #68d8fe" />&nbsp;&nbsp; <ApartmentOutlined style="color: #68d8fe" />&nbsp;&nbsp;
@@ -585,8 +849,8 @@ onBeforeUnmount(() => {
<div class="inner"> <div class="inner">
<h3 <h3
class="toRouter" class="toRouter"
@click="fnToRouter('HistoryAlarm_2097')" @click="fnToRouter('ActiveAlarm_2088')"
:title="t('views.dashboard.overview.toRouter')" :title="t('views.dashboard.overview.Alarmjump')"
> >
<PieChartOutlined style="color: #68d8fe" />&nbsp;&nbsp; <PieChartOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.alarmTypeBar.alarmSum') }} {{ t('views.dashboard.overview.alarmTypeBar.alarmSum') }}
@@ -599,22 +863,62 @@ onBeforeUnmount(() => {
<!-- 资源情况 --> <!-- 资源情况 -->
<div class="resources panel"> <div class="resources panel">
<div class="inner"> <div class="inner">
<h3> <h3 style="display: flex; align-items: center">
<DashboardOutlined style="color: #68d8fe" />&nbsp;&nbsp; <DashboardOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.resources.title') }} {{ t('views.dashboard.overview.resources.title') }}
{{ graphNodeClickID }} <a-dropdown
:trigger="['click']"
:get-Popup-Container="getPopupContainer"
>
<div class="toDeep-text">
{{ graphNodeClickID }}
<DownOutlined style="margin-left: 12px; font-size: 12px" />
</div>
<template #overlay>
<a-menu @click="fnSelectNeRe">
<a-menu-item v-for="v in onlineOtions" :key="v.value">
{{ v.label }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</h3> </h3>
<div class="chart"> <div class="chart">
<NeResources /> <NeResources />
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
</div>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
@import url('./css/index.css'); @import url('./css/index.css');
/* Dashboard 页面专用包装器,不影响其他页面 */
//.dashboard-wrapper {
// position: absolute;
// top: 0;
// left: 0;
// width: 100%;
// height: 100%;
// background: #0b1023;
// display: flex;
// align-items: center;
// justify-content: center;
// padding: 20px;
// overflow: hidden;
// box-sizing: border-box;
//}
//
//.dashboard-container {
// width: 1400px;
// height: 900px;
// background: #0b1023;
// position: relative;
// overflow: visible;
// flex-shrink: 0;
//}
.toDeep { .toDeep {
--editor-background-color: blue; --editor-background-color: blue;
} }
@@ -631,4 +935,15 @@ onBeforeUnmount(() => {
.toDeep :deep(.ant-select-selection-item) { .toDeep :deep(.ant-select-selection-item) {
color: #fff; color: #fff;
} }
.toDeep-text {
color: #4c9bfd !important;
font-size: 0.844rem !important;
position: relative !important;
line-height: 2rem !important;
white-space: nowrap !important;
text-align: start !important;
text-overflow: ellipsis !important;
overflow: hidden !important;
}
</style> </style>

View File

@@ -0,0 +1,305 @@
<script setup lang="ts">
import * as echarts from 'echarts/core';
import {
TitleComponent,
TitleComponentOption,
TooltipComponent,
TooltipComponentOption,
GridComponent,
GridComponentOption,
LegendComponent,
LegendComponentOption,
} from 'echarts/components';
import {
PieChart,
PieSeriesOption,
BarChart,
BarSeriesOption,
} from 'echarts/charts';
import { LabelLayout } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import { markRaw, onMounted, ref } from 'vue';
import { origGet, top3Sel } from '@/api/faultManage/actAlarm';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
const { t } = useI18n();
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
PieChart,
BarChart,
CanvasRenderer,
LabelLayout,
]);
type EChartsOption = echarts.ComposeOption<
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| LegendComponentOption
| PieSeriesOption
| BarSeriesOption
>;
/**图DOM节点实例对象 */
const alarmTypeBar = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
const alarmTypeBarChart = ref<any>(null);
/**告警类型数据 */
const alarmTypeType = ref<any>([
{
value: 0,
name: t('views.index.Critical'),
},
{
value: 0,
name: t('views.index.Major'),
},
{
value: 0,
name: t('views.index.Minor'),
},
{
value: 0,
name: t('views.index.Warning'),
},
// {
// value: 0,
// name: t('views.index.Event'),
// },
]);
/**告警类型Top数据 */
const alarmTypeTypeTop = ref<any>([
{ name: 'AMF', value: 0 },
{ name: 'UDM', value: 0 },
{ name: 'SMF', value: 0 },
]);
//
function initPicture() {
Promise.allSettled([origGet(), top3Sel()])
.then(resArr => {
if (resArr[0].status === 'fulfilled') {
const res0 = resArr[0].value;
if (res0.code === RESULT_CODE_SUCCESS && Array.isArray(res0.data)) {
for (const item of res0.data) {
let index = 0;
switch (item.name) {
case 'Critical':
index = 0;
break;
case 'Major':
index = 1;
break;
case 'Minor':
index = 2;
break;
case 'Warning':
index = 3;
break;
// case 'Event':
// index = 4;
// break;
}
alarmTypeType.value[index].value = Number(item.value);
}
}
}
if (resArr[1].status === 'fulfilled') {
const res1 = resArr[1].value;
if (res1.code === RESULT_CODE_SUCCESS && Array.isArray(res1.data)) {
alarmTypeTypeTop.value = alarmTypeTypeTop.value
.concat(res1.data)
.sort((a: any, b: any) => {
return b.value - a.value;
})
.slice(0, 3);
}
}
})
.then(() => {
const optionData: EChartsOption = {
title: [
{
text: 'Top3',
left: 'center',
top: '36%',
textStyle: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
},
],
grid: [
{ // 主图
top: '5%',
left: '20%',
right: '10%',
height: '35%'
},
{ // Top3
top: '50%',
left: '20%',
right: '10%',
height: '30%'
}
],
tooltip: {
axisPointer: { type: 'shadow' },
formatter: '{b} : {c}',
},
legend: {
show: false
},
xAxis: [
{
type: 'value',
gridIndex: 0,
show: false,
},
{
type: 'value',
gridIndex: 1,
show: false,
}
],
yAxis: [
{
type: 'category',
gridIndex: 0,
data: alarmTypeType.value.map((item: any) => item.name),
axisLabel: { color: '#fff', fontSize: 14,fontWeight: 'bold' },
axisLine: { show: false },
axisTick: { show: false },
inverse: true
},
{
type: 'category',
gridIndex: 1,
data: alarmTypeTypeTop.value.map((item: any) => item.name),
axisLabel: { color: '#fff', fontSize: 14,fontWeight: 'bold' },
axisLine: { show: false },
axisTick: { show: false },
inverse: true
}
],
series: [
// 四类型告警横向柱状图
{
type: 'bar',
xAxisIndex: 0,
yAxisIndex: 0,
barWidth: 18,
itemStyle: {
borderRadius: [0, 8, 8, 0],
color: function (params: any) {
// 渐变色
const colorArr = [
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#f5222d' },
{ offset: 1, color: '#fa8c16' }
]),
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#fa8c16' },
{ offset: 1, color: '#fadb14' }
]),
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#fadb14' },
{ offset: 1, color: '#1677ff' }
]),
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#1677ff' },
{ offset: 1, color: '#00fcff' }
])
];
return colorArr[params.dataIndex] || colorArr[3];
}
},
label: {
show: true,
position: 'right',
color: '#fff', //淡蓝色
fontWeight: 'bold',
fontSize: 16,
formatter: (params: any) => {
if (!params.value) return '';
return `${params.value}`;
},
},
data: alarmTypeType.value.map((item: any) => item.value),
zlevel: 2
},
// Top3横向柱状图
{
name: 'Top3',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 1,
barWidth: 18,
itemStyle: {
borderRadius: [0, 20, 20, 0], // 圆角(左上、右上、右下、左下)
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#f0f5ff' },
{ offset: 0.5, color: '#adc6ff' },
{ offset: 1, color: '#2f54eb' },
]), // 渐变
},
label: {
show: true,
position: 'right',
color: '#fff', //淡蓝色
fontWeight: 'bold',
fontSize: 16,
formatter: '{c}'
},
data: alarmTypeTypeTop.value.map((item: any) => item.value),
zlevel: 1
}
]
};
fnDesign(alarmTypeBar.value, optionData);
});
}
function fnDesign(container: HTMLElement | undefined, option: any) {
if (!container) return;
alarmTypeBarChart.value = markRaw(echarts.init(container, 'light'));
option && alarmTypeBarChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (alarmTypeBarChart.value) {
alarmTypeBarChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
onMounted(() => {
initPicture();
});
</script>
<template>
<div ref="alarmTypeBar" class="chart-container"></div>
</template>
<style lang="less" scoped>
.chart-container {
/* 设置图表容器大小和位置 */
width: 100%;
height: 100%;
}
</style>

View File

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

View File

@@ -0,0 +1,352 @@
<script setup lang="ts">
import { onMounted, ref, nextTick, watch } from 'vue';
import * as echarts from 'echarts/core';
import { GridComponent, GridComponentOption, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { graphNodeClickID, graphNodeState } from '../../hooks/useTopology';
import useI18n from '@/hooks/useI18n';
import { markRaw } from 'vue';
// 引入液体填充图表
import 'echarts-liquidfill';
const { t } = useI18n();
echarts.use([GridComponent, TooltipComponent, CanvasRenderer]);
type EChartsOption = echarts.ComposeOption<GridComponentOption>;
/**图DOM节点实例对象 */
const neResourcesDom = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
const neResourcesChart = ref<any>(null);
// 当前选中的网元ID
const currentNeId = ref('');
// 资源数据
const resourceData = ref({
neCpu: 1,
sysCpu: 1,
sysMem: 1,
sysDisk: 1
});
// 获取颜色
function getColorByValue(value: number) {
if (value >= 70) {
return ['#f5222d', '#ff7875']; // 红色
} else if (value >= 30) {
return ['#2f54eb', '#597ef7']; // 蓝色
} else {
return ['#52c41a', '#95de64']; // 绿色
}
}
/**图数据 */
const optionData: any = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c}%'
},
grid: {
top: '10%',
bottom: '5%',
left: '5%',
right: '5%',
containLabel: true
},
series: [
{
type: 'liquidFill',
radius: '50%',
center: ['15%', '35%'],
data: [resourceData.value.neCpu / 100],
name: t('views.dashboard.overview.resources.neCpu'),
color: getColorByValue(resourceData.value.neCpu),
backgroundStyle: {
color: 'rgba(10, 60, 160, 0.1)'
},
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.neCpu')}\n${resourceData.value.neCpu}%`;
},
textStyle: {
fontSize: 12,
color: '#fff'
}
}
},
outline: {
show: true,
borderDistance: 2,
itemStyle: {
borderColor: '#0a3ca0',
borderWidth: 1
}
}
},
{
type: 'liquidFill',
radius: '50%',
center: ['85%', '35%'],
data: [resourceData.value.sysCpu / 100],
name: t('views.dashboard.overview.resources.sysCpu'),
color: getColorByValue(resourceData.value.sysCpu),
backgroundStyle: {
color: 'rgba(10, 60, 160, 0.1)'
},
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.sysCpu')}\n${resourceData.value.sysCpu}%`;
},
textStyle: {
fontSize: 12,
color: '#fff'
}
}
},
outline: {
show: true,
borderDistance: 2,
itemStyle: {
borderColor: '#0a3ca0',
borderWidth: 1
}
}
},
{
type: 'liquidFill',
radius: '50%',
center: ['35%', '65%'],
data: [resourceData.value.sysMem / 100],
name: t('views.dashboard.overview.resources.sysMem'),
color: getColorByValue(resourceData.value.sysMem),
backgroundStyle: {
color: 'rgba(10, 60, 160, 0.1)'
},
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.sysMem')}\n${resourceData.value.sysMem}%`;
},
textStyle: {
fontSize: 12,
color: '#fff'
}
}
},
outline: {
show: true,
borderDistance: 2,
itemStyle: {
borderColor: '#0a3ca0',
borderWidth: 1
}
}
},
{
type: 'liquidFill',
radius: '50%',
center: ['65%', '65%'],
data: [resourceData.value.sysDisk / 100],
name: t('views.dashboard.overview.resources.sysDisk'),
color: getColorByValue(resourceData.value.sysDisk),
backgroundStyle: {
color: 'rgba(10, 60, 160, 0.1)'
},
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.sysDisk')}\n${resourceData.value.sysDisk}%`;
},
textStyle: {
fontSize: 12,
color: '#fff'
}
}
},
outline: {
show: true,
borderDistance: 2,
itemStyle: {
borderColor: '#0a3ca0',
borderWidth: 1
}
}
}
]
};
/**图数据渲染 */
function handleRanderChart(
container: HTMLElement | undefined,
option: any
) {
if (!container) return;
neResourcesChart.value = markRaw(echarts.init(container));
option && neResourcesChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (neResourcesChart.value) {
neResourcesChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
function fnChangeData(data: any[], itemID: string) {
const neType = itemID.split('_')[0];
const neID = itemID.split('_')[1];
currentNeId.value = neID;
let info = data.find((item: any) => item.id === neType);
if (!info || !info.neStateMap[neID]?.online) return;
let sysCpuUsage = 0;
let nfCpuUsage = 0;
if (info.neStateMap[neID].cpu) {
nfCpuUsage = info.neStateMap[neID].cpu.nfCpuUsage;
const nfCpu = +(info.neStateMap[neID].cpu.nfCpuUsage / 100);
nfCpuUsage = +nfCpu.toFixed(2);
if (nfCpuUsage > 100) {
nfCpuUsage = 100;
}
sysCpuUsage = info.neStateMap[neID].cpu.sysCpuUsage;
let sysCpu = +(info.neStateMap[neID].cpu.sysCpuUsage / 100);
sysCpuUsage = +sysCpu.toFixed(2);
if (sysCpuUsage > 100) {
sysCpuUsage = 100;
}
}
let sysMemUsage = 0;
if (info.neStateMap[neID].mem) {
const men = info.neStateMap[neID].mem.sysMemUsage;
sysMemUsage = +(men / 100).toFixed(2);
if (sysMemUsage > 100) {
sysMemUsage = 100;
}
}
let sysDiskUsage = 0;
if (info.neStateMap[neID].disk && Array.isArray(info.neStateMap[neID].disk.partitionInfo)) {
let disks: any[] = info.neStateMap[neID].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);
}
}
resourceData.value = {
neCpu: nfCpuUsage,
sysCpu: sysCpuUsage,
sysMem: sysMemUsage,
sysDisk: sysDiskUsage
};
// 更新图表数据
neResourcesChart.value.setOption({
series: [
{
data: [resourceData.value.neCpu / 100],
color: getColorByValue(resourceData.value.neCpu),
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.neCpu')}\n${resourceData.value.neCpu}%`;
}
}
}
},
{
data: [resourceData.value.sysCpu / 100],
color: getColorByValue(resourceData.value.sysCpu),
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.sysCpu')}\n${resourceData.value.sysCpu}%`;
}
}
}
},
{
data: [resourceData.value.sysMem / 100],
color: getColorByValue(resourceData.value.sysMem),
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.sysMem')}\n${resourceData.value.sysMem}%`;
}
}
}
},
{
data: [resourceData.value.sysDisk / 100],
color: getColorByValue(resourceData.value.sysDisk),
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.sysDisk')}\n${resourceData.value.sysDisk}%`;
}
}
}
}
]
});
}
watch(
graphNodeState,
v => {
fnChangeData(v, graphNodeClickID.value);
},
{
deep: true,
}
);
watch(graphNodeClickID, v => {
fnChangeData(graphNodeState.value, v);
});
onMounted(() => {
nextTick(() => {
handleRanderChart(neResourcesDom.value, optionData);
});
});
</script>
<template>
<div class="resource-panel">
<div ref="neResourcesDom" class="chart"></div>
</div>
</template>
<style lang="less" scoped>
.resource-panel {
width: 100%;
height: 100%;
margin-top: -32px;
display: flex;
flex-direction: column;
.panel-title {
font-size: 14px;
color: #00fcff;
padding: 8px;
border-bottom: 1px solid rgba(10, 115, 255, 0.3);
}
.chart {
flex: 1;
width: 100%;
}
}
</style>

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,470 @@
.viewport {
/* 限定大小 */
min-width: 1024px;
max-width: 1920px;
min-height: 780px;
margin: 0 auto;
position: relative;
display: flex;
padding: 5rem 0.833rem 0;
line-height: 1.15;
background-image:
linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.75)),
url(../images/bj.png);
height: 100vh;
margin-bottom: -20px;
background-size:80% 80%;
background-attachment:fixed;
-webkit-background-size: cover;
}
.column {
flex: 3;
position: relative;
display: flex;
flex-direction: column;
}
/* 边框 */
.panel {
box-sizing: border-box;
border: 2px solid rgba(252, 252, 252, 0);
border-width: 2.125rem 1.583rem 0.875rem 5.5rem;
position: relative;
margin-bottom: 0.833rem;
}
/* 使用伪元素创建L形角框 */
/* 添加L形角框边框效果 */
.panel::before,
.panel::after {
content: '';
position: absolute;
pointer-events: none;
z-index: 10;
}
/* 左上角L形 - 相对于inner的实际边界 */
.panel::before {
top: -2.125rem;
left: -5.5rem;
width: 10px;
height: 10px;
border-top: 3px solid #4c9bfd;
border-left: 3px solid #4c9bfd;
}
/* 右下角L形 - 相对于inner的实际边界 */
.panel::after {
bottom: -0.875rem;
right: -1.583rem;
width: 10px;
height: 10px;
border-bottom: 3px solid #4c9bfd;
border-right: 3px solid #4c9bfd;
}
/* 右上角L形 - 相对于inner的实际边界 */
.panel .inner::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 10px;
height: 10px;
border-top: 3px solid #4c9bfd;
border-right: 3px solid #4c9bfd;
pointer-events: none;
z-index: 10;
}
/* 左下角L形 - 相对于inner的实际边界 */
.panel .inner::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 10px;
height: 10px;
border-bottom: 3px solid #4c9bfd;
border-left: 3px solid #4c9bfd;
pointer-events: none;
z-index: 10;
}
/* 为base模块调整角框显示 */
/* 第一个base模块只显示上方角框左上角和右上角 */
.skim.panel.base:first-of-type::after,
.skim.panel.base:first-of-type .inner::after {
display: none;
}
/* 第二个base模块只显示下方角框左下角和右下角 */
.skim.panel.base:not(:first-of-type)::before,
.skim.panel.base:not(:first-of-type) .inner::before {
display: none;
}
.panel .inner {
/* 装内容 */
/* height: 60px; */
position: absolute;
top: -2.125rem;
right: -1.583rem;
bottom: -0.875rem;
left: -5.5rem;
padding: 1rem 1.5rem;
}
.panel h3 {
font-size: 0.833rem;
color: #fff;
}
.leftright {
width: 100%;
min-height: 2.5rem;
/*background: url(../images/title.png) no-repeat center center;*/
background-size: 100%;
color: #4c9bfd;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
padding: 0.5rem 1.2rem;
border-radius: 0.7rem 0.7rem 0 0;
margin: 0;
box-sizing: border-box;
text-shadow: 0 1px 4px #000a;
flex-wrap: nowrap;
/* 保证内容不换行且居中 */
}
.leftright .title {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
font-size: 1.2rem;
padding-left: 0;
}
.centerStyle {
width: 100%;
min-height: 2.5rem;
/*background: url(../images/title.png) no-repeat center center;*/
background-size: 90%;
color: #4c9bfd;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
padding: 0.5rem 1.2rem;
border-radius: 0.7rem 0.7rem 0 0;
margin: 0;
box-sizing: border-box;
text-shadow: 0 1px 4px #000a;
flex-wrap: nowrap;
/* 保证内容不换行且居中 */
}
.centerStyle .title {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
font-size: 1.2rem;
padding-left: 0;
}
/* 总览标题 */
.brand {
background-image: url(../images/newBrand.png);
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
position: absolute;
top: 0.833rem;
left: 0;
right: 0;
width: 100%;
height: 5rem;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
}
.brand .brand-title {
color: #ffffff;
font-size: 1.4rem;
font-weight: 600;
padding-top: 1rem;
padding-bottom: 0.5rem;
}
.brand .brand-desc {
color: #d9d9d9;
font-size: 0.9rem;
}
/* 实时流量 */
.upfFlow {
/* min-height: 16rem; */
height: 40%;
}
.upfFlow .inner .chart {
width: 100%;
height: 100%;
margin-top: 0rem;
}
/* 网络拓扑 */
.topology {
/* min-height: 27.8rem; */
height: 46.4%;
flex: 1;
}
.topology .inner h3 {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: baseline;
}
.topology .inner h3 .normal {
color: #52c41a;
font-size: 1.1rem;
margin-right: 8px;
}
.topology .inner h3 .abnormal {
color: #f5222d;
font-size: 1.1rem;
}
.topology .inner .chart {
width: 100%;
height: 100%;
margin-top: 1rem;
}
/* 概览区域 */
.skim {
/* min-height: 7.78rem; */
height: 14.4%;
}
.skim .inner .data {
display: flex;
flex-direction: row;
align-items: center;
height: 90%;
}
.skim .inner .data .item {
display: flex;
flex-direction: column;
align-items: baseline;
width: 33%;
}
.skim .inner .data .item div {
font-size: 1.467rem;
color: #fff;
margin-bottom: 0;
display: flex;
align-items: baseline;
line-height: 2rem;
}
.skim .inner .data .item span {
color: #4c9bfd;
font-size: 0.833rem;
width: 100%;
position: relative;
line-height: 2rem;
white-space: nowrap;
text-align: start;
text-overflow: ellipsis;
overflow: hidden;
}
.skim .inner .data .item span::before {
content: ' ';
position: absolute;
top: 2px;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
background-image: linear-gradient(to right, #fff, #fff0);
height: 1px;
border-radius: 4px;
}
/* 概览区域 衍生基站信息 */
.skim.base {
height: 11.25%;
}
.skim.base .inner .data {
display: flex;
flex-direction: row;
align-items: center;
height: 75%;
}
.skim.base .inner .data .item {
display: flex;
flex-direction: column;
align-items: baseline;
width: 50%;
}
/* 用户行为 */
.userActivity {
/* min-height: 35.8rem; */
height: 54.6%;
flex: 1;
}
.userActivity .inner .chart {
width: 100%;
height: 100%;
margin-top: 1rem;
}
/* 流量统计 */
.upfFlowTotal1 {
/* min-height: 7.5rem; */
height: 14.4%;
}
.upfFlowTotal1 .inner h3 {
display: flex;
justify-content: space-between;
}
.upfFlowTotal1 .inner h3 .filter {
display: flex;
}
.upfFlowTotal1 .inner h3 .filter span {
display: block;
height: 0.75rem;
line-height: 1;
padding: 0 0.75rem;
color: #1950c4;
font-size: 0.75rem;
border-right: 0.083rem solid #00f2f1;
cursor: pointer;
}
.upfFlowTotal1 .inner h3 .filter span:first-child {
padding-left: 0;
}
.upfFlowTotal1 .inner h3 .filter span:last-child {
border-right: none;
}
.upfFlowTotal1 .inner h3 .filter span.active {
color: #fff;
font-size: 0.833rem;
}
.upfFlowTotal1 .inner .chart {
width: 100%;
height: 100%;
margin-top: 0.1rem;
}
.upfFlowTotal1 .inner .chart .data {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 60%;
}
.upfFlowTotal1 .inner .chart .data .item {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.upfFlowTotal1 .inner .chart .data .item h4 {
font-size: 1.467rem;
color: #fff;
margin-bottom: 0;
}
.upfFlowTotal1 .inner .chart .data .item span {
color: #4c9bfd;
font-size: 0.867rem;
}
/* 流量统计 */
.upfFlowTotal {
/* min-height: 7.5rem; */
height: 14.4%;
}
.upfFlowTotal .inner h3 {
display: flex;
justify-content: space-between;
}
.upfFlowTotal .inner h3 .filter {
display: flex;
}
.upfFlowTotal .inner h3 .filter span {
display: block;
height: 0.75rem;
line-height: 1;
padding: 0 0.75rem;
color: #1950c4;
font-size: 0.75rem;
border-right: 0.083rem solid #00f2f1;
cursor: pointer;
}
.upfFlowTotal .inner h3 .filter span:first-child {
padding-left: 0;
}
.upfFlowTotal .inner h3 .filter span:last-child {
border-right: none;
}
.upfFlowTotal .inner h3 .filter span.active {
color: #fff;
font-size: 0.833rem;
}
.upfFlowTotal .inner .chart {
width: 100%;
height: 100%;
margin-top: 0.1rem;
}
.upfFlowTotal .inner .chart .data {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 60%;
}
.upfFlowTotal .inner .chart .data .item {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.upfFlowTotal .inner .chart .data .item h4 {
font-size: 1.467rem;
color: #fff;
margin-bottom: 0;
}
.upfFlowTotal .inner .chart .data .item span {
color: #4c9bfd;
font-size: 0.867rem;
}
/* 资源情况 */
.resources {
/* min-height: 18rem; */
height: 24.4%;
}
.resources .inner .chart {
width: 100%;
height: 100%;
margin-top: 1rem;
}
/* 告警统计 */
.alarmType {
/* min-height: 25rem; */
height: 35%;
}
.alarmType .inner .chart {
width: 100%;
height: 100%;
}
/* 跳转鼠标悬浮 */
.toRouter:hover {
cursor: pointer;
color: #fff !important;
}
.toRouter:hover > *,
.toRouter:hover > * > * {
color: #fff !important;
}

View File

@@ -0,0 +1,197 @@
import { parseDateToStr } from '@/utils/date-utils';
import { computed, reactive, ref } from 'vue';
/**非网元元素 */
export const notNeNodes = [
'5GC',
'DN',
'UE',
'Base',
'lan',
'lan1',
'lan2',
'lan3',
'lan4',
'lan5',
'lan6',
'lan7',
'LAN',
'NR',
];
/**图状态 */
export const graphState = reactive<Record<string, any>>({
/**当前图组名 */
group: '5GC System Architecture',
/**图数据 */
data: {
combos: [],
edges: [],
nodes: [],
},
});
/**图实例对象 */
export const graphG6 = ref<any>(null);
/**图点击选择 */
export const graphNodeClickID = ref<string>('UPF_001');
/**图节点网元信息状态 */
export const graphNodeState = computed(() =>{
return graphState.data.nodes.map((item: any) => ({
id: item.id,
label: item.label,
neInfo: item.neInfo,
neState: item.neState,
neInfoList:item.neInfoList,
neStateMap: item.neStateMap,
}))
}
);
/**图节点网元状态数量 */
export const graphNodeStateNum = computed(() => {
let normal = 0;
let abnormal = 0;
for (const item of graphState.data.nodes) {
const neId = item.neState.neId;
if (neId) {
if (item.neState.online) {
normal += 1;
} else {
abnormal += 1;
}
}
}
return [normal, abnormal];
});
/**网元状态请求标记 */
export const neStateRequestMap = ref<Map<string, boolean>>(new Map());
/**neStateParse 网元状态 数据解析 */
export function neStateParse(neType: string, data: Record<string, any>,neId: string) {
// console.log('neStateParse',neType, data, neId);
const { combos, edges, nodes } = graphState.data;
const node = nodes.find((item: Record<string, any>) => item.id === neType);
//console.log('neStateParse',node);
if (!node) return;
// 初始化状态映射
if (!node.neStateMap) node.neStateMap = {};
// 更新网元状态
const newNeState :any = {
...data, // 先展开data对象
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'),
online: !!data.cpu,
neId: neId
};
// 如果是001更新节点状态。neInfo为主要的网元信息
if (node.neInfo && node.neInfo.neId === neId) {
Object.assign(node.neState, newNeState);
}
//console.log(node.neState)
// 无论是否为主要网元,都更新状态映射
node.neStateMap[neId] = {...newNeState};
// 通过 ID 查询节点实例
const item = graphG6.value.findById(node.id);
if (item) {
// 检查当前节点下所有网元的状态
const allStates = Object.values(node.neStateMap);
// 判断状态颜色
let stateColor = '#52c41a'; // 默认绿色(所有网元都正常)
if (allStates.some((state: any) => !state.online)) {
// 如果有任何一个网元不正常
stateColor = allStates.every((state: any) => !state.online) ? '#f5222d' : '#faad14'; // 红色(全部不正常)或黄色(部分不正常)
}
// 图片类型不能填充
if (node.type && node.type.startsWith('image')) {
// 更新节点
if (node.label !== newNeState.neType) {
graphG6.value.updateItem(item, {
label: newNeState.neType,
});
}
// 设置状态
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
} else {
// 更新节点
graphG6.value.updateItem(item, {
label: newNeState.neType,
style: {
fill: stateColor, // 填充色
stroke: stateColor, // 填充色
},
});
// 设置状态
graphG6.value.setItemState(item, 'stroke', newNeState.online);
}
}
// 设置边状态
for (const edge of edges) {
const edgeSource: string = edge.source;
const edgeTarget: string = edge.target;
const neS = nodes.find((n: any) => n.id === edgeSource);
const neT = nodes.find((n: any) => n.id === edgeTarget);
// console.log(neS, edgeSource, neT, edgeTarget);
if (neS && neT) {
// 通过 ID 查询节点实例
// const item = graphG6.value.findById(edge.id);
// console.log(
// `${edgeSource} - ${edgeTarget}`,
// neS.neState.online && neT.neState.online
// );
// const stateColor = neS.neState.online && neT.neState.online ? '#000000' : '#ff4d4f'; // 状态颜色
// 更新边
// graphG6.value.updateItem(item, {
// label: `${edgeSource} - ${edgeTarget}`,
// style: {
// stroke: stateColor, // 填充色
// },
// labelCfg: {
// style: {
// fill: '#ffffff', // 标签文本色
// },
// },
// });
// 设置状态
graphG6.value.setItemState(
edge.id,
'circle-move',
neS.neState.online && neT.neState.online
);
}
if (neS && notNeNodes.includes(edgeTarget)) {
graphG6.value.setItemState(edge.id, 'line-dash', neS.neState.online);
}
if (neT && notNeNodes.includes(edgeSource)) {
graphG6.value.setItemState(edge.id, 'line-dash', neT.neState.online);
}
}
// 请求标记复位
neStateRequestMap.value.set(neType, false);
}
/**属性复位 */
export function topologyReset() {
graphState.data = {
combos: [],
edges: [],
nodes: [],
};
graphG6.value = null;
graphNodeClickID.value = 'UPF_001';
neStateRequestMap.value = new Map();
}

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

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