263 Commits

Author SHA1 Message Date
TsMask
618499e777 style: 网元公共参数EMS_IP标记必填红 2024-06-01 16:18:50 +08:00
TsMask
71c0306587 fix: 文件切片操作超时时间统一60s 2024-06-01 15:11:46 +08:00
TsMask
a3cd8f05fb style: 编译类型声明 2024-05-31 19:17:54 +08:00
TsMask
d8ca0ca5ef feat: 网元安装IMS预输入mf包参数 2024-05-31 19:06:42 +08:00
TsMask
da08e1cc5b style: 开战公共参数保存提示成功 2024-05-31 17:36:09 +08:00
TsMask
8e4c5d0b90 feat: 网元信息配置文件导入导出 2024-05-31 15:08:55 +08:00
TsMask
467c93f710 fix: 锁屏重启检查404时重试 2024-05-31 11:05:03 +08:00
TsMask
22ced8bdb2 fix: 网元公共参数初始数据组件回填,adb绑定0.00.0 2024-05-30 21:20:10 +08:00
TsMask
2ee26a6958 Merge remote-tracking branch 'origin/main' into lichang 2024-05-30 18:07:52 +08:00
TsMask
c63e892544 style: 系统操作日志去除请求方法列 2024-05-30 18:00:50 +08:00
TsMask
cce3088f73 Merge remote-tracking branch 'origin/main' into lichang 2024-05-30 17:55:22 +08:00
TsMask
2211896768 Merge remote-tracking branch 'origin/main' into lichang 2024-05-30 17:53:51 +08:00
TsMask
5bb3cd814c fix: 网元参数配置属性监听异常 2024-05-30 17:53:26 +08:00
lai
266d816a21 修改文件默认名 2024-05-30 17:49:39 +08:00
TsMask
90c64c029a Merge remote-tracking branch 'origin/main' into lichang 2024-05-30 17:29:45 +08:00
TsMask
d06e2da72e chore: 更新版本240530 2024-05-30 17:23:49 +08:00
TsMask
7714d506c4 feat: 网元参数配置smf选择upfId 2024-05-30 17:08:55 +08:00
lai
0b7a198235 Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-05-30 15:44:41 +08:00
TsMask
68362ba3e3 feat: 网元参数配置可收起左侧菜单 2024-05-30 15:41:17 +08:00
lai
a9633e652f Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3 2024-05-30 15:34:27 +08:00
TsMask
6592561bca style: 参数校验多语言翻译 2024-05-30 15:32:42 +08:00
lai
09e0353053 增加事件告警 2024-05-30 15:25:08 +08:00
lai
c1e95fd1e9 仪表盘去除事件告警 2024-05-30 14:49:45 +08:00
TsMask
89857e0c1b style: 开站页面样式居中 2024-05-30 11:38:36 +08:00
TsMask
7aa37ee330 chore: 更新版本号240530 2024-05-30 11:00:57 +08:00
TsMask
4c16888184 fix: 网元快速安装授权检查是否可用后在进行文件上传 2024-05-29 18:06:26 +08:00
TsMask
a61716d40d feat: 网元公共参数配置表单组件 2024-05-29 16:32:11 +08:00
TsMask
3f02cb628b feat: 网元软件包支持携带依赖进行记录 2024-05-28 15:31:41 +08:00
TsMask
22805924fb fix: 网元软件多上传同开站逻辑 2024-05-28 15:29:45 +08:00
TsMask
e00229e5ff revt: 回退网元软件包类型支持adb,mf,rtproxy 2024-05-27 18:49:41 +08:00
TsMask
939a235a87 style: 网元软件代码优化 2024-05-27 18:33:46 +08:00
TsMask
cd49162dfc fix: 网元安装去除默认agtuser输入 2024-05-27 18:31:33 +08:00
TsMask
b7f2df5d1c fix: 网元版本升级勾选逐个进行,多语言翻译 2024-05-27 18:28:15 +08:00
TsMask
8b24bc55b9 fix: 网元授权上传进行重启网元 2024-05-27 17:09:39 +08:00
TsMask
9f2b80718e fix: 网元软件包类型支持adb,mf,rtproxy 2024-05-27 17:08:56 +08:00
TsMask
38cb406687 chore: 更新版本号240524 2024-05-24 20:54:55 +08:00
TsMask
c25fe63a26 style: 开站安装关闭按钮禁用 2024-05-24 20:54:37 +08:00
TsMask
b84d6bb9fc fix: 开站网元信息删除无id触发undefined 2024-05-24 17:54:39 +08:00
TsMask
b325cde5c0 fix: 国际化切换选择控件根据配置隐藏 2024-05-24 17:53:36 +08:00
TsMask
34266da44e style: 移除无效引用 2024-05-24 17:42:28 +08:00
TsMask
d82d6b7b47 fix: 网元授权勾选重置 2024-05-24 17:41:47 +08:00
TsMask
b1c7a068cf perf: 网元授权勾选多上传更新 2024-05-24 17:41:08 +08:00
TsMask
c155160cf7 style: 开站网元安装多语言翻译 2024-05-24 16:21:44 +08:00
TsMask
2ae4559958 fix: 开站软件安装逐个进行避免dpkg look错误 2024-05-24 16:21:08 +08:00
TsMask
782283bba6 pref: 网元软件多上传选择弹窗组件 2024-05-24 16:19:53 +08:00
TsMask
1b50708786 fix: 移除无用loding和危险操作高亮红色 2024-05-24 16:18:52 +08:00
TsMask
206d3755a0 fix: 网元公共参数设置omcIP进行更新 2024-05-24 16:15:09 +08:00
TsMask
6470effbda style: 网元相关页面多语言翻译 2024-05-24 09:41:56 +08:00
TsMask
f94b1ef44a style: 快速开站多语言翻译 2024-05-24 09:41:20 +08:00
TsMask
7c78eab431 style: 快速开站多语言翻译 2024-05-21 17:10:45 +08:00
TsMask
814b7b0058 feat: 网元快速安装多语言翻译 2024-05-20 20:18:00 +08:00
TsMask
624f67ecca feat: 网元公共配置多语言翻译 2024-05-20 16:20:58 +08:00
TsMask
c1a9497b77 feat: 网元信息表单多语言翻译 2024-05-20 16:10:13 +08:00
TsMask
1ad2ed2d26 feat: 网元信息表单多语言翻译 2024-05-20 15:14:23 +08:00
TsMask
48f278997f feat: 网元信息OAM多语言翻译 2024-05-20 11:58:25 +08:00
TsMask
9b9d6222e1 feat: 网元信息多语言翻译 2024-05-20 10:56:55 +08:00
TsMask
2e8a878365 Merge remote-tracking branch 'origin/main' into lichang 2024-05-17 20:23:52 +08:00
TsMask
bc59dc67e6 style: IMS用户话单表格宽度 2024-05-17 18:15:16 +08:00
TsMask
df6022b4a8 Merge remote-tracking branch 'origin/main' into lichang 2024-05-17 18:11:53 +08:00
TsMask
f2c67d087b chore: 更新版本240517 2024-05-17 18:02:22 +08:00
TsMask
7a0298a419 feat: IMS用户话单支持号码查询 2024-05-17 18:00:56 +08:00
TsMask
b40e798090 feat: AMF用户事件支持IMSI查询 2024-05-17 18:00:28 +08:00
TsMask
d48d5d6c95 feat: 网元软件多语言翻译 2024-05-17 17:14:14 +08:00
TsMask
69f3347a4d feat: 网元版本多语言翻译 2024-05-17 16:04:44 +08:00
TsMask
85ae7dc5ea fix: 网元授权多语言翻译 2024-05-17 15:18:01 +08:00
TsMask
531cd6d03d fix: 开站网元信息telnet不显示 2024-05-17 11:32:30 +08:00
TsMask
9fd8c4597a fix: 开站网元信息telnet默认值 2024-05-17 11:06:19 +08:00
TsMask
7060a3d887 fix: 网元授权文件变更提供重启功能,排除OMC类型 2024-05-17 11:04:59 +08:00
TsMask
c12ffa583c fix: 开站时网元主机可不填备注 2024-05-16 19:21:06 +08:00
TsMask
05aabb8c61 chore: 更新版本号240516 2024-05-16 14:46:59 +08:00
TsMask
6b461ead7c fix: 网元信息编辑添加备注输入 2024-05-16 14:43:51 +08:00
TsMask
7c54e372e5 feat: 开站网元授权状态标签字典 2024-05-16 14:43:18 +08:00
TsMask
5be95a7af6 feat: 网元版本状态标签字典 2024-05-16 14:42:10 +08:00
TsMask
08515541b4 style: 网元公共参数配置初始值 2024-05-16 10:00:50 +08:00
TsMask
3948924f61 del: 删除旧系统页面 2024-05-15 19:21:49 +08:00
TsMask
ce1944881a style: 隐藏IP地址区域信息 2024-05-15 19:21:24 +08:00
lai
f6122c0758 调整为活动告警 2024-05-15 14:44:35 +08:00
lai
3d3f4c2cc6 看板微调 2024-05-15 14:43:07 +08:00
TsMask
e8e5b92a57 Merge remote-tracking branch 'origin/main' into lichang 2024-05-14 19:14:13 +08:00
TsMask
4ad704398d fix: 网元授权安装结果遍历message提示 2024-05-14 19:13:58 +08:00
TsMask
26289b7787 fix: 文件上传接口超时60s 2024-05-14 19:13:21 +08:00
TsMask
7ad8c1706e fix: 网元OAM端口输入限制5 2024-05-14 19:12:54 +08:00
TsMask
424a596613 fix: 看板定时器初始化前清除后设置 2024-05-14 19:12:16 +08:00
TsMask
6d5b9f417c fix: 网元特定排序添加SMSC 2024-05-14 17:24:50 +08:00
TsMask
7278e24dce Merge remote-tracking branch 'origin/main' into lichang 2024-05-14 15:02:11 +08:00
TsMask
76839ae18a fix: 系统重置操作等待遮罩 2024-05-14 15:01:30 +08:00
TsMask
c8a6aa3210 feat: 锁屏遮罩添加系统重置等待 2024-05-14 14:58:35 +08:00
TsMask
9b4bbcedc5 fix: 黄金指标图表项数据切换重复累积问题 2024-05-14 14:47:09 +08:00
TsMask
3b793c107c style: 角色键值输入提示变更多语言 2024-05-14 12:01:23 +08:00
lai
a076e6d079 疑似{}引发的问题 2024-05-14 10:02:43 +08:00
TsMask
fb2a7c51f9 Merge remote-tracking branch 'origin/main' into lichang 2024-05-13 09:41:19 +08:00
TsMask
1e2c2b5170 fix: 拓扑信息首个非OMC的异常显示问题 2024-05-11 18:03:49 +08:00
lai
56833b654e 更改提示语 2024-05-11 16:59:40 +08:00
lai
f50e02e6e7 为dnn,st添加星号 2024-05-11 16:58:22 +08:00
TsMask
52cc24813f Merge remote-tracking branch 'origin/main' into lichang 2024-05-11 16:42:13 +08:00
TsMask
c8a0d4c3f7 fix: 网元主机ssh私钥长度3000限制 2024-05-11 16:41:51 +08:00
TsMask
7560f21f11 style: 注释修改 2024-05-11 10:45:34 +08:00
TsMask
8a5f80fe47 feat: 网元配置OAM弹窗表单 2024-05-11 10:45:13 +08:00
TsMask
29eb1f08b1 del: 移除旧的参数配置页面 2024-05-10 14:54:36 +08:00
TsMask
9981de2271 del: 移除旧在线会话页面 2024-05-10 14:49:37 +08:00
TsMask
207acb3f3d fix: 网元信息IP地址输入校验 2024-05-10 14:43:57 +08:00
TsMask
873a76f48f fix: 开站排除omc类型选择 2024-05-10 14:42:26 +08:00
TsMask
4ca700092a fix: 登录后重定向页面 2024-05-10 14:19:30 +08:00
TsMask
ad9f8574bd del: 移除网元无用接口 2024-05-10 10:17:11 +08:00
TsMask
75faae6d7c Merge remote-tracking branch 'origin/main' into lichang 2024-05-09 19:26:20 +08:00
TsMask
c88c146a5e style: 网元版本页面多语言翻译 2024-05-09 19:25:14 +08:00
TsMask
c65cda1468 fix: 网元快速安装页面初始表单简化 2024-05-09 19:24:51 +08:00
TsMask
d8059341fe fix: 网元公共参数配置页面补充mme_ip 2024-05-09 19:22:03 +08:00
TsMask
ad86bee5f9 fix: 开站页面同步公共参数配置,优化页面样式 2024-05-09 19:18:21 +08:00
TsMask
f44cc44a1b style: 网元类型列表排序 2024-05-09 18:29:08 +08:00
TsMask
ad93588796 fix: 参数配置方法权限控制post,put,delete 2024-05-09 18:27:32 +08:00
TsMask
b5f6a5d24a feat: 网元公共参数配置数据名称调整 2024-05-09 15:54:48 +08:00
TsMask
fa4be253f7 fix: 系统配置重置操作多语言翻译 2024-05-08 16:09:39 +08:00
TsMask
2db0220c98 chore: 更新版本240508 2024-05-08 11:35:52 +08:00
TsMask
05fd678ce2 fix: SMF在线用户模拟数据变更/RAT Type文字换行 2024-05-08 11:35:33 +08:00
TsMask
165b28bab1 style: 开站页面样式调整 2024-05-08 11:05:35 +08:00
TsMask
dc67cdc262 fix: 看站系统配置新增管理员账号变更 2024-05-08 11:04:17 +08:00
TsMask
c97394a0ed fix: 看站完成确认提示 2024-05-08 11:03:03 +08:00
TsMask
6b9297d30e style: 看站开始页面样式调整 2024-05-08 11:02:30 +08:00
TsMask
ace7f26b53 style: 引导接口函数变更 2024-05-08 11:01:11 +08:00
TsMask
976f72f79a fix: 网元授权接口变更字段属性调整 2024-05-07 18:39:56 +08:00
TsMask
530e7fd43f feat: 系统引导重置操作 2024-05-07 16:28:10 +08:00
TsMask
b76fed7dcf feat: 系统引导使用接口变更 2024-05-07 16:27:45 +08:00
TsMask
60a26dd015 Merge remote-tracking branch 'origin/main' into lichang 2024-05-07 12:10:07 +08:00
TsMask
7bdfc257c1 chore: 依赖更新 2024-05-07 12:06:38 +08:00
TsMask
e3395b47cf feat: 更新xterm依赖库@xterm/xterm 2024-05-07 12:06:03 +08:00
TsMask
4c9b4de12f fix: 看板用户行为初始查询数据排序 2024-05-07 12:01:04 +08:00
lai
1cfe5e2777 主页饼图无感更新数据 2024-05-07 11:21:57 +08:00
TsMask
0cae26a1ee pref: 网元快速安装页面重构 2024-05-07 10:33:52 +08:00
TsMask
ed4d556384 pref: 网元授权页面的组件重构 2024-05-07 10:28:14 +08:00
TsMask
6d9d7362a4 fix: 网元公共配置参数文件页面 2024-05-07 09:59:42 +08:00
TsMask
5a40733f20 Merge remote-tracking branch 'origin/main' into lichang 2024-04-30 20:25:27 +08:00
TsMask
45ee884529 style: 查询网元信息接口函数命名 2024-04-30 20:08:55 +08:00
TsMask
7de3f7f05d fix: 网元软件上传窗口响应引用无效 2024-04-30 20:08:05 +08:00
TsMask
97bb4db1eb pref: 重构网元安装页面流程 2024-04-30 20:07:21 +08:00
TsMask
fc6dfe9894 style: 开站网元信息函数命名 2024-04-30 20:03:55 +08:00
TsMask
607660124a fix: 开站步骤hooks移除无用属性 2024-04-30 20:03:12 +08:00
TsMask
c50b3add95 feat: 网元安装授权页面 2024-04-30 20:02:24 +08:00
TsMask
2f7f0e9e20 feat: 网元安装配置页面 2024-04-30 20:00:56 +08:00
TsMask
6875ffd113 feat: 网元安装信息编辑页面 2024-04-30 19:59:45 +08:00
TsMask
44844bfa7f feat: 网元安装检查页面组件 2024-04-30 19:59:06 +08:00
TsMask
21ab066761 fix: 网元授权编辑弹出窗优化支持类型查询 2024-04-30 19:58:22 +08:00
TsMask
83bdf289fa feat: 网元信息免密直连授权操作 2024-04-30 19:56:17 +08:00
lai
d7aa0fc91a 更正-详情信息添加upState字段 2024-04-30 10:22:43 +08:00
lai
a6be5190ec 详情信息添加upState字段 2024-04-30 10:15:02 +08:00
TsMask
03cd06e835 style: 开站安装网元改为下一步 2024-04-29 18:18:11 +08:00
TsMask
2d7fe5a73b feat: 网元授权文件支持勾选指定网元类型替换 2024-04-29 18:17:45 +08:00
TsMask
dd5604c8b7 perf: 重构网元公共文件编辑参数 2024-04-29 18:16:39 +08:00
TsMask
7bf2672981 fix: 移除OAM编辑也慢慢 2024-04-29 18:15:26 +08:00
TsMask
6e616c63f0 fix: UDM签约数据sst-sd格式无sd也带- 2024-04-28 11:46:30 +08:00
TsMask
3ded481cce docs: 补充参数说明 2024-04-28 10:24:31 +08:00
TsMask
1baff582d8 feat: 网元授权编辑查看弹出窗口 2024-04-28 10:24:09 +08:00
TsMask
957868cc27 feat: 开站网元安装步骤进度的显示 2024-04-28 10:23:14 +08:00
TsMask
efb98ac577 feat: 补充授权文件按类型多上传 2024-04-28 10:22:14 +08:00
TsMask
ff228daae7 fix: UE SMF状态查询Up State可选 2024-04-26 19:21:44 +08:00
TsMask
c93e693517 fix: pcf新增编辑表单数据组装异常,上传超时30s 2024-04-26 17:13:07 +08:00
TsMask
55863c1c58 fix: pcf新增表单数据新增回显问题 2024-04-26 12:15:01 +08:00
lai
8a2d6c91ba 中英文翻译 2024-04-25 19:54:00 +08:00
lai
86a939f206 中英文翻译修正 2024-04-25 19:53:35 +08:00
TsMask
2b86f724b1 style: 系统菜单图标选择编号icon-001 2024-04-25 19:37:03 +08:00
TsMask
68fbc45b7c feat: 新增oam配置文件读写接口 2024-04-25 17:19:05 +08:00
TsMask
4629ef28ff fix: 接口变更 2024-04-25 17:18:46 +08:00
lai
e3a11b0ede 增加一个搜索字段 2024-04-25 17:17:02 +08:00
lai
3df9ee4f3a QOS=>Qos 2024-04-25 17:16:12 +08:00
lai
e64d558c27 批量新增 2024-04-25 17:10:22 +08:00
lai
5a2aa383fe 图标名字为icon001一直往下 2024-04-25 14:32:53 +08:00
lai
fe9f458465 pcf动态提示 2024-04-25 14:30:59 +08:00
TsMask
8e2e9aec67 style: 开站网元软件信息高度70vh 2024-04-25 14:22:09 +08:00
TsMask
ad48e8e2e1 feat: 网元软件安装页面 2024-04-25 14:21:35 +08:00
TsMask
6f8b1000ba feat: 开站网元公告参数配置表单填写 2024-04-25 14:21:08 +08:00
TsMask
f5549992c2 style: 网元公共参数表单名称修改 2024-04-25 14:19:29 +08:00
TsMask
19771ea6dc fix: 网元版本勾选升级关闭提示信息 2024-04-25 14:19:05 +08:00
TsMask
5965737384 feat: 网元软件包勾选删除记录 2024-04-25 14:17:59 +08:00
TsMask
b3cb40fd8c fix: 文件上传后关闭窗口复位上传结果 2024-04-25 14:16:41 +08:00
TsMask
0ab5141369 chore: 版本日期240425 2024-04-25 11:59:19 +08:00
TsMask
a23284da4c fix: 鉴权用户sst-sd输入限制1-3,sd补零 2024-04-25 11:56:41 +08:00
TsMask
1c8cc13436 style: PCF批量操作点击展开 2024-04-24 16:04:22 +08:00
TsMask
e6a439faea feat: 开站目录变更quick-start 2024-04-24 15:57:03 +08:00
TsMask
f8b1b1f6c3 Merge remote-tracking branch 'origin/main' into lichang 2024-04-24 15:40:48 +08:00
TsMask
3844e07258 feat: 开站目录变更quick-start 2024-04-24 15:34:07 +08:00
TsMask
1fab9b3d97 feat: 开站页面的部分修改 2024-04-24 15:30:38 +08:00
TsMask
1bfacbf2e0 feat: 开站系统信息页面逻辑对接 2024-04-24 15:29:51 +08:00
TsMask
c01f9f3830 feat: 开站网元信息页面逻辑对接 2024-04-24 15:29:29 +08:00
TsMask
28f9d365fd style: 网元新增编辑操作信息 2024-04-24 15:28:11 +08:00
TsMask
1085fa16aa feat: 初始访问时跳转开站引导页面 2024-04-24 15:27:23 +08:00
TsMask
e351960229 feat: 网元公共参数页面同步操作改弹出窗显示操作信息 2024-04-24 15:25:56 +08:00
TsMask
03caa354da feat: 开站开始页面获取接口授权 2024-04-24 15:24:45 +08:00
TsMask
34754ea0b6 feat: 开站引导开始完成接口 2024-04-24 15:24:00 +08:00
TsMask
86e1f07f08 feat: 开站结束页面标记完成 2024-04-24 15:22:27 +08:00
TsMask
2add7547d6 style: 删除console 2024-04-23 16:03:39 +08:00
TsMask
ecb89e9f26 feat: 用户管理和个人信息手机号输入改为国际电话输入 2024-04-23 14:56:35 +08:00
TsMask
7a9b38dc66 feat: 国际手机号输入框 2024-04-23 14:55:57 +08:00
TsMask
979b18092d fix: 个人信息手机号提示翻译出错 2024-04-23 14:53:37 +08:00
TsMask
ba43a647dd style: 查询年月日控件改为年月日时分秒 2024-04-22 17:17:29 +08:00
TsMask
3be14590c6 style: 告警alarmSeq改Sequence Number 2024-04-22 17:16:09 +08:00
TsMask
857f8c4313 feat: 系统首次使用引导页面 2024-04-19 19:54:07 +08:00
TsMask
c9c39d2d4a fix: 网元信息新增默认amf示例,安装默认y 2024-04-19 19:52:19 +08:00
TsMask
845084f77f feat: 系统首次使用引导标记 2024-04-19 19:51:01 +08:00
TsMask
becba4919e style: 多语言翻译 2024-04-19 17:22:37 +08:00
lai
b797055df9 KB转换成MB 2024-04-19 16:05:14 +08:00
TsMask
8101c852c8 style: 告警前传日志alarmSeq改Sequence 2024-04-19 14:07:35 +08:00
TsMask
9bbeb9fc9a fix: 锁屏reload延迟解除防止二次重启导致服务失败 2024-04-18 09:34:25 +08:00
TsMask
19a9bd3f5c fix: 锁屏reload不解除问题 2024-04-17 18:20:37 +08:00
TsMask
399bddc635 Merge remote-tracking branch 'origin/main' into lichang 2024-04-17 15:39:33 +08:00
TsMask
5a67a1a51c chore: 控制台版本号240418 2024-04-17 15:32:42 +08:00
TsMask
9a168d2ba0 fix: 登录页无网络隐藏多言选择 2024-04-17 15:32:07 +08:00
TsMask
e42a620aed feat: 快速开始建站页面 2024-04-17 15:07:02 +08:00
TsMask
eb902594b8 style: 网元菜单表格调整 2024-04-17 15:05:17 +08:00
TsMask
817fd97496 fix: 表字段列排序组件首位排序无效问题 2024-04-17 14:32:06 +08:00
TsMask
96aab47003 Merge remote-tracking branch 'origin/main' into lichang 2024-04-17 10:54:26 +08:00
TsMask
432666b1ab fix: 看板Hooks数据复位 2024-04-17 10:48:53 +08:00
TsMask
662583c73b fix: 拓扑组件监听窗口变化 2024-04-17 10:47:21 +08:00
TsMask
f73c21e6e6 fix: 页面卸载前判断是否断开ws 2024-04-17 10:45:45 +08:00
TsMask
17e72f1db1 fix: 岗位搜索标签positionCode显示不对 2024-04-17 09:47:45 +08:00
TsMask
55d4a36efb fix: 终端telnet支持调整窗口行宽 2024-04-16 19:49:44 +08:00
TsMask
d3794ba904 feat: 网元公共配置文件配置页面 2024-04-16 19:38:16 +08:00
TsMask
832f18e2ee style: 移动网元快速安装到ne目录 2024-04-16 19:37:37 +08:00
TsMask
cd2e282624 fix: 网元软件包新增去除防重复校验 2024-04-16 11:44:29 +08:00
TsMask
3db88f1d3a style: 网元授权信息去除斑马纹样式 2024-04-16 11:43:38 +08:00
TsMask
f291bb0907 feat: 软件版本勾选多升级 2024-04-16 11:42:45 +08:00
TsMask
69c32b2593 feat: 软件包多文件上传 2024-04-16 11:41:59 +08:00
TsMask
1a2939ab01 fix: 网元管理OMC禁止start stop,开启重启等待 2024-04-16 11:12:54 +08:00
TsMask
8bf4a2a9ce Merge remote-tracking branch 'origin/main' into lichang 2024-04-15 20:18:59 +08:00
TsMask
efd389f98a fix: 查询时间搜索值为时间戳 2024-04-15 20:17:39 +08:00
TsMask
f1125cc042 Merge remote-tracking branch 'origin/main' into lichang 2024-04-15 10:08:43 +08:00
TsMask
f1a0e200dc del: 移除表格斑马纹 2024-04-15 10:08:04 +08:00
TsMask
70c025f0f5 Merge remote-tracking branch 'origin/main' into lichang 2024-04-12 21:49:11 +08:00
TsMask
b9f0a3923d feat: 网元版本记录多勾选 2024-04-12 20:27:11 +08:00
TsMask
5c942c9ef7 style: 更新接口注释 2024-04-12 20:19:04 +08:00
TsMask
4294fe82c5 fix: 网元版本操作升级回退 2024-04-12 20:18:46 +08:00
TsMask
4e7fb90544 fix: 网元服务操作接口 2024-04-12 20:18:01 +08:00
TsMask
f050e500e9 fix: pcf页面添加分页支持勾选删除 2024-04-12 17:47:52 +08:00
TsMask
6bc10babba feat: 软件包上传组件 2024-04-12 17:43:26 +08:00
TsMask
4d36f9952a fix: 网元安装接口参数变更 2024-04-12 17:42:43 +08:00
lai
b03f4bb1d6 PCF策略中英文翻译 2024-04-12 10:35:53 +08:00
TsMask
4aaedf0f77 style: 网元安装授权检查接受提示 2024-04-12 10:23:48 +08:00
TsMask
c1ea851705 style: 网元信息修改弹出窗口改为异步组件 2024-04-12 10:23:12 +08:00
TsMask
47103249ef fix: 网元快速安装页面接口变更 2024-04-11 20:09:24 +08:00
TsMask
2642f18204 fix: 网元软件/版本/授权页面 2024-04-11 17:36:38 +08:00
TsMask
e2e22eabf5 style: 下拉触发改为点击显示 2024-04-11 17:36:07 +08:00
TsMask
325ecc8029 fix: 软件包表字段更新 2024-04-11 17:35:37 +08:00
TsMask
dc6f4560d5 feat: 快速安装页面 2024-04-11 17:35:19 +08:00
TsMask
3970797d5b feat: BA需要的mocn数据看板 2024-04-11 11:58:34 +08:00
TsMask
73776ed0f9 feat: kpi指标跳转指定网元类型 2024-04-11 11:54:48 +08:00
TsMask
aca842f8c2 feat: 参数配置access只读属性支持['read-only', 'read', 'ro'] 2024-04-11 11:53:17 +08:00
TsMask
612fb77861 fix: 网元管理修改网元类型属性绑定 2024-04-11 10:16:42 +08:00
TsMask
6ba695ceaf fix: 下拉展开取消悬浮,支持点击 2024-04-10 19:52:12 +08:00
TsMask
070f77d3b8 feat: 配置-网元管理添加Column settings 2024-04-10 19:38:14 +08:00
TsMask
15de63212f style: 网元管理-Province改为Region 2024-04-10 19:37:27 +08:00
lai
2202540763 更新用户策略提示信息 2024-04-10 19:00:56 +08:00
TsMask
c674b9b3f9 fix: KPI排除不可用网元类型的选择 2024-04-10 18:54:36 +08:00
TsMask
43d30e7bfa fix: 性能任务激活停止放出,编辑删除放到更多 2024-04-10 18:42:12 +08:00
TsMask
cd0633d519 fix: 性能任务激活停止放出,编辑删除放到更多 2024-04-10 18:40:56 +08:00
TsMask
b8bff2a159 style: 网元信息拓展信息显示横向排版 2024-04-10 16:50:56 +08:00
TsMask
638f890228 style: 移除多语言=暂无网元列表数据 2024-04-10 16:49:46 +08:00
TsMask
4460f7201c style: 网元配置类型可选择或输入 2024-04-10 16:47:40 +08:00
TsMask
d0bbfafedc fix: 系统日志的删除多语言提示 2024-04-09 19:41:12 +08:00
TsMask
16dc1c2e20 style: 中英文翻译父子联动->节点联动 2024-04-09 15:46:16 +08:00
TsMask
73ed5f5285 style: 网元新增初始参数 2024-04-09 15:39:04 +08:00
TsMask
50cb92a95b fix: 去除锁屏 2024-04-09 15:33:28 +08:00
TsMask
3888fd2bca fix: 软件管理下发激活回退关闭防重复提交判断 2024-04-09 15:25:34 +08:00
TsMask
4194d8cca6 fix: 用户昵称和手机号正则校验限长度 2024-04-08 15:03:53 +08:00
TsMask
ac84a0ca0a feat: 新增终端文本显示组件 2024-04-07 19:52:37 +08:00
144 changed files with 14171 additions and 10484 deletions

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
{
"name": "ems_frontend_vue3",
"type": "module",
"description": "核心网管理平台",
"description": "Core Network EMS",
"author": "TsMask",
"engines": {
"node": ">=18.0.0"
@@ -15,17 +15,20 @@
"@ant-design/icons-vue": "^7.0.1",
"@antv/g6": "~4.8.24",
"@codemirror/lang-javascript": "^6.2.1",
"@codemirror/lang-yaml": "^6.0.0",
"@codemirror/lang-yaml": "^6.1.1",
"@codemirror/merge": "^6.6.1",
"@codemirror/theme-one-dark": "^6.1.2",
"@tato30/vue-pdf": "^1.9.6",
"@vueuse/core": "~10.9.0",
"@xterm/xterm": "^5.5.0",
"@xterm/addon-fit": "^0.10.0",
"ant-design-vue": "^3.2.20",
"antdv-pro-layout": "^3.2.6",
"codemirror": "^6.0.1",
"dayjs": "^1.11.10",
"dayjs": "^1.11.11",
"echarts": "~5.5.0",
"file-saver": "^2.0.5",
"intl-tel-input": "~22.0.2",
"js-base64": "^3.7.7",
"js-cookie": "^3.0.5",
"localforage": "^1.10.0",
@@ -33,12 +36,10 @@
"p-queue": "^8.0.1",
"pinia": "^2.1.7",
"vue": "~3.3.13",
"vue-i18n": "~9.10.0",
"vue-router": "^4.2.5",
"vue-i18n": "^9.13.1",
"vue-router": "^4.3.2",
"vue3-smooth-dnd": "^0.0.6",
"xlsx": "^0.18.5",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0"
"xlsx": "~0.18.5"
},
"devDependencies": {
"@types/file-saver": "^2.0.7",
@@ -47,10 +48,10 @@
"@types/nprogress": "^0.2.3",
"@vitejs/plugin-vue": "^5.0.4",
"less": "^4.2.0",
"typescript": "^5.4.3",
"unplugin-vue-components": "^0.26.0",
"vite": "^5.2.6",
"vite-plugin-compression": "^0.5.1",
"vue-tsc": "^1.8.27"
"typescript": "~5.4.5",
"unplugin-vue-components": "~0.26.0",
"vite": "~5.2.10",
"vite-plugin-compression": "~0.5.1",
"vue-tsc": "~1.8.27"
}
}

View File

@@ -4,13 +4,15 @@ import { usePrimaryColor } from '@/hooks/useTheme';
import zhCN from 'ant-design-vue/lib/locale/zh_CN';
import enUS from 'ant-design-vue/lib/locale/en_US';
import dayjs from 'dayjs';
import advancedFormat from 'dayjs/plugin/advancedFormat';
import 'dayjs/locale/zh-cn';
import { ref, watch } from 'vue';
import useAppStore from '@/store/modules/app';
import useI18n from '@/hooks/useI18n';
const { t, currentLocale } = useI18n();
const appStore = useAppStore();
dayjs.extend(advancedFormat)
dayjs.locale('zh-cn'); // 默认中文
usePrimaryColor(); // 载入用户自定义主题色

View File

@@ -38,13 +38,13 @@ export async function getParamConfigTopTab(neType: string) {
}
/**
* 查询配置参数标签栏对应信息
* 查询配置参数标签栏对应信息和规则
* @param neType 网元类型
* @param topTag
* @param neId
* @returns object { wrRule, dataArr }
*/
async function getParamConfigInfo(
async function getParamConfigInfoAndRule(
neType: string,
topTag: string,
neId: string
@@ -57,7 +57,6 @@ async function getParamConfigInfo(
params: {
SQL: `SELECT param_json FROM param_config WHERE ne_type = '${neType}' AND top_tag='${topTag}'`,
},
timeout: 1_000,
}),
// 获取对应信息
request({
@@ -66,7 +65,6 @@ async function getParamConfigInfo(
params: {
ne_id: neId,
},
timeout: 1_000,
}),
]).then(resArr => {
let wrRule: Record<string, any> = {};
@@ -108,159 +106,6 @@ async function getParamConfigInfo(
});
}
/**
* 查询配置参数标签栏对应信息-表格处理
* @param neType 网元类型
* @param topTag
* @param neId
* @returns object
*/
export async function getParamConfigInfoTable(
neType: string,
topTag: string,
neId: string
) {
const { wrRule, dataArr } = await getParamConfigInfo(neType, topTag, neId);
// UPF参数不统一
// if (neType === 'UPF') {
// if (Reflect.has(wrRule, 'list')) {
// for (const arr of wrRule['list']) {
// arr['name'] = parseFirstLower(arr['name']);
// }
// for (const item of dataArr) {
// for (const k in item) {
// item[parseFirstLower(k)] = item[k];
// Reflect.deleteProperty(item, k);
// }
// }
// }
// if (Reflect.has(wrRule, 'array')) {
// for (const arr of wrRule['array']) {
// if (Array.isArray(arr['array'])) {
// for (const child of arr['array']) {
// child['name'] = parseFirstLower(child['name']);
// }
// }
// arr['name'] = parseFirstLower(arr['name']);
// }
// for (const item of dataArr) {
// for (const k in item) {
// // 处理子列表
// if (Array.isArray(item[k])) {
// for (const child of item[k]) {
// for (const childKey in child) {
// child[parseFirstLower(childKey)] = child[childKey];
// Reflect.deleteProperty(child, childKey);
// }
// }
// }
// item[parseFirstLower(k)] = item[k];
// Reflect.deleteProperty(item, k);
// }
// }
// }
// }
// 拼装数据
const result = {
code: RESULT_CODE_SUCCESS,
msg: RESULT_MSG_SUCCESS,
data: {
type: 'list' as 'list' | 'array',
data: [] as any[],
dataRule: {},
columns: [] as any[],
},
};
// kv单列表
if (Reflect.has(wrRule, 'list')) {
result.data.type = 'list';
const ruleArr = Object.freeze(wrRule['list']);
// 列表项数据
let dataList = [];
for (const item of dataArr) {
for (const key of Object.keys(item)) {
// 规则为准
for (const rule of ruleArr) {
if (rule['name'] === key) {
const ruleItem = Object.assign({ optional: 'true' }, rule, {
value: item[key],
});
dataList.push(ruleItem);
break;
}
}
}
}
result.data.data = dataList;
// 列表字段
result.data.columns = [
{
title: 'Key',
dataIndex: 'display',
align: 'left',
width: '30%',
},
{
title: 'Value',
dataIndex: 'value',
align: 'left',
width: '70%',
},
];
}
// 多列表
if (Reflect.has(wrRule, 'array')) {
result.data.type = 'array';
const ruleArr = Object.freeze(wrRule['array']);
// 列表项数据
const dataArray = [];
for (const item of dataArr) {
let record: Record<string, any> = {};
for (const key of Object.keys(item)) {
// 规则为准
for (const rule of ruleArr) {
if (rule['name'] === key) {
const ruleItem = Object.assign({ optional: 'true' }, rule, {
value: item[key],
});
record[ruleItem.name] = ruleItem;
break;
}
}
}
dataArray.push(record);
}
result.data.data = dataArray;
// 无数据时,用于新增
let dataRule: Record<string, any> = {};
for (const rule of ruleArr) {
dataRule[rule.name] = rule;
}
result.data.dataRule = dataRule;
// 列表字段
const columns: Record<string, any>[] = [];
for (const rule of ruleArr) {
columns.push({
title: rule.display,
dataIndex: rule.name,
align: 'left',
width: 5,
});
}
result.data.columns = columns;
}
return result;
}
/**
* 查询配置参数标签栏对应信息-表单结构处理
* @param neType 网元类型
@@ -273,7 +118,11 @@ export async function getParamConfigInfoForm(
topTag: string,
neId: string
) {
const { wrRule, dataArr } = await getParamConfigInfo(neType, topTag, neId);
const { wrRule, dataArr } = await getParamConfigInfoAndRule(
neType,
topTag,
neId
);
// 拼装数据
const result = {
@@ -340,6 +189,35 @@ export async function getParamConfigInfoForm(
return result;
}
/**
* 查询配置参数标签栏对应信息
* @param neType 网元类型
* @param topTag
* @param neId
* @returns object
*/
export async function getParamConfigInfo(
neType: string,
topTag: string,
neId: string
) {
// 发起请求
const result = await request({
url: `/api/rest/systemManagement/v1/elementType/${neType.toLowerCase()}/objectType/config/${topTag}`,
method: 'get',
params: {
ne_id: neId,
},
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
return Object.assign(result, {
data: parseObjLineToHump(result.data.data),
});
}
return result;
}
/**
* 查询配置参数标签栏对应信息子节点
* @param neType 网元类型
@@ -462,3 +340,144 @@ export async function updateNeConfigReload(neType: string, neId: string) {
}
return result;
}
/**
* 从参数配置PCF中获取对应信息提供给PCC用户策略输入框
* @param neType 网元类型
* @param topTag
* @param neId
* @returns object { wrRule, dataArr }
*/
export async function getPCCRule(neId: any) {
return await Promise.allSettled([
// 获取参数规则
request({
url: `/api/rest/systemManagement/v1/elementType/pcf/objectType/config/pccRules`,
method: 'get',
params: {
ne_id: neId,
},
timeout: 1_000,
}),
// 获取对应信息
request({
url: `/api/rest/systemManagement/v1/elementType/pcf/objectType/config/sessionRules`,
method: 'get',
params: {
ne_id: neId,
},
timeout: 1_000,
}),
request({
url: `/api/rest/systemManagement/v1/elementType/pcf/objectType/config/qosTemplate`,
method: 'get',
params: {
ne_id: neId,
},
timeout: 1_000,
}),
request({
url: `/api/rest/systemManagement/v1/elementType/pcf/objectType/config/headerEnrichTemplate`,
method: 'get',
params: {
ne_id: neId,
},
timeout: 1_000,
}),
request({
url: `/api/rest/systemManagement/v1/elementType/pcf/objectType/config/serviceAreaRestriction`,
method: 'get',
params: {
ne_id: neId,
},
timeout: 1_000,
}),
]).then(resArr => {
let pccJson: any = new Map();
let sessJson: any = new Map();
let qosJson: any = new Map();
let headerJson: any = new Map();
let sarJson: any = new Map();
// 规则数据
if (resArr[0].status === 'fulfilled') {
const itemV = resArr[0].value;
// 解析数据
if (
itemV.code === RESULT_CODE_SUCCESS &&
Array.isArray(itemV.data?.data)
) {
let itemData = itemV.data.data;
itemData.forEach((item: any) => {
pccJson.set(item.ruleId, { value: item.ruleId, label: item.ruleId });
});
}
}
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;
itemData.forEach((item: any) => {
sessJson.set(item.ruleId, { value: item.ruleId, label: item.ruleId });
});
}
}
if (resArr[2].status === 'fulfilled') {
const itemV = resArr[2].value;
// 解析数据
if (
itemV.code === RESULT_CODE_SUCCESS &&
Array.isArray(itemV.data?.data)
) {
let itemData = itemV.data.data;
itemData.forEach((item: any) => {
qosJson.set(item.qosId, { value: item.qosId, label: item.qosId });
});
}
}
if (resArr[3].status === 'fulfilled') {
const itemV = resArr[3].value;
// 解析数据
if (
itemV.code === RESULT_CODE_SUCCESS &&
Array.isArray(itemV.data?.data)
) {
let itemData = itemV.data.data;
itemData.forEach((item: any) => {
headerJson.set(item.templateName, {
value: item.templateName,
label: item.templateName,
});
});
}
}
if (resArr[4].status === 'fulfilled') {
const itemV = resArr[4].value;
// 解析数据
if (
itemV.code === RESULT_CODE_SUCCESS &&
Array.isArray(itemV.data?.data)
) {
let itemData = itemV.data.data;
itemData.forEach((item: any) => {
sarJson.set(item.name, { value: item.name, label: item.name });
});
}
}
pccJson = Array.from(pccJson.values());
sessJson = Array.from(sessJson.values());
qosJson = Array.from(qosJson.values());
headerJson = Array.from(headerJson.values());
sarJson = Array.from(sarJson.values());
return { pccJson, sessJson, qosJson, headerJson, sarJson };
});
}

View File

@@ -19,7 +19,7 @@ export async function listLicense(query: Record<string, any>) {
// 分页
const pageNum = (query.pageNum - 1) * query.pageSize;
const limtSql = ` order by created_at desc limit ${pageNum},${query.pageSize} `;
const limtSql = ` order by create_time desc limit ${pageNum},${query.pageSize} `;
// 发起请求
const result = await request({

View File

@@ -5,6 +5,7 @@ import {
} from '@/constants/result-constants';
import { language, request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
/**
* 查询网元列表
@@ -15,24 +16,6 @@ export async function listNeInfo(query: Record<string, any>) {
let totalSQL = 'select count(*) as total from ne_info where 1=1 ';
let rowsSQL = 'select * from ne_info where 1=1 ';
// 系统特定顺序
const specificOrder = [
'OMC',
'MME',
'AMF',
'AUSF',
'UDM',
'SMF',
'PCF',
'UPF',
'NRF',
'NSSF',
'IMS',
'N3IWF',
'NEF',
'LMF',
];
// 查询
let querySQL = '';
if (query.neType) {
@@ -70,8 +53,8 @@ export async function listNeInfo(query: Record<string, any>) {
data.rows = itemData.map(v => parseObjLineToHump(v));
//通过sort进行冒泡排序
data.rows.sort((a: any, b: any) => {
const typeA = specificOrder.indexOf(a.neType);
const typeB = specificOrder.indexOf(b.neType);
const typeA = NE_TYPE_LIST.indexOf(a.neType);
const typeB = NE_TYPE_LIST.indexOf(b.neType);
if (typeA === -1) return 1; // 如果不在特定顺序中,排到后面
if (typeB === -1) return -1; // 如果不在特定顺序中,排到后面
return typeA - typeB;
@@ -156,7 +139,7 @@ export async function delNeInfo(data: Record<string, any>) {
/**
* 导出网元配置文件
* @param
* @param data data {neType neId}
* @returns bolb
*/
export function exportSet(data: Record<string, any>) {

View File

@@ -111,6 +111,7 @@ export async function sendNeSoftware(data: Record<string, any>) {
url: `/api/rest/systemManagement/v1/${data.neType}/software/${data.version}/${data.neId}`,
method: 'post',
timeout: 180_000,
repeatSubmit: false,
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
@@ -130,6 +131,7 @@ export async function runNeSoftware(data: Record<string, any>) {
url: `/api/rest/systemManagement/v1/${data.neType}/software/${data.version}/${data.neId}`,
method: 'put',
timeout: 180_000,
repeatSubmit: false,
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
@@ -149,6 +151,7 @@ export async function backNeSoftware(data: Record<string, any>) {
url: `/api/rest/systemManagement/v1/${data.neType}/software/${data.version}/${data.neId}`,
method: 'PATCH',
timeout: 180_000,
repeatSubmit: false,
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {

View File

@@ -294,7 +294,7 @@ export async function exportAll(query: Record<string, any>) {
* @returns bolb
*/
export async function origGet() {
let totalSQL = `select count(*) as value,orig_severity as name from alarm group by orig_severity`;
let totalSQL = `select count(*) as value,orig_severity as name from alarm WHERE alarm_status='1' and orig_severity!='Event' group by orig_severity`;
// 发起请求
const result = await request({
@@ -329,10 +329,10 @@ export async function origGet() {
* @returns object
*/
export async function top3Sel(filterFlag?: string) {
let filter = ` WHERE orig_severity='${filterFlag}'`;
if (!filterFlag) filter = '';
let filter = ` WHERE alarm_status='1'and orig_severity='${filterFlag}'`;
if (!filterFlag) filter = "WHERE alarm_status='1'";
let top3SQL = `select count(*) as value,ne_type as name from alarm ${filter} group by ne_type ORDER BY value desc limit 0,3 `;
let top3SQL = `select count(*) as value,ne_type as name from alarm ${filter} and orig_severity!='Event' group by ne_type ORDER BY value desc limit 0,3 `;
// 发起请求
const result = await request({

View File

@@ -0,0 +1,139 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
import { parseDateToStr } from '@/utils/date-utils';
import useUserStore from '@/store/modules/user';
/**
* 查询列表
* @param query 查询参数
* @returns object
*/
export async function listAct(query: Record<string, any>) {
let totalSQL = `select count(*) as total from alarm_event where 1=1 `;
let rowsSQL = `select * from alarm_event where 1=1 `;
// 查询
let querySQL = '';
if (query.alarmCode) {
querySQL += ` and alarm_code = '${query.alarmCode}' `;
}
if (query.alarmType) {
querySQL += ` and alarm_type = '${query.alarmType}' `;
}
if (query.pvFlag) {
querySQL += ` and pv_flag = '${query.pvFlag}' `;
}
if (query.neId) {
querySQL += ` and ne_id like '%${query.neId}%' `;
}
if (query.neName) {
querySQL += ` and ne_name like '%${query.neName}%' `;
}
if (query.neType) {
querySQL += ` and ne_type like '%${query.neType}%' `;
}
if (query.beginTime && query.endTime) {
querySQL += ` and event_time BETWEEN '${query.beginTime}' and ' ${query.endTime}'`;
}
// 分页
const pageNum = (query.pageNum - 1) * query.pageSize;
const limtSql = ` order by event_time desc limit ${pageNum},${query.pageSize} `;
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm_event`,
method: 'get',
params: {
SQL: 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) => {
console.log(item)
const itemData = item['alarm_event'];
if (Array.isArray(itemData)) {
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
data.total = itemData[0]['total'];
} else {
data.rows = itemData.map(v => parseObjLineToHump(v));
}
}
});
return data;
}
return result;
}
/**
* 事件告警导出
* @param query 查询参数
* @returns bolb
*/
export async function exportAll(query: Record<string, any>) {
let rowsSQL = `select * from alarm_event where 1=1`;
// 查询
let querySQL = '';
querySQL += query.alarm_code
? ` and alarm_code = '${query.alarm_code}' `
: '';
querySQL += query.alarm_type
? ` and alarm_type = '${query.alarm_type}' `
: '';
querySQL += query.pv_flag ? ` and pv_flag = '${query.pv_flag}' ` : '';
querySQL += query.orig_severity
? ` and orig_severity in('${query.orig_severity}' )`
: '';
querySQL += query.ne_id ? ` and ne_id like '%${query.ne_id}%' ` : '';
querySQL += query.ne_name ? ` and ne_name like '%${query.ne_name}%' ` : '';
querySQL += query.ne_type ? ` and ne_type like '%${query.ne_type}%' ` : '';
querySQL +=
query.beginTime && query.endTime
? ` and event_time BETWEEN '${query.beginTime}' and ' ${query.endTime}'`
: '';
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm_event`,
method: 'get',
params: {
rowsSQL: rowsSQL + querySQL,
},
});
if (result.code === RESULT_CODE_SUCCESS) {
let v = result.data.data[0];
const vArr = parseObjLineToHump(v['alarm_event']);
result.data = vArr == null ? [] : vArr;
}
return result;
}

View File

@@ -1,29 +1,14 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch';
import { parseDateToStr } from '@/utils/date-utils';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
/**
* 查询公告列表
* @param query 查询参数
* @returns object
*/
export async function listMain() {
// 系统特定顺序
const specificOrder = [
'OMC',
'MME',
'AMF',
'AUSF',
'UDM',
'SMF',
'PCF',
'UPF',
'NRF',
'NSSF',
'IMS',
'N3IWF',
'NEF',
'LMF',
];
const result = await request({
url: '/api/rest/systemManagement/v1/elementType/all/objectType/systemState',
method: 'get',
@@ -39,7 +24,6 @@ export async function listMain() {
const serialNum = (value as any).serialNum;
const version = (value as any).version;
const errCode = systemState && systemState['errorCode'];
var time = new Date();
// console.log(key, value);
@@ -59,7 +43,7 @@ export async function listMain() {
ipAddress,
serialNum,
name: key.split('/').join('_'),
expiryDate:'-',
expiryDate: '-',
status: 'Abnormal',
};
}
@@ -67,8 +51,8 @@ export async function listMain() {
});
//通过sort进行冒泡排序
mergedData.sort((a: any, b: any) => {
const typeA = specificOrder.indexOf(a.name.split('_')[0]);
const typeB = specificOrder.indexOf(b.name.split('_')[0]);
const typeA = NE_TYPE_LIST.indexOf(a.name.split('_')[0]);
const typeB = NE_TYPE_LIST.indexOf(b.name.split('_')[0]);
if (typeA === -1) return 1; // 如果不在特定顺序中,排到后面
if (typeB === -1) return -1; // 如果不在特定顺序中,排到后面
return typeA - typeB;
@@ -104,5 +88,6 @@ export function getSysConf() {
return request({
url: `/sys-conf`,
method: 'get',
whithToken: false,
});
}

View File

@@ -1,93 +0,0 @@
import {
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
RESULT_MSG_ERROR,
} from '@/constants/result-constants';
import { language, request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
/**
* 查询用户会话列表
* @param query 查询参数
* @returns object
*/
export async function listSession(query: Record<string, any>) {
let totalSQL = 'select count(*) as total from session where 1=1 ';
let rowsSQL = 'select * from session where 1=1 ';
// 查询
let querySQL = '';
if (query.accountId) {
querySQL += ` and account_id like '%${query.accountId}%' `;
}
if (query.ip) {
querySQL += ` and host like '%${query.ip}%' `;
}
// 分页
const pageNum = (query.pageNum - 1) * query.pageSize;
const limtSql = ` limit ${pageNum},${query.pageSize} `;
// 排序
let sortSql = ' order by login_time ';
if (query.sortOrder === 'desc') {
sortSql += ' desc ';
} else {
sortSql += ' asc ';
}
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/omc_db/session`,
method: 'get',
params: {
totalSQL: totalSQL + querySQL,
rowsSQL: rowsSQL + querySQL + sortSql + limtSql,
},
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
const data: DataList = {
total: 0,
rows: [],
code: result.code,
msg: result.msg,
};
result.data.data.forEach((item: any) => {
const itemData = item['session'];
if (Array.isArray(itemData)) {
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
data.total = itemData[0]['total'];
} else {
data.rows = itemData.map(v => parseObjLineToHump(v));
}
}
});
return data;
}
return result;
}
/**
* 强退用户会话
* @param tokenId 授权标识
* @returns object
*/
export async function logoutSession(id: string) {
const result = await request({
url: `/api/rest/databaseManagement/v1/omc_db/session?WHERE=id='${id}'`,
method: 'delete',
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && result.data.data) {
let rows = result.data.data.affectedRows;
if (rows) {
delete result.data;
return result;
} else {
return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_ERROR[language] };
}
}
return result;
}

View File

@@ -67,7 +67,7 @@ export function delNeInfo(infoIds: string | number) {
/**
* 查询网元列表全部无分页
* @param query 查询参数 neType neId bandStatus
* @param query 查询参数 neType neId bandStatus bandHost
* @returns object
*/
export function listAllNeInfo(query: Record<string, any>) {
@@ -99,7 +99,7 @@ export function stateNeInfo(neType: string, neId: string) {
* @param neId 网元ID
* @returns object
*/
export function getTypeAndIDNeInfo(neType: string, neId: string) {
export function getNeInfoByTypeAndID(neType: string, neId: string) {
return request({
url: '/ne/info/byTypeAndID',
method: 'get',
@@ -108,28 +108,69 @@ export function getTypeAndIDNeInfo(neType: string, neId: string) {
}
/**
* 网元端配置文件读取
* 网元端OAM配置文件读取
* @param neType 网元类型
* @param neId 网元ID
* @param filePath 不带文件时重新覆盖返回目录列表
* @returns object
*/
export function getConfigFile(neType: string, neId: string, filePath: string) {
export function getOAMFile(neType: string, neId: string) {
return request({
url: '/ne/info/configFile',
url: '/ne/info/oamFile',
method: 'get',
params: { neType, neId, filePath },
params: { neType, neId },
});
}
/**
* 网元端配置文件写入
* @param data neType网元类型 neId网元ID filePath文件 content内容 sync同步到网元
* @param neType 网元类型
* @param neId 网元ID
* @param content 用json对象
* @param sync 同步到网元
* @returns object
*/
export function saveConfigFile(data: Record<string, any>) {
export function saveOAMFile(data: Record<string, any>) {
return request({
url: `/ne/info/configFile`,
url: `/ne/info/oamFile`,
method: 'put',
data: data,
});
}
/**
* 网元端公共配置文件读取
* @returns object
*/
export function getPara5GFilee() {
return request({
url: '/ne/info/para5GFile',
method: 'get',
});
}
/**
* 网元端公共配置文件写入
* @param content txt内容为字符串 其他文件格式都用json对象
* @param syncNe 同步到网元端 NeType@ NeId
* @returns object
*/
export function savePara5GFile(data: Record<string, any>) {
return request({
url: `/ne/info/para5GFile`,
method: 'put',
data: data,
timeout: 60_000,
});
}
/**
* 网元服务操作
* @param data 对象 {neType,neId,action}
* @returns object
*/
export function serviceNeAction(data: Record<string, any>) {
return request({
url: `/ne/action/service`,
method: 'put',
data: data,
});

View File

@@ -27,41 +27,16 @@ export function getNeLicense(licenseId: string | number) {
}
/**
* 网元授权新增
* @param data 网元对象
* 网元neType和neID查询
* @param neType 网元类型
* @param neId 网元ID
* @returns object
*/
export function addNeLicense(data: Record<string, any>) {
export function getNeLicenseByTypeAndID(neType: string, neId: string) {
return request({
url: `/ne/license`,
method: 'post',
data: data,
});
}
/**
* 网元授权修改
* @param data 网元对象
* @returns object
*/
export function updateNeLicense(data: Record<string, any>) {
return request({
url: `/ne/license`,
method: 'put',
data: data,
});
}
/**
* 网元授权删除
* @param id 信息ID
* @returns object
*/
export function delNeLicense(licenseIds: string | number) {
return request({
url: `/ne/license/${licenseIds}`,
method: 'delete',
timeout: 60_000,
url: `/ne/license/byTypeAndID`,
method: 'get',
params: { neType, neId },
});
}
@@ -81,7 +56,7 @@ export function codeNeLicense(neType: string, neId: string) {
/**
* 网元授权激活授权文件替换
* @param data 网元对象
* @param data 网元对象 {"neType": "", "neId": "", "licensePath": "", "reload": true}
* @returns object
*/
export function changeNeLicense(data: Record<string, any>) {

View File

@@ -36,6 +36,7 @@ export function addNeSoftware(data: Record<string, any>) {
url: `/ne/software`,
method: 'post',
data: data,
repeatSubmit: false,
});
}
@@ -66,15 +67,14 @@ export function delNeSoftware(softwareIds: string | number) {
}
/**
* 网元软件包安装检查
* @param data 网元对象
* 网元软件包设为网元新版本
* @param data data { "version": "2.2404.18", "neType": "SMF", "name": "smf-r2.2404.18-ub22.deb"}
* @returns object
*/
export function checkInstallNeSoftware(data: Record<string, any>) {
export function newNeVersion(data: Record<string, any>) {
return request({
url: `/ne/software/checkInstall`,
url: `/ne/software/newNeVersion`,
method: 'post',
data: data,
timeout: 180_000,
});
}
}

View File

@@ -27,40 +27,15 @@ export function getNeVersion(versionId: string | number) {
}
/**
* 网元版本新增
* @param data 网元对象
* 网元版本操作
* @param data {neType,neId,preinput:{参数},action:"upgrade"}
* @returns object
*/
export function addNeVersion(data: Record<string, any>) {
export function operateNeVersion(data: Record<string, any>) {
return request({
url: `/ne/version`,
url: `/ne/version/operate`,
method: 'post',
data: data,
});
}
/**
* 网元版本修改
* @param data 网元对象
* @returns object
*/
export function updateNeVersion(data: Record<string, any>) {
return request({
url: `/ne/version`,
method: 'put',
data: data,
});
}
/**
* 网元版本删除
* @param id 信息ID
* @returns object
*/
export function delNeVersion(versionIds: string | number) {
return request({
url: `/ne/version/${versionIds}`,
method: 'delete',
timeout: 60_000,
timeout: 180_000,
});
}

View File

@@ -136,6 +136,7 @@ export async function batchUpdateRule(data: Record<string, any>) {
url: `/api/rest/ueManagement/v1/elementType/pcf/objectType/ueInfo/batch/${data.num}?neId=${data.neId}`,
method: 'put',
data: data,
timeout: 60_000,
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
@@ -164,6 +165,7 @@ export async function addRule(data: Record<string, any>) {
url: `/api/rest/ueManagement/v1/elementType/pcf/objectType/ueInfo?neId=${data.neId}`,
method: 'post',
data: data,
timeout: 60_000,
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && result.data?.status) {
@@ -186,6 +188,7 @@ export async function batchAddRule(data: Record<string, any>) {
url: `/api/rest/ueManagement/v1/elementType/pcf/objectType/ueInfo/batch/${data.num}?neId=${data.neId}`,
method: 'post',
data: data,
timeout: 60_000,
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
@@ -209,10 +212,11 @@ export async function batchAddRule(data: Record<string, any>) {
* @param data 规则对象
* @returns object
*/
export function delRule(neId: string, data: Record<string, any>) {
export function delRule(neId: string, imsi: string) {
return request({
url: `/api/rest/ueManagement/v1/elementType/pcf/objectType/ueInfo?neId=${neId}&imsi=${data.imsi}`,
url: `/api/rest/ueManagement/v1/elementType/pcf/objectType/ueInfo?neId=${neId}&imsi=${imsi}`,
method: 'delete',
timeout: 60_000,
});
}
@@ -225,5 +229,6 @@ export async function batchDelRule(data: Record<string, any>) {
return request({
url: `/api/rest/ueManagement/v1/elementType/pcf/objectType/ueInfo/batch/${data.num}?neId=${data.neId}&imsi=${data.imsi}`,
method: 'delete',
timeout: 60_000,
});
}

View File

@@ -28,35 +28,38 @@ export async function listUEInfoBySMF(query: Record<string, any>) {
}
// 模拟数据
// data.code = RESULT_CODE_SUCCESS;
// data.rows = [
// {
// imsi: 'imsi-460029004200044',
// msisdn: 'msisdn-12346002044',
// imsi: 'imsi-460002082101038',
// msisdn: 'msisdn-12307550000',
// pduSessionInfo: [
// {
// activeTime: '2023-11-29 18:39:06',
// activeTime: '2024-05-08 11:08:22',
// dnn: 'ims',
// ipv4: '10.10.48.97',
// ipv6: '',
// pduSessionID: 6,
// ranN3IP: '192.168.8.223',
// sstSD: '1-000001',
// tai: '46000-0001',
// upfN3IP: '192.168.1.161',
// },
// {
// activeTime: '2023-11-29 18:39:05',
// dnn: 'cmnet',
// ipv4: '10.10.48.62',
// ipv4: '10.10.86.2',
// ipv6: '',
// pduSessionID: 5,
// ranN3IP: '192.168.8.223',
// ranN3IP: '192.168.5.100',
// sstSD: '1-000001',
// tai: '46000-0001',
// upfN3IP: '192.168.1.163',
// tai: '46000-001124',
// upState: 'Active',
// upfN3IP: '192.168.14.201',
// },
// {
// activeTime: '2024-05-08 11:08:23',
// dnn: 'cmnet',
// ipv4: '10.10.86.201',
// ipv6: '',
// pduSessionID: 6,
// ranN3IP: '192.168.5.100',
// sstSD: '1-000001',
// tai: '46000-001124',
// upState: 'Active',
// upfN3IP: '192.168.14.201',
// },
// ],
// ratType: 'EUTRAN',
// ratType: 'NR',
// },
// ];
return data;

View File

@@ -0,0 +1,52 @@
import { request } from '@/plugins/http-fetch';
/**
* 首次引导开始
* @returns object
*/
export function bootloaderStart() {
return request({
url: `/bootloader`,
method: 'post',
whithToken: false,
repeatSubmit: false,
});
}
/**
* 首次引导完成
* @returns object
*/
export function bootloaderDone() {
return request({
url: `/bootloader`,
method: 'put',
});
}
/**
* 引导系统数据重置
* @returns object
*/
export function bootloaderReset() {
return request({
url: `/bootloader`,
method: 'delete',
timeout: 180_000
});
}
/**
* 管理员账号变更
* @returns object
*/
export function bootloaderAccount(username: string, password: string) {
return request({
url: `/bootloader/account`,
method: 'put',
data: {
username,
password,
},
});
}

View File

@@ -18,6 +18,7 @@ export async function downloadFile(filePath: string, range?: string) {
method: 'get',
headers: range ? { range } : {},
responseType: 'blob',
timeout: 60_000,
});
}
@@ -79,6 +80,7 @@ export function uploadFile(data: FormData) {
method: 'post',
data,
dataType: 'form-data',
timeout: 180_000,
});
}
@@ -169,6 +171,7 @@ export function chunkCheck(identifier: string, fileName: string) {
url: '/file/chunkCheck',
method: 'post',
data: { identifier, fileName },
timeout: 60_000,
});
}
@@ -188,6 +191,7 @@ export function chunkMerge(
url: '/file/chunkMerge',
method: 'post',
data: { identifier, fileName, subPath },
timeout: 60_000,
});
}
@@ -202,6 +206,7 @@ export function chunkUpload(data: FormData) {
method: 'post',
data,
dataType: 'form-data',
timeout: 60_000,
});
}
@@ -214,6 +219,7 @@ export function transferStaticFile(data: Record<string, any>) {
url: `/file/transferStaticFile`,
method: 'post',
data,
timeout: 60_000,
});
}
@@ -241,6 +247,7 @@ export async function uploadFileToNE(
neType,
neId,
},
timeout: 60_000,
});
return transferToNeFileRes;
}

View File

@@ -0,0 +1,252 @@
//* THIS FILE IS AUTO-GENERATED. DO NOT EDIT.
export default {
af: "Afghanistan",
ax: "Åland Islands",
al: "Albania",
dz: "Algeria",
as: "American Samoa",
ad: "Andorra",
ao: "Angola",
ai: "Anguilla",
aq: "Antarctica",
ag: "Antigua & Barbuda",
ar: "Argentina",
am: "Armenia",
aw: "Aruba",
au: "Australia",
at: "Austria",
az: "Azerbaijan",
bs: "Bahamas",
bh: "Bahrain",
bd: "Bangladesh",
bb: "Barbados",
by: "Belarus",
be: "Belgium",
bz: "Belize",
bj: "Benin",
bm: "Bermuda",
bt: "Bhutan",
bo: "Bolivia",
ba: "Bosnia & Herzegovina",
bw: "Botswana",
bv: "Bouvet Island",
br: "Brazil",
io: "British Indian Ocean Territory",
vg: "British Virgin Islands",
bn: "Brunei",
bg: "Bulgaria",
bf: "Burkina Faso",
bi: "Burundi",
kh: "Cambodia",
cm: "Cameroon",
ca: "Canada",
cv: "Cape Verde",
bq: "Caribbean Netherlands",
ky: "Cayman Islands",
cf: "Central African Republic",
td: "Chad",
cl: "Chile",
cn: "China",
cx: "Christmas Island",
cc: "Cocos (Keeling) Islands",
co: "Colombia",
km: "Comoros",
cg: "Congo - Brazzaville",
cd: "Congo - Kinshasa",
ck: "Cook Islands",
cr: "Costa Rica",
ci: "Côte dIvoire",
hr: "Croatia",
cu: "Cuba",
cw: "Curaçao",
cy: "Cyprus",
cz: "Czechia",
dk: "Denmark",
dj: "Djibouti",
dm: "Dominica",
do: "Dominican Republic",
ec: "Ecuador",
eg: "Egypt",
sv: "El Salvador",
gq: "Equatorial Guinea",
er: "Eritrea",
ee: "Estonia",
sz: "Eswatini",
et: "Ethiopia",
fk: "Falkland Islands",
fo: "Faroe Islands",
fj: "Fiji",
fi: "Finland",
fr: "France",
gf: "French Guiana",
pf: "French Polynesia",
tf: "French Southern Territories",
ga: "Gabon",
gm: "Gambia",
ge: "Georgia",
de: "Germany",
gh: "Ghana",
gi: "Gibraltar",
gr: "Greece",
gl: "Greenland",
gd: "Grenada",
gp: "Guadeloupe",
gu: "Guam",
gt: "Guatemala",
gg: "Guernsey",
gn: "Guinea",
gw: "Guinea-Bissau",
gy: "Guyana",
ht: "Haiti",
hm: "Heard & McDonald Islands",
hn: "Honduras",
hk: "Hong Kong SAR China",
hu: "Hungary",
is: "Iceland",
in: "India",
id: "Indonesia",
ir: "Iran",
iq: "Iraq",
ie: "Ireland",
im: "Isle of Man",
il: "Israel",
it: "Italy",
jm: "Jamaica",
jp: "Japan",
je: "Jersey",
jo: "Jordan",
kz: "Kazakhstan",
ke: "Kenya",
ki: "Kiribati",
kw: "Kuwait",
kg: "Kyrgyzstan",
la: "Laos",
lv: "Latvia",
lb: "Lebanon",
ls: "Lesotho",
lr: "Liberia",
ly: "Libya",
li: "Liechtenstein",
lt: "Lithuania",
lu: "Luxembourg",
mo: "Macao SAR China",
mg: "Madagascar",
mw: "Malawi",
my: "Malaysia",
mv: "Maldives",
ml: "Mali",
mt: "Malta",
mh: "Marshall Islands",
mq: "Martinique",
mr: "Mauritania",
mu: "Mauritius",
yt: "Mayotte",
mx: "Mexico",
fm: "Micronesia",
md: "Moldova",
mc: "Monaco",
mn: "Mongolia",
me: "Montenegro",
ms: "Montserrat",
ma: "Morocco",
mz: "Mozambique",
mm: "Myanmar (Burma)",
na: "Namibia",
nr: "Nauru",
np: "Nepal",
nl: "Netherlands",
nc: "New Caledonia",
nz: "New Zealand",
ni: "Nicaragua",
ne: "Niger",
ng: "Nigeria",
nu: "Niue",
nf: "Norfolk Island",
kp: "North Korea",
mk: "North Macedonia",
mp: "Northern Mariana Islands",
no: "Norway",
om: "Oman",
pk: "Pakistan",
pw: "Palau",
ps: "Palestinian Territories",
pa: "Panama",
pg: "Papua New Guinea",
py: "Paraguay",
pe: "Peru",
ph: "Philippines",
pn: "Pitcairn Islands",
pl: "Poland",
pt: "Portugal",
pr: "Puerto Rico",
qa: "Qatar",
re: "Réunion",
ro: "Romania",
ru: "Russia",
rw: "Rwanda",
ws: "Samoa",
sm: "San Marino",
st: "São Tomé & Príncipe",
sa: "Saudi Arabia",
sn: "Senegal",
rs: "Serbia",
sc: "Seychelles",
sl: "Sierra Leone",
sg: "Singapore",
sx: "Sint Maarten",
sk: "Slovakia",
si: "Slovenia",
sb: "Solomon Islands",
so: "Somalia",
za: "South Africa",
gs: "South Georgia & South Sandwich Islands",
kr: "South Korea",
ss: "South Sudan",
es: "Spain",
lk: "Sri Lanka",
bl: "St. Barthélemy",
sh: "St. Helena",
kn: "St. Kitts & Nevis",
lc: "St. Lucia",
mf: "St. Martin",
pm: "St. Pierre & Miquelon",
vc: "St. Vincent & Grenadines",
sd: "Sudan",
sr: "Suriname",
sj: "Svalbard & Jan Mayen",
se: "Sweden",
ch: "Switzerland",
sy: "Syria",
tw: "Taiwan",
tj: "Tajikistan",
tz: "Tanzania",
th: "Thailand",
tl: "Timor-Leste",
tg: "Togo",
tk: "Tokelau",
to: "Tonga",
tt: "Trinidad & Tobago",
tn: "Tunisia",
tr: "Turkey",
tm: "Turkmenistan",
tc: "Turks & Caicos Islands",
tv: "Tuvalu",
um: "U.S. Outlying Islands",
vi: "U.S. Virgin Islands",
ug: "Uganda",
ua: "Ukraine",
ae: "United Arab Emirates",
gb: "United Kingdom",
us: "United States",
uy: "Uruguay",
uz: "Uzbekistan",
vu: "Vanuatu",
va: "Vatican City",
ve: "Venezuela",
vn: "Vietnam",
wf: "Wallis & Futuna",
eh: "Western Sahara",
ye: "Yemen",
zm: "Zambia",
zw: "Zimbabwe",
};

View File

@@ -0,0 +1,4 @@
import countryTranslations from './countries';
import interfaceTranslations from './interface';
export default { ...countryTranslations, ...interfaceTranslations };

View File

@@ -0,0 +1,14 @@
//* English. Translated by: Jack O'Connor (jackocnr).
export default {
selectedCountryAriaLabel: "Selected country",
noCountrySelected: "No country selected",
countryListAriaLabel: "List of countries",
searchPlaceholder: "Search",
zeroSearchResults: "No results found",
oneSearchResult: "1 result found",
multipleSearchResults: "${count} results found",
// additional countries (not supported by country-list library)
ac: "Ascension Island",
xk: "Kosovo",
};

View File

@@ -0,0 +1,252 @@
//* THIS FILE IS AUTO-GENERATED. DO NOT EDIT.
export default {
al: "阿尔巴尼亚",
dz: "阿尔及利亚",
af: "阿富汗",
ar: "阿根廷",
ae: "阿拉伯联合酋长国",
aw: "阿鲁巴",
om: "阿曼",
az: "阿塞拜疆",
eg: "埃及",
et: "埃塞俄比亚",
ie: "爱尔兰",
ee: "爱沙尼亚",
ad: "安道尔",
ao: "安哥拉",
ai: "安圭拉",
ag: "安提瓜和巴布达",
at: "奥地利",
ax: "奥兰群岛",
au: "澳大利亚",
bb: "巴巴多斯",
pg: "巴布亚新几内亚",
bs: "巴哈马",
pk: "巴基斯坦",
py: "巴拉圭",
ps: "巴勒斯坦领土",
bh: "巴林",
pa: "巴拿马",
br: "巴西",
by: "白俄罗斯",
bm: "百慕大",
bg: "保加利亚",
mp: "北马里亚纳群岛",
mk: "北马其顿",
bj: "贝宁",
be: "比利时",
is: "冰岛",
pr: "波多黎各",
pl: "波兰",
ba: "波斯尼亚和黑塞哥维那",
bo: "玻利维亚",
bz: "伯利兹",
bw: "博茨瓦纳",
bt: "不丹",
bf: "布基纳法索",
bi: "布隆迪",
bv: "布韦岛",
kp: "朝鲜",
gq: "赤道几内亚",
dk: "丹麦",
de: "德国",
tl: "东帝汶",
tg: "多哥",
do: "多米尼加共和国",
dm: "多米尼克",
ru: "俄罗斯",
ec: "厄瓜多尔",
er: "厄立特里亚",
fr: "法国",
fo: "法罗群岛",
pf: "法属波利尼西亚",
gf: "法属圭亚那",
tf: "法属南部领地",
mf: "法属圣马丁",
va: "梵蒂冈",
ph: "菲律宾",
fj: "斐济",
fi: "芬兰",
cv: "佛得角",
fk: "福克兰群岛",
gm: "冈比亚",
cg: "刚果(布)",
cd: "刚果(金)",
co: "哥伦比亚",
cr: "哥斯达黎加",
gd: "格林纳达",
gl: "格陵兰",
ge: "格鲁吉亚",
gg: "根西岛",
cu: "古巴",
gp: "瓜德罗普",
gu: "关岛",
gy: "圭亚那",
kz: "哈萨克斯坦",
ht: "海地",
kr: "韩国",
nl: "荷兰",
bq: "荷属加勒比区",
sx: "荷属圣马丁",
hm: "赫德岛和麦克唐纳群岛",
me: "黑山",
hn: "洪都拉斯",
ki: "基里巴斯",
dj: "吉布提",
kg: "吉尔吉斯斯坦",
gn: "几内亚",
gw: "几内亚比绍",
ca: "加拿大",
gh: "加纳",
ga: "加蓬",
kh: "柬埔寨",
cz: "捷克",
zw: "津巴布韦",
cm: "喀麦隆",
qa: "卡塔尔",
ky: "开曼群岛",
cc: "科科斯(基林)群岛",
km: "科摩罗",
ci: "科特迪瓦",
kw: "科威特",
hr: "克罗地亚",
ke: "肯尼亚",
ck: "库克群岛",
cw: "库拉索",
lv: "拉脱维亚",
ls: "莱索托",
la: "老挝",
lb: "黎巴嫩",
lt: "立陶宛",
lr: "利比里亚",
ly: "利比亚",
li: "列支敦士登",
re: "留尼汪",
lu: "卢森堡",
rw: "卢旺达",
ro: "罗马尼亚",
mg: "马达加斯加",
im: "马恩岛",
mv: "马尔代夫",
mt: "马耳他",
mw: "马拉维",
my: "马来西亚",
ml: "马里",
mh: "马绍尔群岛",
mq: "马提尼克",
yt: "马约特",
mu: "毛里求斯",
mr: "毛里塔尼亚",
us: "美国",
um: "美国本土外小岛屿",
as: "美属萨摩亚",
vi: "美属维尔京群岛",
mn: "蒙古",
ms: "蒙特塞拉特",
bd: "孟加拉国",
pe: "秘鲁",
fm: "密克罗尼西亚",
mm: "缅甸",
md: "摩尔多瓦",
ma: "摩洛哥",
mc: "摩纳哥",
mz: "莫桑比克",
mx: "墨西哥",
na: "纳米比亚",
za: "南非",
aq: "南极洲",
gs: "南乔治亚和南桑威奇群岛",
ss: "南苏丹",
nr: "瑙鲁",
ni: "尼加拉瓜",
np: "尼泊尔",
ne: "尼日尔",
ng: "尼日利亚",
nu: "纽埃",
no: "挪威",
nf: "诺福克岛",
pw: "帕劳",
pn: "皮特凯恩群岛",
pt: "葡萄牙",
jp: "日本",
se: "瑞典",
ch: "瑞士",
sv: "萨尔瓦多",
ws: "萨摩亚",
rs: "塞尔维亚",
sl: "塞拉利昂",
sn: "塞内加尔",
cy: "塞浦路斯",
sc: "塞舌尔",
sa: "沙特阿拉伯",
bl: "圣巴泰勒米",
cx: "圣诞岛",
st: "圣多美和普林西比",
sh: "圣赫勒拿",
kn: "圣基茨和尼维斯",
lc: "圣卢西亚",
sm: "圣马力诺",
pm: "圣皮埃尔和密克隆群岛",
vc: "圣文森特和格林纳丁斯",
lk: "斯里兰卡",
sk: "斯洛伐克",
si: "斯洛文尼亚",
sj: "斯瓦尔巴和扬马延",
sz: "斯威士兰",
sd: "苏丹",
sr: "苏里南",
sb: "所罗门群岛",
so: "索马里",
tj: "塔吉克斯坦",
tw: "台湾",
th: "泰国",
tz: "坦桑尼亚",
to: "汤加",
tc: "特克斯和凯科斯群岛",
tt: "特立尼达和多巴哥",
tn: "突尼斯",
tv: "图瓦卢",
tr: "土耳其",
tm: "土库曼斯坦",
tk: "托克劳",
wf: "瓦利斯和富图纳",
vu: "瓦努阿图",
gt: "危地马拉",
ve: "委内瑞拉",
bn: "文莱",
ug: "乌干达",
ua: "乌克兰",
uy: "乌拉圭",
uz: "乌兹别克斯坦",
es: "西班牙",
eh: "西撒哈拉",
gr: "希腊",
sg: "新加坡",
nc: "新喀里多尼亚",
nz: "新西兰",
hu: "匈牙利",
sy: "叙利亚",
jm: "牙买加",
am: "亚美尼亚",
ye: "也门",
iq: "伊拉克",
ir: "伊朗",
il: "以色列",
it: "意大利",
in: "印度",
id: "印度尼西亚",
gb: "英国",
vg: "英属维尔京群岛",
io: "英属印度洋领地",
jo: "约旦",
vn: "越南",
zm: "赞比亚",
je: "泽西岛",
td: "乍得",
gi: "直布罗陀",
cl: "智利",
cf: "中非共和国",
cn: "中国",
mo: "中国澳门特别行政区",
hk: "中国香港特别行政区",
};

View File

@@ -0,0 +1,4 @@
import countryTranslations from './countries';
import interfaceTranslations from './interface';
export default { ...countryTranslations, ...interfaceTranslations };

View File

@@ -0,0 +1,15 @@
//* Chinese (Simplified). Translated by: Google Translate.
export default {
selectedCountryAriaLabel: "所选国家",
noCountrySelected: "未选择国家/地区",
countryListAriaLabel: "国家名单",
searchPlaceholder: "搜索",
zeroSearchResults: "未找到结果",
oneSearchResult: "找到 1 个结果",
multipleSearchResults: "找到 ${count} 个结果",
// additional countries (not supported by country-list library)
ac: "阿森松岛",
xk: "科索沃",
};

View File

@@ -0,0 +1,145 @@
<!-- https://github.com/jackocnr/intl-tel-input/blob/master/react/src/intl-tel-input/react.tsx -->
<script lang="ts" setup>
import intlTelInput, { Iti, SomeOptions } from 'intl-tel-input';
import 'intl-tel-input/build/css/intlTelInput.min.css';
import 'intl-tel-input/build/js/utils.js';
import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
import useI18n from '@/hooks/useI18n';
const { currentLocale } = useI18n();
const emit = defineEmits(['update:value', 'update:change']);
const props = defineProps({
/**有效检验 */
preciseValidation: {
type: Boolean,
default: false,
},
/**手机号 */
value: {
type: String,
default: '',
},
/**禁用输入 */
disabled: {
type: Boolean,
default: false,
},
/**手机号输入提示 */
placeholder: {
type: String,
default: '',
},
/**手机号输入最大字符串长度 */
maxlength: {
type: Number,
default: 255,
},
/**允许清空手机号输入框 */
allowClear: {
type: Boolean,
},
});
const inputRef = ref<HTMLInputElement | null>(null);
const itiRef = ref<Iti | null>(null);
function fnChange() {
if (!itiRef.value) return;
const num = itiRef.value?.getNumber() || '';
const countryIso = itiRef.value?.getSelectedCountryData().iso2 || '';
// note: this number will be in standard E164 format, but any container component can use
// intlTelInputUtils.formatNumber() to convert this to another format
// as well as intlTelInputUtils.getNumberType() etc. if need be
let data = {
num,
countryIso,
validity: false,
errorCode: -1,
};
const isValid = props.preciseValidation
? itiRef.value.isValidNumberPrecise()
: itiRef.value.isValidNumber();
if (isValid) {
data.validity = true;
data.errorCode = 0;
} else {
const errorCode = itiRef.value.getValidationError();
data.validity = false;
data.errorCode = errorCode;
}
// console.log(data);
emit('update:value', num);
emit('update:change', data);
}
watch(
() => props.value,
v => {
if (v) {
itiRef.value?.setNumber(v);
} else {
itiRef.value?.setNumber('');
}
}
);
onMounted(() => {
nextTick(async () => {
if (inputRef.value) {
let i18n = undefined;
let initialCountry = 'us';
// 语言文件导入问题只能复制到项目内处理
// import fr from "intl-tel-input/i18n/fr";
if (currentLocale.value.startsWith('zh')) {
const { default: zh } = await import('./i18n/zh');
i18n = zh;
initialCountry = 'cn';
} else {
const { default: en } = await import('./i18n/en');
i18n = en;
initialCountry = 'us';
}
itiRef.value = intlTelInput(inputRef.value, {
initialCountry: initialCountry,
formatOnDisplay: true,
autoPlaceholder: 'polite',
i18n: i18n,
} as SomeOptions);
inputRef.value.addEventListener('countrychange', fnChange);
}
});
});
onBeforeUnmount(() => {
if (inputRef.value) {
inputRef.value.removeEventListener('countrychange', fnChange);
}
itiRef.value?.destroy();
});
</script>
<template>
<input
type="tel"
class="ant-input"
ref="inputRef"
:value="value"
:disabled="disabled"
:placeholder="placeholder"
:maxlength="maxlength"
:allow-clear="allowClear"
@input="fnChange"
/>
</template>
<style lang="css">
.iti {
display: block;
}
.iti .iti__country-container .iti__search-input {
padding: 4px 8px;
}
</style>

View File

@@ -53,30 +53,32 @@ function backLogin() {
const isLocked = computed(() => lockedStore.type !== 'none');
onMounted(() => {
getConfigKey('sys.lockTime')
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && res.data) {
lockedStore.lockTimeout = res.data;
timeoutDuration = res.data * 1000;
}
// 本地锁定类型设置
lockedStore.fnLock(lockedStore.type);
})
.finally(() => {
if (timeoutDuration !== 0) {
resetTimeout();
// 监听用户的操作,重置超时时间
window.addEventListener('mousemove', resetTimeout);
window.addEventListener('keydown', resetTimeout);
}
});
// 本地锁定类型设置;
lockedStore.fnLock(lockedStore.type);
// getConfigKey('sys.lockTime')
// .then(res => {
// if (res.code === RESULT_CODE_SUCCESS && res.data) {
// lockedStore.lockTimeout = res.data;
// timeoutDuration = res.data * 1000;
// }
// // 本地锁定类型设置
// lockedStore.fnLock(lockedStore.type);
// })
// .finally(() => {
// if (timeoutDuration !== 0) {
// resetTimeout();
// // 监听用户的操作,重置超时时间
// window.addEventListener('mousemove', resetTimeout);
// window.addEventListener('keydown', resetTimeout);
// }
// });
});
/**组件实例被卸载之后调用 */
onUnmounted(() => {
if (timeoutId) {
clearTimeout(timeoutId);
}
// if (timeoutId) {
// clearTimeout(timeoutId);
// }
});
</script>
<template>
@@ -141,6 +143,17 @@ onUnmounted(() => {
{{ t('components.LockScreen.backReload2') }}
</div>
</div>
<!-- 锁屏-OMC系统重置 -->
<div class="lock-screen_reload" v-if="lockedStore.type === 'reset'">
<LoadingOutlined style="font-size: 56px" />
<div class="text">
{{ t('components.LockScreen.systemReset') }}
</div>
<div class="desc">
{{ t('components.LockScreen.systemReset2') }}
</div>
</div>
</a-modal>
</template>

View File

@@ -62,7 +62,7 @@ function fnTableColumnsCheckAllChange(e: any) {
/**表格字段列拖拽操作 */
function fnTableColumnsDrop(dropResult: any) {
const { removedIndex, addedIndex, payload } = dropResult;
if (!removedIndex || !addedIndex) {
if (removedIndex === null || addedIndex === null) {
return;
}
let itemToAdd = payload;
@@ -184,6 +184,7 @@ onMounted(() => {
<a-button
v-if="c.fixed !== undefined"
size="small"
:title="c.fixed ? `Fixed ${c.fixed} side` : ''"
:type="c.fixed ? 'primary' : 'dashed'"
@click="fnTableColumnsFixed(c)"
>

View File

@@ -1,8 +1,8 @@
<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { FitAddon } from 'xterm-addon-fit';
import { Terminal } from 'xterm';
import 'xterm/css/xterm.css';
import { FitAddon } from '@xterm/addon-fit';
import { Terminal } from '@xterm/xterm';
import '@xterm/xterm/css/xterm.css';
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
import { OptionsType, WS } from '@/plugins/ws-websocket';
const ws = new WS();

View File

@@ -1,9 +1,9 @@
<script lang="ts" setup>
import { message } from 'ant-design-vue/lib';
import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { FitAddon } from 'xterm-addon-fit';
import { Terminal } from 'xterm';
import 'xterm/css/xterm.css';
import { FitAddon } from '@xterm/addon-fit';
import { Terminal } from '@xterm/xterm';
import '@xterm/xterm/css/xterm.css';
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
import { OptionsType, WS } from '@/plugins/ws-websocket';
const ws = new WS();
@@ -19,6 +19,16 @@ const props = defineProps({
type: String,
required: true,
},
/**窗口单行字符数 */
cols: {
type: Number,
default: 100,
},
/**窗口行数 */
rows: {
type: Number,
default: 128,
},
/**禁止输入 */
disable: {
type: Boolean,
@@ -238,6 +248,8 @@ onMounted(() => {
url: '/ws/telnet',
params: {
hostId: props.hostId,
cols: props.cols,
rows: props.rows,
},
onmessage: wsMessage,
onerror: wsError,

View File

@@ -0,0 +1,97 @@
<script lang="ts" setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import { FitAddon } from '@xterm/addon-fit';
import { Terminal } from '@xterm/xterm';
import '@xterm/xterm/css/xterm.css';
const emit = defineEmits(['update:value']);
const props = defineProps({
/**终端ID必传 */
id: {
type: String,
required: true,
},
/**窗口单行字符数 */
cols: {
type: Number,
default: 80,
},
/**窗口行数 */
rows: {
type: Number,
default: 40,
},
/**信息 */
value: {
type: String,
default: '',
},
});
/**终端输入DOM节点实例对象 */
const terminalDom = ref<HTMLElement | undefined>(undefined);
/**终端输入实例对象 */
const terminal = ref<any>(null);
/**终端输入渲染 */
function handleRanderXterm(container: HTMLElement | undefined) {
if (!container) return;
const xterm = new Terminal({
cols: props.cols,
rows: props.rows,
lineHeight: 1.2,
fontSize: 12,
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
theme: {
background: '#000000',
},
cursorBlink: false, // 光标闪烁
cursorStyle: 'block',
scrollback: 1000,
scrollSensitivity: 15,
tabStopWidth: 4,
disableStdin: true, // 禁止输入
});
// 挂载
xterm.open(container);
// 自适应尺寸
const fitAddon = new FitAddon();
xterm.loadAddon(fitAddon);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
fitAddon.fit();
});
// 监听元素大小变化
observer.observe(container);
terminal.value = xterm;
}
onMounted(() => {
nextTick(() => {
handleRanderXterm(terminalDom.value);
// 初始发送命令
if (typeof props.value === 'string') {
terminal.value.write(props.value);
}
});
});
onBeforeUnmount(() => {
if (terminal.value != null) {
terminal.value.dispose();
}
});
</script>
<template>
<div ref="terminalDom" :id="id" class="terminal"></div>
</template>
<style lang="css" scoped>
.terminal {
width: 100%;
height: 100%;
}
</style>

View File

@@ -1,18 +1,26 @@
/**网元列表,默认顺序 */
export const NE_TYPE_LIST = [
'OMC',
'MME',
'IMS',
'AMF',
'AUSF',
'UDM',
'SMF',
'PCF',
'UPF',
'NRF',
'NSSF',
'IMS',
'N3IWF',
'NEF',
'NRF',
'UPF',
'LMF',
'NEF',
'MME',
'N3IWF',
'MOCNGW',
'SMSC',
];
/**
* 网元拓展包列表,默认顺序
* IMS-adb/rtproxy/mf
* UDM-adb
*/
export const NE_EXPAND_LIST = ['ADB', 'RTPROXY', 'MF'];

View File

@@ -2,6 +2,7 @@ export default {
// 语言
i18n: 'English',
hello: 'Hello',
welcome: 'Welcome, Core Network Management Platform',
// 通用
common: {
@@ -34,7 +35,6 @@ export default {
unableNull:' Cannot be empty',
moreText: 'More',
searchBarText: 'Search bar',
tableStripedText: 'Form Zebra',
reloadText: 'Refresh',
columnSetText: 'Column Setting',
columnSetTitle: 'Column Display / Sorting',
@@ -53,9 +53,15 @@ export default {
fold: 'Fold',
},
rowId: 'ID',
createTime: 'Create Time',
updateTime: 'Update Time',
remark: 'Remark',
description: 'Description',
operate: 'Operation',
operateOk: 'Operation Successful!',
operateErr: 'Operation Failed!',
copyText: "Copy",
copyOk: 'Copy Successful!',
units: {
second: 'Second',
minute: 'Minute',
@@ -129,7 +135,9 @@ export default {
validError:'Validation Failure',
backLogin:'Logout to Relogin',
backReload:'Restarting now, please wait...',
backReload2:'When ready, your browser will automatically refresh.',
backReload2:'When ready, Your browser will automatically refresh.',
systemReset:'Resetting now, please wait...',
systemReset2:'Data information is being reset.',
},
},
@@ -164,6 +172,9 @@ export default {
codeHit: 'Verification code',
codeText: 'Obtain verification code',
codeSmsSend: 'Successfully sent, please pay attention to checking the SMS',
ipPlease: 'Please enter a valid IP address',
ipv4Reg: 'Not a valid IPv4 address',
ipv6Reg: 'Not a valid IPv6 address',
},
// 布局
@@ -356,7 +367,7 @@ export default {
pvflag:'PV Flag',
pnf:'Physical Network Element',
vnf:'Virtual Network Element',
province:'Province',
province:'Region',
vendorName:'Vendor Name',
dn:'Network Identification',
reload: 'Reload',
@@ -424,15 +435,14 @@ export default {
letUpTime:'Activation time',
createTime:'Creation time',
onlyAble:'Only upload file format {fileText} is supported',
nullData:'No network element list data yet',
nullVersion:'There is no rollback version for the current network element.',
},
license: {
neTypePlease: 'Select network element type',
neType: 'NE Type',
fileName: 'File Name',
createTime: 'Uploaded Time',
comment: 'File Description',
serialNum: 'Serial Num',
createTime: 'Time',
comment: 'Description',
updateComment: 'License Description',
updateCommentPlease: 'Please enter a license description',
updateFile: 'License File',
@@ -471,21 +481,21 @@ export default {
neType: 'NE Type',
neTypePleace: "Please select the network element type",
noConfigData: "No data on configuration items",
updateValue: "The value of the {num} attribute was modified successfully.",
updateValue: "[ {num} ] parameter value modified successfully.",
updateValueErr: "Attribute value modification failure",
updateItem: "Modify Index to {num}.",
updateItemErr: "Record modification failure",
delItemOk: "Deleting Index as {num} succeeded",
addItemOk: "Add Index as {num} Record Succeeded",
addItemErr: "Record addition failure",
requireUn: "{display} input value is of unknown type",
requireString: "The {display} parameter value is invalid.",
requireInt: "{display} Parameter value not in reasonable range {filter}",
requireIpv4: "{display} not a legitimate IPV4 address",
requireIpv6: "{display} Not a legitimate IPV6 address.",
requireEnum: "{display} is not a reasonable enumeration value.",
requireBool: "{display} is not a reasonable boolean value.",
editOkTip: "Confirm updating the value of this {num} attribute?",
requireUn: "[ {display} ] input value is of unknown type",
requireString: "[ {display} ] parameter value is invalid.",
requireInt: "[ {display} ] parameter value not in reasonable range {filter}",
requireIpv4: "[ {display} ] not a legitimate IPV4 address",
requireIpv6: "[ {display} ] not a legitimate IPV6 address.",
requireEnum: "[ {display} ] is not a reasonable enumeration value.",
requireBool: "[ {display} ] is not a reasonable boolean value.",
editOkTip: "Confirm updating the value of this [ {num} ] attribute?",
updateItemTip: "Confirm updating the data item with Index [{num}]?",
delItemTip: "Confirm deleting the data item with Index [{num}]?",
arrayMore: "Expand",
@@ -575,14 +585,42 @@ export default {
},
},
ne: {
neInfo: {
version: "Version",
common: {
neType: 'NE Type',
neTypePlease: "Please select network element type",
neTypeTip: 'Fill in the type of network element to be created, e.g. SMF.',
neId: 'NE ID',
neIdPlease: 'Please enter the network element identification',
neIdTip: 'Fill in the unique identifier of the network element binding',
rmUid: 'Resource Unique ID',
rmUidPlease: 'Please enter a resource unique ID',
rmUidTip: "Tagging for data reporting of network element logs, alarms, metrics, etc.",
neName: 'NE Name',
neNamePlease: 'Please enter the name of the network element',
ipAddr: 'IP Addr',
ipAddrPlease: 'Please enter the IP address of the network element',
ipAddrTip: "Support IPV4/IPV6, synchronized change of configuration address",
port: 'Port',
portTip: "Network element port default:33030",
serialNum: 'Serial Number',
expiryDate: 'Expiry Date',
normalcy: 'Normal',
exceptions: 'Abnormal',
restart: 'Restart',
restartTip: 'Are you sure you want to restart the network element service?',
start: 'Start',
startTip: 'Are you sure you want to start the network element service?',
stop: 'Stop',
stopTip: 'Are you sure you want to stop the network element service?',
reload: 'Reload',
reloadTip: 'Confirm that you want to reload the network element configuration information?',
oam: 'OAM',
log: 'Logs',
},
neInfo: {
version: "Version",
state: "State",
serviceState: "Service Status",
normalcy: "Normal",
exceptions: "Abnormal",
serviceState: "Service Status",
info: 'Status Message',
resourceInfo: 'Resource Situation',
sysMem: "SYS Mem",
@@ -590,8 +628,41 @@ export default {
sysDisk: "SYS Store",
neCpu: "NE CPU",
hostConfig: "Connection Configuration",
rmUID: "Data resource location identifiers are used for data tagging such as logging, alarm reporting, etc.",
ipAddr: "Support IPV4/IPV6, synchronize the change of the configuration address of the following configuration",
pvflag: 'NE Virtualization',
pnf: 'physical network element',
vnf: 'virtual network element',
neAddress: 'MAC',
neAddressTip: 'Record the physical address (MAC) of the network card of the network element',
dn: 'network identifier',
vendorName: 'provider',
province: 'Service Area',
addTitle: 'New network element information',
editTitle: 'Edit network element information',
delTip: 'Confirm deletion of network element information data items?',
oam: {
title: 'OAM Configuration',
sync: 'Sync to NE',
oamEnable: 'Service',
oamPort: 'Port',
snmpEnable: 'Service',
snmpPort: 'Port',
kpiEnable: 'Report',
kpiTimer: 'Reporting Cycle',
kpiTimerPlease: 'Please enter the reporting period (in seconds)',
},
backConf: {
export: 'Config Export',
import: 'Config Import',
title: 'Configuration File Import',
importType: 'Source of File',
server:'Server File',
local:'Local File',
localUpload:'Local Upload',
exportTip:'Confirm that you want to export the network element configuration file?',
exportMsg:'Exported successfully, please download from [Backup Management].',
filePlease: "Please upload a file",
fileNamePlease: 'Please select the server file',
},
},
neHost: {
hostType: "Type",
@@ -610,13 +681,13 @@ export default {
privateKey: "Private Key",
privateKeyPlease: "Please fill in the private key characters correctly ~/.ssh/id_rsa",
passPhrase: "Private Key Cipher",
remark: "Remark",
createTime: "Time",
delTip: "Confirm that you want to delete the host number [{num}]?",
addTitle: "Add Host Connection",
editTitle: "Edit Host Connection",
test: "Test Connection To Host",
test: "Test Connection",
testOk: "Test Connection Successful",
authRSA: 'Secret Authorization',
authRSATip: "Do I have to configure secret-free authorization?",
},
neHostCmd: {
cmdType: "Type",
@@ -625,12 +696,125 @@ export default {
titlePlease: "Please fill in the command name correctly",
command: "Command",
commandPlease: "Please enter a valid command string correctly",
remark: "Remark",
createTime: "Time",
delTip: "Are you sure you want to delete the message with command number [{num}]?",
addTitle: "New Host Commands",
editTitle: "Edit Host Commands",
},
neSoftware: {
uploadTitle: "Update Software",
upload: "Upload",
uploadNotFile: "No software files uploaded",
uploadBatch: "Update Softwares",
uploadBatchMax: "Multiple packages can be uploaded, with up to {txt} selected at the same time.",
uploadFileName: "Parses file names in the format of: amf-r2.240x.xx-xxx",
name: "File Name",
path: "Software File",
pathPlease: "Please upload the software package file",
version: "Software Version",
versionPlease: "Please enter the software version number",
delTip: "Confirmed to remove the package?",
downTip: "Confirmation to download package [{txt}]?",
fileCheckType: 'The corresponding network element type is not resolved',
fileCheckVer: 'The corresponding version number is not resolved',
fileTypeNotEq: 'Not a specified network element type {txt}',
fileTypeExists: 'Same type of file already exists',
fileNameExists: 'File with same name already exists',
fileCheckTypeDep: 'The specified dependency package type is not resolved',
dependFile: 'Software Dependencies',
dependFileTip: 'File name resolution is the same as above, and installation is based on the order of uploading.',
},
neVersion: {
upgrade: "Upgrade To New Version",
upgradeTip: "Confirmed to upgrade to the new version?",
upgradeTipEmpty: "There are currently no new versions available",
upgradeTipEqual: "Current version is the same as the new version",
rollback: 'Switch to previous version',
rollbackTip: "Confirm switching to the previous version?",
rollbackTipEmpty: "There is currently no previous version available",
rollbackTipEqual: 'The current version is the same as the previous version',
version: "Current Version",
preVersion: "Previous Version",
newVersion: "New Version",
status: "Revision Status",
upgradeBatch: "Batch Upgrade",
upgradeBatchTip: "Do you perform new version upgrades on checked records?",
upgradeNotNewVer: 'No new version found',
upgradeDone: 'Update complete, service being reloaded',
upgradeFail: 'The update failed, check if the service terminal environment is available!',
upgradeModal: 'Network Element Version Updates',
},
neLicense: {
status: "License Status",
change: "Change License",
reload: "Refresh Info",
reloadTip: "Confirmed to refresh license information?",
reloadBatch: "Batch Refresh",
reloadBatchTip: "Do you do an information refresh on checked records?",
updateTtile: "Update License",
downCodeTop: "Confirmed to save the license activation code to a file?",
activationRequestCode: "License Activation Code",
licensePath: "License File",
licensePathTip: "Please upload license file",
upload: 'Upload',
uploadBatch: "Upload License",
uploadChangeFail: "Some network elements failed to update the license, please check whether the service terminal environment is available!",
},
neConfPara5G: {
save: 'Save',
reload: 'Reload',
title: 'Save Info',
sync: 'Sync to NE',
syncNe: 'Select NE',
syncNeDone: 'Synchronization to network element terminals complete',
saveOk: 'Save Success!',
},
neQuickSetup: {
stepPrev: 'Previous',
stepPrevTip: 'Confirm that you want to abandon the current change and return to the previous step?',
stepNext: 'Next',
stepSave: 'Save',
startTitle: 'Service Terminal Environment',
startDesc: 'Test connectivity to network element services',
startStepNext: 'Confirm that you want to proceed to the next step to configure the network element information?',
addr: 'Terminal IP',
kernelName: 'System',
kernelRelease: 'Kernel',
machine: 'Framework',
prettyName: 'Platform',
prettyNameTip: 'Support Ubuntu',
nodename: 'Node Name',
auth: 'Competence Grant',
sudo: 'sudo',
sudoErr: 'To ensure that you have permission to install packages, configure to grant the current user permission to allow passwordless sudo privileges.',
sshLink: 'confidentiality',
configTitle: "Configuring Network Elements",
configDesc: "Fill in the basic information of the network element",
configAddTitle: 'New Tips',
configAddTip: 'Is it added as new network element information and continue?',
configUpdateTitle: 'Update Tips',
configUpdateTip: 'Does it update to the already existing network element information and continue?',
configStepNext: 'Confirm that you want to proceed to the next step for the Network Element software installation?',
installTitle: "Network Element Installation",
installDesc: "Installation to Service Terminal",
installConfirmTip: 'Are you sure you want to install package [{name}]?',
installStepNext: 'Confirm that you want to proceed to the next step for network element authorization?',
installSource: 'Software Source',
installSourceOption: 'Uploaded',
installSourceUpload: 'New Upload',
installSelect: 'Select Record',
installUpload: 'Upload File',
installText: 'Installed',
licenseTitle: "Licenses",
licenseDesc: "Network element service authorization certification",
licenseResultTitle: "Whether to authorize activation immediately",
licenseResultTitleOk: 'Successful Activation',
licenseUpload: 'License',
licenseEnd: 'Finish',
licenseEndTip: "Confirmed to end the installation?",
licenseCheack: 'Waiting for network element validation',
licenseTip1: '1. Click [License] to get the license activation code, and then contact the network element vendor for activation.',
licenseTip2: '2. Clicking [Finish] will end the installation process.',
},
},
neUser: {
auth: {
@@ -644,7 +828,7 @@ export default {
import: 'Import',
loadDataConfirm: 'Are you sure you want to reload the data?',
loadData: 'Load Data',
loadDataTip: 'Successfully fetched load data: {num} entries, the system is internally updating the data. You can click reset to refresh the data list after the loading is finished, please don it repeat click to get update!!!!',
loadDataTip: 'Successfully fetched load data: {num} entries, the system is internally updating the data. Please wait a moment!!!!',
startIMSI: 'Start IMSI',
batchAddText: 'Batch Add',
batchDelText: 'Batch Delete',
@@ -671,7 +855,7 @@ export default {
import: 'Import',
loadDataConfirm: 'Are you sure you want to reload the data?',
loadData: 'Load Data',
loadDataTip: 'Successfully fetched load data: {num} entries, the system is internally updating the data. You can click reset to refresh the data list after the loading is finished, please don it repeat click to get update!!!!',
loadDataTip: 'Successfully fetched load data: {num} entries, the system is internally updating the data. Please wait a moment!!!!',
numAdd: 'Number of releases',
numDel: 'Number of deleted',
checkDel: 'Check Delete',
@@ -709,6 +893,7 @@ export default {
addTitle: 'Adding Policy Control Information',
updateTitle: '{imsi} Policy control information',
startIMSI: 'Start IMSI',
batchOper: 'Batch Operations',
batchAddText: 'Batch Add',
batchDelText: 'Batch Delete',
batchUpdateText: 'Batch Modify',
@@ -717,9 +902,19 @@ export default {
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.',
checkDel: 'Check Delete',
delSure:'Are you sure you want to delete the user with IMSI number: {imsi}?',
uploadFileOk: 'File Upload Successful',
uploadFileErr: 'File Upload Failed',
pccRuleTip:'PCC policy rule template (corresponding to parameter configuration -PCC Rules)',
sessRuleTip:' Session policy rule template (corresponding to parameter configuration-session Rules)',
qosAudioTip:' Voice call QoS(corresponding parameter configuration -QoS Template QoS ID)',
qosVideoTip:' Video call QoS(corresponding parameter configuration -QoS Template QoS ID)',
hdrTip:'HTTP Header enhancement (corresponding parameter configuration -Header Enrich Template)',
ueTip:'UE policy template (example: uep_001)',
sarTip1:' Service area Restriction ',
sarTip2:'(corresponding parameter setting -Service Area Restriction)',
rfsfTip:'RAT Frequency Selection Priority',
},
base5G: {
neType: 'NE Object',
@@ -755,7 +950,7 @@ export default {
viewTask:'View Task',
editTask:'Edit Task',
addTask:'Add Task',
stopTask:'Pending',
stopTask:'Stop',
errorTaskInfo: 'Failed to obtain task information',
granulOptionPlease:'Please select the measurement granularity',
letupSure:'Confirm activation of task with number [{id}]?',
@@ -815,7 +1010,7 @@ export default {
exportSure:'Confirm whether to export all statistical data',
exportEmpty: "Export data is empty",
showChartSelected: "Show All",
realTimeData: "Real Time 5s Data",
realTimeData: "Real Time Data",
},
customTarget:{
kpiId:' Custom Indicator',
@@ -944,7 +1139,7 @@ export default {
alarmSeq:'Sequence Number',
objectName:'Object Name',
locationInfo:'Location Info',
province:'Province',
province:'Region',
addInfo:'Additional Info',
specificProblemId:'Cause ID',
specificProblem:'Cause',
@@ -965,6 +1160,7 @@ export default {
showSet:'Show filter settings',
exportSure:'Confirm whether to export all active alarm information',
viewIdInfo:'View {alarmId} record information',
closeModal:'Close',
},
historyAlarm:{
exportSure:'Confirm whether to export all historical alarm information',
@@ -977,13 +1173,16 @@ export default {
noChange:'There is no change in the alarm forwarding settings.',
forwardSet:'Alarm Forwarding Setting',
},
eventAlarm:{
exportSure:'Confirm whether to export all event alarm information',
}
},
logManage:{
alarm:{
type:'NE Type',
neId:'NE UID',
alarmId:'Alarm ID',
alarmSeq:'Sequece Number',
alarmSeq:'Sequence Number',
alarmCode:'Alarm Code',
alarmStatus:'Status',
eventTime:'Event Time',
@@ -1002,12 +1201,13 @@ export default {
type:'NE Type',
neId:'NE UID',
alarmId:'Alarm ID',
alarmSeq:'Sequece Number',
alarmObj:'Object',
alarmSeq:'Sequence Number',
alarmObj:'Forward Users',
alarmInter:'Forward Interface',
alarmTitle:'Alarm Title',
alarmInfo:'Alarm Content',
eventTime:'Generation Time',
logTime:'Record Time'
alarmInfo:'Operation Results',
eventTime:'Event Time',
logTime:'Log Time'
},
neFile: {
neType:'NE Type',
@@ -1152,12 +1352,12 @@ export default {
jobLog: {
jobName: "Job Name",
jobGroup: "Job Group",
invokeTarget: "Invoke Target",
invokeTarget: "Job Invoke",
status: "Status",
status0: "Failures",
status1: "Active",
createTime: "Create Time",
costTime: "Cost Time",
createTime: "Time",
costTime: "Time Consumption",
viewLog: "Scheduling log Info",
delTip: "Are you sure you want to delete the scheduling log entry with the number [{num}]?",
delOk: "Deleted Successfully",
@@ -1191,7 +1391,7 @@ export default {
cacheInfo: {
baseInfo: "Basic Info",
version: "Service Versions",
mode: "Perating Mode",
mode: "Operating Mode",
modeStandalone: "stand-alone",
modeClusters: "clusters",
port: "Port",
@@ -1392,7 +1592,7 @@ export default {
userInfo:' User Info',
userNum: 'User Number',
account: 'Account',
userName: 'User Name',
userName: 'Nick Name',
permission: 'Role',
className: 'Department',
loginIp: 'Login Address',
@@ -1400,7 +1600,7 @@ export default {
status: 'Status',
userNameTip:'The account cannot start with a number and can contain uppercase and lowercase letters, numbers, and no less than 5 digits',
passwdTip:'The password should contain at least uppercase and lowercase letters, numbers, special symbols, and no less than 6 digits',
nickNameTip:'Nicknames can only contain letters, numbers, Chinese characters, and underscores, with no less than 2 digits',
nickNameTip:'Nicknames no less than 2 digits',
emailTip:'Please enter the correct email address',
phoneTip:'Please enter the correct phone number',
resetPwd:'Reset Password',
@@ -1423,7 +1623,7 @@ export default {
userWork:'User position',
userWorkPlease: 'Please select user post',
userTip:'User Description',
loginPwd:'Login password',
loginPwd:'Password',
updateSure:'Do you want to update existing data',
downloadObj:'Download Tpl',
importTitle:'User Import',
@@ -1509,6 +1709,9 @@ export default {
i18nOpen: "Display Switch",
i18nDefault: "Default Languages",
i18nInstruction: 'Whether to display the internationalization switch and set the system default language',
reset: "System Reset",
resetInstruction: "A system reset will erase all data in the current system, please proceed with caution!!!!",
resetTipContent: 'Are you sure you want to clear all data from the current system and insist on continuing?',
},
role:{
allScopeOptions:'All data permissions',
@@ -1516,11 +1719,11 @@ export default {
onlyClassScopeOptions:'Data permissions of this department',
classAllScopeOptions:'Data permissions for this department and the following',
myselfScopeOptions:'Only personal data permissions',
roleId:'Role ID',
roleId:'Role Number',
roleName:'Role Name',
roleKey:'Role Key',
roleSort:'Role Order',
roleStatus:'Status',
roleSort:'Role Sort',
roleStatus:'Role Status',
createTime:'Creation Time',
trueValue:'Please enter {msg} correctly',
roleInfo:'Role information',
@@ -1533,10 +1736,10 @@ export default {
distributeUser:'Assign Users',
roleMark:'Role Description',
menu:'Menu Permissions',
roleKeyTip:"Example of permission identification: Use permission identification in dba controller, such as: @PreAuthorize({ hasRoles: ['dba'] })",
roleKeyTip:"Privilege identifiers are used to control page controls or routing interfaces",
openSwitch:'Expand/Collapse',
selAllSwitch:'Select all/Deselect all',
relationSwitch:'Father and son linkage',
selAllSwitch:'Select All/Deselect All',
relationSwitch:'Node Linkage',
normal:'Active',
stop:'Pending',
preScope:'Scope of authority',
@@ -1578,7 +1781,7 @@ export default {
positionId:'Position Number',
positionCode:'Position Code',
positionName:'Position Name',
positionSort:'Position Sorting',
positionSort:'Position Sort',
positionStatus:'Position Status',
positionMark:'Position Description',
createTime:'Creation Time',
@@ -1588,15 +1791,15 @@ export default {
},
log:{
operate:{
operId:'ID',
operId:'Log ID',
moduleName:'Module Name',
workType:'Business Type',
operUser:'Operator',
requestMe:'Request Method',
host:'Request Host',
operStatus:'Operation Status',
operDate:'Operation Date',
useTime:'Consumption Time',
operStatus:'Status',
operDate:'Time',
useTime:'Time Consumption',
logInfo:'Operation Log Information',
delSure:'Are you sure to delete the data item with access number [{ids}]?',
delAllSure:'Confirm to clear all login log data items?',
@@ -1703,6 +1906,65 @@ export default {
exportOk: "Completed export",
typeDataErr: "Failed to get dictionary type information",
},
quickStart: {
start: 'Start Setup',
skip: 'Skip',
finish: 'Complete Setup',
stepPrev: 'Previous',
stepNext: 'Next',
exit: 'Exit',
save: 'Save Info',
sysTitle: 'System Configuration',
sysAdmin: 'Administrator',
sysInfo: 'System Info',
sysLogo: 'System Logo',
sysLogoTip: 'Show the image to the system logo area to see the effect, please use a transparent background, the size of the proportion to adapt to the size of the area',
sysName: 'System Name',
sysNameTip: 'Limit system name to 20 characters in length',
sysUploadLogo: 'Confirmation of uploading the system logo file?',
sysUploadOk: 'File uploaded successfully, please save the information',
sysSave: 'Save',
sysSaveOk: 'Info saved successfully!',
sysPrevTip: 'Confirmed to go back to the previous step?',
sysNextNe: 'Confirming that you want to do a network element installation?',
sysNextDone: 'Confirmed to skip the network element installation?',
stepNeInfoTitle: "Service Configuration",
stepNeInfoDesc: "Setting the service terminal corresponding to a network element",
stepNeInfoStepPrev: 'Confirming that you want to exit the network element installation step?',
stepNeInfoStepNext: 'Confirm that you want to proceed to the next step to configure the parameters of the network element?',
stepPara5GTitle: "Configuration Parameter",
stepPara5GDesc: "Setting network element global parameter information",
stepPara5GStepPrev: 'Confirm that you want to abandon the current change and return to the previous step?',
stepPara5GStepNext: 'Confirm that you want to proceed to the next step for the network element service installation?',
stepInstallTitle: "Service Install",
stepInstallDesc: "Installation of network element services to service terminals",
stepInstallStepPrev: 'Confirm that you want to abandon the current change and return to the previous step?',
stepInstallStepNext: 'Confirm that you want to proceed to the next step for network element license authorization?',
stepInstallText: 'Select Install',
stepInstallTip: 'Confirm the installation of the new version of the chosen Net Meta?',
stepInstallModal: 'Network Element For Install',
stepInstallNotNewVer: 'No new version found',
stepInstallDone: 'Installation complete, service initialized',
stepInstallFail: 'Installation fails, check if the service terminal environment is available!',
stepLicenseTitle: "Service License",
stepLicenseDesc: "Obtaining a license activation code for authorization authentication",
stepLicenseReload: 'Select Refresh',
stepLicenseReloadTip: 'Confirm refreshing selected license information?',
stepLicenseDownCode: 'Select Download Activation Code',
stepLicenseDownCodeTip: 'Confirmed to download the selected network element license activation code to a text file?',
stepLicenseStepPrev: 'Confirm that you want to abandon the current change and return to the previous step?',
stepLicenseStepNext: 'Confirmed to end the network element installation step?',
stepLicenseStepNext2: 'Please download the Net Element License Authorization Code file to save it and contact the Net Element vendor to get the authorization license',
stepLicenseEnd: 'End',
doneTitle: "Completing the Configuration",
doneTip: 'Please enter the system and configure it as appropriate.',
doneNETitle: 'Configuration of the network element installation has been performed',
doneNEDesc: 'In case of abnormal network elements, the license can be reinstalled in the system',
doneSkipTitle: 'No network element installation configuration',
doneSkipDesc: 'The system will initialize the network element information by default, which can be modified or installed within the system.',
donePrevTip: 'Confirmed to go back to the previous step?',
doneOkTip: 'Confirm that you have completed the setup and started using it?',
},
},
mmlManage: {
cmdTitle: "Command Navigator",
@@ -1710,14 +1972,14 @@ export default {
cmdOpTip: "Select the item to be operated in the left command navigation!",
cmdNoTip: "{num} no optional command operation",
require: "Mandatory parameter: {num}",
requireUn: "{display} input value is of unknown type",
requireString: "The {display} parameter value is invalid.",
requireInt: "{display} Parameter value not in reasonable range {filter}",
requireIpv4: "{display} not a legitimate IPV4 address",
requireIpv6: "{display} Not a legitimate IPV6 address.",
requireEnum: "{display} is not a reasonable enumeration value.",
requireBool: "{display} is not a reasonable boolean value.",
requireFile: "{display} is not a value that matches the parameter file type.",
requireUn: "[ {display} ] input value is of unknown type",
requireString: "[ {display} ] parameter value is invalid.",
requireInt: "[ {display} ] parameter value not in reasonable range {filter}",
requireIpv4: "[ {display} ] not a legitimate IPV4 address",
requireIpv6: "[ {display} ] not a legitimate IPV6 address.",
requireEnum: "[ {display} ] is not a reasonable enumeration value.",
requireBool: "[ {display} ] is not a reasonable boolean value.",
requireFile: "[ {display} ] is not a value that matches the parameter file type.",
cmdQuickEntry: "Command Quick Entry",
cmdQuickEntryHelp: "Line feed (Shift + Enter), Execute (Enter)",
cmdParamPanel: "Parameter Panel",

View File

@@ -2,6 +2,7 @@ export default {
// 语言
i18n: '中文',
hello: '你好',
welcome: '欢迎使用,核心网管理平台',
// 通用
common: {
@@ -34,7 +35,6 @@ export default {
unableNull:'不能为空',
moreText: '更多',
searchBarText: '搜索栏',
tableStripedText: '表格斑马纹',
reloadText: '刷新',
columnSetText: '列设置',
columnSetTitle: '列展示/排序',
@@ -53,9 +53,15 @@ export default {
fold: '折叠',
},
rowId: '编号',
createTime: '创建时间',
updateTime: '更新时间',
remark: '备注',
description: '说明',
operate: '操作',
operateOk: '操作成功!',
operateErr: '操作失败!',
copyText: "复制",
copyOk: '复制成功!',
units: {
second: '秒',
minute: '分钟',
@@ -130,6 +136,8 @@ export default {
backLogin:'退出并重新登录',
backReload:'正在重启,请稍等...',
backReload2:'当准备就绪的时候,你的浏览器会自动刷新。',
systemReset:'正在重置,请稍等...',
systemReset2:'数据信息正在重置',
},
},
@@ -164,6 +172,9 @@ export default {
codeHit: '验证码',
codeText: '获取验证码',
codeSmsSend: '发送成功,请注意查看短信',
ipPlease: '请输入有效的IP地址',
ipv4Reg: '不是有效IPv4地址',
ipv6Reg: '不是有效IPv6地址',
},
// 布局
@@ -262,10 +273,10 @@ export default {
email: "电子邮箱",
emailPleace: "请输入正确的电子邮箱",
phonenumber: "手机号码",
phonenumberPleace: "请输入正确的电子邮箱",
phonenumberPleace: "请输入正确的手机号码",
nick: "用户昵称",
nickPleace: "请输入用户昵称",
nickTip: "昵称只能包含字母、数字、中文和下划线,且不少于2位",
nickTip: "昵称少于2位",
profileTip: "确认要提交修改用户基本信息吗?",
profileOk: "用户基本信息修改成功!",
know: "我知道了",
@@ -424,15 +435,14 @@ export default {
letUpTime:'激活时间',
createTime:'创建时间',
onlyAble:'只支持上传文件格式 {fileText}',
nullData:'暂无网元列表数据',
nullVersion:'当前网元无可回退版本',
},
license: {
neTypePlease: '选择网元类型',
neType: '网元类型',
fileName: '文件名',
createTime: '上传时间',
comment: '文件说明',
serialNum: '序列号',
createTime: '时间',
comment: '说明',
updateComment: 'License说明',
updateCommentPlease: '请输入License说明',
updateFile: 'License文件',
@@ -471,21 +481,21 @@ export default {
neType: "网元类型",
neTypePleace: "请选择网元类型",
noConfigData: "暂无配置项数据",
updateValue: "{num} 属性值修改成功",
updateValue: "{num} 属性值修改成功",
updateValueErr: "属性值修改失败",
updateItem: "修改 Index 为 {num} 记录成功",
updateItemErr: "记录修改失败",
delItemOk: "删除 Index 为 {num} 记录成功",
addItemOk: "新增 Index 为 {num} 记录成功",
addItemErr: "记录新增失败",
requireUn: "{display} 输入值是未知类型",
requireString: "{display} 参数值不合理",
requireInt: "{display} 参数值不在合理范围 {filter}",
requireIpv4: "{display} 不是合法的IPV4地址",
requireIpv6: "{display} 不是合法的IPV6地址",
requireEnum: "{display} 不是合理的枚举值",
requireBool: "{display} 不是合理的布尔类型的值",
editOkTip: "确认更新该{num}属性值吗?",
requireUn: "{display} 输入值是未知类型",
requireString: "{display} 参数值不合理",
requireInt: "{display} 参数值不在合理范围 {filter}",
requireIpv4: "{display} 不是合法的IPV4地址",
requireIpv6: "{display} 不是合法的IPV6地址",
requireEnum: "{display} 不是合理的枚举值",
requireBool: "{display} 不是合理的布尔类型的值",
editOkTip: "确认更新该{num}属性值吗?",
updateItemTip: "确认更新Index为 【{num}】 的数据项?",
delItemTip: "确认删除Index为 【{num}】 的数据项?",
arrayMore: "展开",
@@ -575,14 +585,42 @@ export default {
},
},
ne: {
neInfo: {
version: "网元版本",
common: {
neType: '网元类型',
neTypePlease: "请选择网元类型",
neTypeTip: '填写创建的网元类型,如:SMF',
neId: '网元标识',
neIdPlease: '请输入网元标识',
neIdTip: '填写网元绑定的唯一标识',
rmUid: '资源唯一标识',
rmUidPlease: '请输入资源唯一标识',
rmUidTip: "用于网元日志、告警、指标等数据上报的标记",
neName: '网元名称',
neNamePlease: '请输入网元名称',
ipAddr: '服务IP',
ipAddrPlease: '请输入网元服务IP地址',
ipAddrTip: "支持IPV4/IPV6,同步变更配置地址",
port: '服务端口',
portTip: "网元服务端口,默认:33030",
serialNum: '序列号',
expiryDate: '许可证到期日期',
normalcy: '正常',
exceptions: '异常',
restart: '重启',
restartTip: '确认要重新启动网元服务吗?',
start: '启动',
startTip: '确认要启动网元服务吗?',
stop: '停止',
stopTip: '确认要停止网元服务吗?',
reload: '重载',
reloadTip: '确认要重载网元配置信息吗?',
oam: 'OAM',
log: '日志',
},
neInfo: {
version: "网元版本",
state: "网元状态",
serviceState: "服务状态",
normalcy: "正常",
exceptions: "异常",
serviceState: "服务状态",
info: '状态信息',
resourceInfo: '资源情况',
sysMem: "系统内存",
@@ -590,8 +628,41 @@ export default {
sysDisk: "系统存储",
neCpu: "网元CPU",
hostConfig: "终端连接配置",
rmUID: "数据资源定位标识符用于日志、告警上报等数据标记",
ipAddr: "支持IPV4/IPV6,同步变更下方配置的配置地址",
pvflag: '网元虚拟化标识',
pnf: '物理网元',
vnf: '虚拟网元',
neAddress: '物理地址(MAC)',
neAddressTip: '记录网元的网卡物理地址(MAC)',
dn: '网络标识',
vendorName: '提供厂商',
province: '服务地域',
addTitle: '新增网元信息',
editTitle: '编辑网元信息',
delTip: '确认删除网元信息数据项吗?',
oam: {
title: 'OAM配置',
sync: '同步到网元',
oamEnable: '服务',
oamPort: '端口',
snmpEnable: '服务',
snmpPort: '端口',
kpiEnable: '上报',
kpiTimer: '上报周期',
kpiTimerPlease: '请输入上报周期(单位秒)',
},
backConf: {
export: '配置导出',
import: '配置导入',
title: '配置文件导入',
importType: '文件来源',
server:'服务器文件',
local:'本地文件',
localUpload:'本地上传',
exportTip:'确认要导出网元配置信息到文件?',
exportMsg:'导出成功,请到【备份管理】进行下载',
filePlease: "请上传文件",
fileNamePlease: '请选择服务器文件',
},
},
neHost: {
hostType: "主机类型",
@@ -610,13 +681,13 @@ export default {
privateKey: "私钥",
privateKeyPlease: "请正确填写私钥字符内容 ~/.ssh/id_rsa",
passPhrase: "私钥密码",
remark: "备注信息",
createTime: "创建时间",
delTip: "确认要删除主机编号为【{num}】的信息吗?",
addTitle: "新增主机连接",
editTitle: "编辑主机连接",
test: "测试连接",
testOk: "测试连接成功",
authRSA: "免密授权",
authRSATip: "是否要配置免密授权?",
},
neHostCmd: {
cmdType: "命令类型",
@@ -625,12 +696,125 @@ export default {
titlePlease: "请正确填写命令名称",
command: "命令",
commandPlease: "请正确输入有效命令字符串",
remark: "备注",
createTime: "创建时间",
delTip: "确认要删除命令编号为【{num}】的信息吗?",
addTitle: "新增主机命令",
editTitle: "编辑主机命令",
},
neSoftware: {
uploadTitle: "上传软件",
upload: "上传",
uploadNotFile: "未上传软件文件",
uploadBatch: "上传软件包",
uploadBatchMax: "可上传多个软件包,最多同时选择{txt}个。",
uploadFileName: "解析文件名称格式如: amf-r2.240x.xx-xxx",
name: "文件名",
path: "软件文件",
pathPlease: "请上传软件包文件",
version: "软件版本",
versionPlease: "请输入软件版本号",
delTip: "确认要删除软件包吗?",
downTip: "确认要下载软件包【{txt}】吗?",
fileCheckType: '未解析出对应的网元类型',
fileCheckVer: '未解析出对应的版本号',
fileTypeNotEq: '不是指定网元类型 {txt}',
fileTypeExists: '已存在相同类型文件',
fileNameExists: '已存在相同名称文件',
fileCheckTypeDep: '未解析出对应指定的依赖包类型',
dependFile: '软件包依赖',
dependFileTip: '文件名解析同上,依据上传顺序安装',
},
neVersion: {
upgrade: "升级到新版本",
upgradeTip: "确认要升级到新版本吗?",
upgradeTipEmpty: "当前没有可用的新版本",
upgradeTipEqual: "当前版本与新版本相同",
rollback: '切换到上一个版本',
rollbackTip: "确认切换到上一个版本吗?",
rollbackTipEmpty: "目前没有可用的上一个版本",
rollbackTipEqual: '当前版本与之前版本相同',
version: "当前版本",
preVersion: "上一个版本",
newVersion: "新版本",
status: "版本状态",
upgradeBatch: "批量更新",
upgradeBatchTip: "对勾选的记录进行新版本升级吗?",
upgradeNotNewVer: '没有发现新版本',
upgradeDone: '更新完成,服务正在重载',
upgradeFail: '更新失败,请检查服务终端环境是否可用!',
upgradeModal: '网元版本更新',
},
neLicense: {
status: "许可证状态",
change: "变更许可证",
reload: "刷新信息",
reloadTip: "确认要刷新许可证信息吗?",
reloadBatch: "批量刷新",
reloadBatchTip: "对勾选的记录进行信息刷新吗?",
updateTtile: "更新许可证",
downCodeTop: "确认要将许可激活码保存到文件吗?",
activationRequestCode: "许可激活码",
licensePath: "许可证文件",
licensePathTip: "请上传许可证文件",
upload: '上传',
uploadBatch: "上传许可证",
uploadChangeFail: "部分网元更新许可证失败,请检查服务终端环境是否可用!",
},
neConfPara5G: {
save: '保存',
reload: '刷新',
title: '保存信息',
sync: '同步到网元',
syncNe: '选择网元',
syncNeDone: '同步到网元终端完成',
saveOk: '保存成功!',
},
neQuickSetup: {
stepPrev: '上一步',
stepPrevTip: '确认要放弃当前变更返回上一步吗?',
stepNext: '下一步',
stepSave: '保存信息',
startTitle: '服务终端环境',
startDesc: '测试连接网元服务',
startStepNext: '确认要下一步进行配置网元信息?',
addr: '终端IP',
kernelName: '系统',
kernelRelease: '内核',
machine: '架构',
prettyName: '平台',
prettyNameTip: '支持 Ubuntu',
nodename: '主机名',
auth: '权限授予',
sudo: '提权',
sudoErr: '确保有权限进行软件包安装,请配置授予当前用户允许无密码 sudo 权限。',
sshLink: '免密直连',
configTitle: "配置网元",
configDesc: "填写网元基础信息",
configAddTitle: '新增提示',
configAddTip: '是否新增为新的网元信息并继续?',
configUpdateTitle: '更新提示',
configUpdateTip: '是否更新到已存在网元信息并继续?',
configStepNext: '确认要下一步进行网元软件安装?',
installTitle: "网元安装",
installDesc: "安装到服务终端",
installConfirmTip: '确认要安装软件包【{name}】吗?',
installStepNext: '确认要下一步进行网元授权吗?',
installSource: '软件来源',
installSourceOption: '已上传',
installSourceUpload: '新上传',
installSelect: '选择记录',
installUpload: '上传文件',
installText: '安装',
licenseTitle: "授权许可",
licenseDesc: "网元服务授权认证",
licenseResultTitle: "是否立即授权激活",
licenseResultTitleOk: '成功激活',
licenseUpload: '许可证',
licenseEnd: '结束',
licenseEndTip: "确认要结束安装吗?",
licenseCheack: '等待网元验证',
licenseTip1: '1. 点击【许可证】可获取许可激活码,随后联系网元厂商进行激活',
licenseTip2: '2. 点击【结束】将结束安装过程',
},
},
neUser: {
auth: {
@@ -644,7 +828,7 @@ export default {
import: '导入',
loadDataConfirm: '确认要重新加载数据吗?',
loadData: '加载数据',
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新。加载结束后可点击重置刷新数据列表,请勿重复点击获取更新!!!',
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,请稍候!!!',
startIMSI: '起始IMSI',
batchAddText: '批量新增',
batchDelText: '批量删除',
@@ -671,7 +855,7 @@ export default {
import: '导入',
loadDataConfirm: '确认要重新加载数据吗?',
loadData: '加载数据',
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新。加载结束后可点击重置刷新数据列表,请勿重复点击获取更新!!!',
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,请稍候!!!',
numAdd: '放号个数',
numDel: '删除个数',
checkDel:'勾选删除',
@@ -709,6 +893,7 @@ export default {
addTitle: '新增策略控制信息',
updateTitle: '{imsi} 策略控制信息',
startIMSI: '起始IMSI',
batchOper: '批量操作',
batchAddText: '批量新增',
batchDelText: '批量删除',
batchUpdateText: '批量更新',
@@ -717,9 +902,19 @@ export default {
imsiTip1: 'MCC=移动国家号码, 由三位数字组成',
imsiTip2: 'MNC=移动网络号,由两位数字组成',
imsiTip3: 'MSIN=移动客户识别码采用等长10位数字构成',
delSure:'确认删除IMSI编号为: {imsi} 的用户吗?',
checkDel:'勾选删除',
delSure:'确认删除IMSI编号为: {imsi} 的数据项吗?',
uploadFileOk: '文件上传成功',
uploadFileErr: '文件上传失败',
pccRuleTip:'PCC策略规则模板(对应参数配置-PCC Rules)',
sessRuleTip:'会话策略规则模板(对应参数配置-Session Rules)',
qosAudioTip:'语音呼叫QoS(对应参数配置-QoS Template的QoS ID)',
qosVideoTip:'视频呼叫QoS(对应参数配置-QoS Template的QoS ID)',
hdrTip:'HTTP头增强(对应参数配置-Header Enrich Template)',
ueTip:'UE策略模板(样例: uep_001)',
sarTip1:'服务区限制',
sarTip2:'(对应参数配置-Service Area Restriction)',
rfsfTip:'无线频率选择优先级',
},
base5G: {
neType: '网元对象',
@@ -755,7 +950,7 @@ export default {
viewTask:'查看任务',
editTask:'修改任务',
addTask:'新增任务',
stopTask:'挂起',
stopTask:'停止',
errorTaskInfo: '获取任务信息失败',
granulOptionPlease:'请选择测量粒度',
letupSure:'确认激活编号为 【{id}】 的任务?',
@@ -815,7 +1010,7 @@ export default {
exportSure:'确认是否导出全部统计数据',
exportEmpty: "导出数据为空",
showChartSelected: "显示全部",
realTimeData: "实时5s数据",
realTimeData: "实时数据",
},
customTarget:{
kpiId:'自定义指标项',
@@ -965,6 +1160,7 @@ export default {
showSet:'显示过滤设置',
exportSure:'确认是否导出全部活动告警信息',
viewIdInfo:'查看{alarmId} 记录信息',
closeModal:'关闭',
},
historyAlarm:{
exportSure:'确认是否导出全部历史告警信息?',
@@ -976,6 +1172,9 @@ export default {
save:'保存设置',
noChange:'告警前转接口设置无变更',
forwardSet:'告警前转接口设置',
},
eventAlarm:{
exportSure:'确认是否导出全部事件告警信息?',
}
},
logManage:{
@@ -1004,8 +1203,9 @@ export default {
alarmId:'告警唯一标识',
alarmSeq:'告警流水号',
alarmObj:'告警前转对象',
alarmInter:'告警前转接口',
alarmTitle:'告警标题',
alarmInfo:'告警内容',
alarmInfo:'操作结果',
eventTime:'告警产生时间',
logTime:'记录时间'
},
@@ -1400,7 +1600,7 @@ export default {
status: '用户状态',
userNameTip:'账号不能以数字开头,可包含大写小写字母,数字,且不少于5位',
passwdTip:'密码至少包含大小写字母、数字、特殊符号,且不少于6位',
nickNameTip:'昵称只能包含字母、数字、中文和下划线,且不少于2位',
nickNameTip:'昵称不少于2位',
emailTip:'请输入正确的邮箱地址',
phoneTip:'请输入正确的手机号码',
resetPwd:'重置密码',
@@ -1423,7 +1623,7 @@ export default {
userWork:'用户岗位',
userWorkPlease: '请选择用户岗位',
userTip:'用户说明',
loginPwd:'登密码',
loginPwd:'登密码',
updateSure:'是否更新已经存在的数据',
downloadObj:'下载模板',
importTitle:'用户导入',
@@ -1509,6 +1709,9 @@ export default {
i18nOpen: "显示切换",
i18nDefault: "默认语言",
i18nInstruction: '是否显示国际化切换,设置系统默认语言',
reset: "系统重置",
resetInstruction: "系统重置将会清除当前系统内所有数据,请谨慎操作!!!",
resetTipContent: '确认要清除当前系统内所有数据并坚持继续吗?',
},
role:{
allScopeOptions:'全部数据权限',
@@ -1533,10 +1736,10 @@ export default {
distributeUser:'分配用户',
roleMark:'角色说明',
menu:'菜单权限',
roleKeyTip:"权限标识示例:dba 控制器中使用权限标识,如: @PreAuthorize({ hasRoles: ['dba'] })",
roleKeyTip:"权限标识用于控制页面控件或路由接口",
openSwitch:'展开/折叠',
selAllSwitch:'全选/全不选',
relationSwitch:'父子联动',
relationSwitch:'节点联动',
normal:'正常',
stop:'暂停',
preScope:'权限范围',
@@ -1703,6 +1906,65 @@ export default {
exportOk: "已完成导出",
typeDataErr: "获取字典类型信息失败",
},
quickStart: {
start: '开始设置',
skip: '跳过',
finish: '完成设置',
stepPrev: '上一步',
stepNext: '下一步',
exit: '退出',
save: '保存信息',
sysTitle: '系统配置',
sysAdmin: '管理员',
sysInfo: '系统信息',
sysLogo: '系统LOGO',
sysLogoTip: '将图片展示到系统LOGO区域查看效果请使用透明背景尺寸比例适应区域大小',
sysName: '系统名称',
sysNameTip: '系统名称限制20个字符长度',
sysUploadLogo: '确认要上传系统LOGO文件吗?',
sysUploadOk: '文件上传成功,请进行保存信息',
sysSave: '保存',
sysSaveOk: '信息保存成功!',
sysPrevTip: '确认要返回上一个步骤吗?',
sysNextNe: '确认要进行网元安装吗?',
sysNextDone: '确认要跳过网元安装吗?',
stepNeInfoTitle: "网元服务配置",
stepNeInfoDesc: "设置网元对应的服务终端",
stepNeInfoStepPrev: '确认要退出网元安装步骤吗?',
stepNeInfoStepNext: '确认要下一步进行网元配置参数?',
stepPara5GTitle: "网元配置参数",
stepPara5GDesc: "设置网元全局参数信息",
stepPara5GStepPrev: '确认要放弃当前变更返回上一步吗?',
stepPara5GStepNext: '确认要下一步进行网元服务安装吗?',
stepInstallTitle: "网元服务安装",
stepInstallDesc: "将网元服务安装到服务终端",
stepInstallStepPrev: '确认要放弃当前变更返回上一步吗?',
stepInstallStepNext: '确认要下一步进行网元许可授权吗?',
stepInstallText: '选择安装',
stepInstallTip: '确认安装选择的网元新版本吗?',
stepInstallModal: '网元进行安装',
stepInstallNotNewVer: '没有发现新版本',
stepInstallDone: '安装完成,服务进入初始化',
stepInstallFail: '安装失败,请检查服务终端环境是否可用!',
stepLicenseTitle: "网元许可授权",
stepLicenseDesc: "获取网元许可激活码进行授权认证",
stepLicenseReload: '选择刷新许可证',
stepLicenseReloadTip: '确认刷新选择的许可证信息吗?',
stepLicenseDownCode: '选择下载网元许可激活码',
stepLicenseDownCodeTip: '确认下载选择的网元许可激活码到文本文件吗?',
stepLicenseStepPrev: '确认要放弃当前变更返回上一步吗?',
stepLicenseStepNext: '确认要结束网元安装步骤吗?',
stepLicenseStepNext2: '请下载网元许可授权码文件保存,并联系网元厂商获取授权许可证',
stepLicenseEnd: '结束',
doneTitle: "完成配置",
doneTip: '请进入系统后,根据情况进行更多相关配置',
doneNETitle: '已经进行网元安装配置',
doneNEDesc: '如有异常网元,可在系统内重新安装授权许可',
doneSkipTitle: '未进行网元安装配置',
doneSkipDesc: '系统将会默认初始网元信息,可在系统内自行修改或进行安装',
donePrevTip: '确认要返回上一个步骤吗?',
doneOkTip: '确认完成设置并开始使用吗?',
},
},
mmlManage: {
cmdTitle: "命令导航",
@@ -1710,14 +1972,14 @@ export default {
cmdOpTip: "左侧命令导航中选择要操作项!",
cmdNoTip: "{num} 无可选命令操作",
require: "必填参数:{num}",
requireUn: "{display} 输入值是未知类型",
requireString: "{display} 参数值不合理",
requireInt: "{display} 参数值不在合理范围 {filter}",
requireIpv4: "{display} 不是合法的IPV4地址",
requireIpv6: "{display} 不是合法的IPV6地址",
requireEnum: "{display} 不是合理的枚举值",
requireBool: "{display} 不是合理的布尔类型的值",
requireFile: "{display} 不是符合参数文件类型的值",
requireUn: "{display} 输入值是未知类型",
requireString: "{display} 参数值不合理",
requireInt: "{display} 参数值不在合理范围 {filter}",
requireIpv4: "{display} 不是合法的IPV4地址",
requireIpv6: "{display} 不是合法的IPV6地址",
requireEnum: "{display} 不是合理的枚举值",
requireBool: "{display} 不是合理的布尔类型的值",
requireFile: "{display} 不是符合参数文件类型的值",
cmdQuickEntry: "命令快速输入",
cmdQuickEntryHelp: "换行(Shift + Enter) 执行发送(Enter)",
cmdParamPanel: "参数面板",

View File

@@ -77,7 +77,7 @@ function fnChangeLocale(e: any) {
</template>
</a-button>
<a-tooltip placement="bottom">
<a-tooltip placement="bottom" v-if="false">
<template #title>{{ t('loayouts.rightContent.lock') }}</template>
<a-button type="text" @click="fnClickLock">
<template #icon>
@@ -107,7 +107,7 @@ function fnChangeLocale(e: any) {
<a-dropdown
placement="bottom"
:trigger="['click', 'hover']"
trigger="click"
v-if="appStore.i18nOpen && hasPermissions(['system:setting:i18n'])"
>
<a-button size="small" type="default">
@@ -123,7 +123,7 @@ function fnChangeLocale(e: any) {
</template>
</a-dropdown>
<a-dropdown placement="bottomRight" :trigger="['click', 'hover']">
<a-dropdown placement="bottomRight" trigger="click">
<div class="user">
<a-avatar
shape="circle"

View File

@@ -95,7 +95,7 @@ function fnTabClose(path: string) {
/**
* 国际化翻译转换
*/
function fnLocale(title: string) {
function fnLocale(title: string) {
if (title.indexOf('router.') !== -1) {
title = t(title);
}
@@ -150,7 +150,7 @@ watch(router.currentRoute, v => tabsStore.tabOpen(v), { immediate: true });
</a-tooltip>
<a-tooltip placement="topRight">
<template #title>{{ t('loayouts.tabs.more') }}</template>
<a-dropdown :trigger="['click', 'hover']" placement="bottomRight">
<a-dropdown trigger="click" placement="bottomRight">
<a-button type="ghost" shape="circle" size="small">
<template #icon><DownOutlined /></template>
</a-button>

View File

@@ -87,6 +87,12 @@ const constantRoutes: RouteRecordRaw[] = [
meta: { title: 'router.helpDoc' },
component: () => import('@/views/tool/help/index.vue'),
},
{
path: '/quick-start',
name: 'QuickStart',
meta: { title: 'router.quickStart' },
component: () => import('@/views/system/quick-start/index.vue'),
},
{
path: '/redirect',
name: 'Redirect',
@@ -136,17 +142,30 @@ router.afterEach((to, from, failure) => {
});
/**无Token可访问页面地址白名单 */
const WHITE_LIST: string[] = ['/login', '/auth-redirect', '/help', '/register'];
const WHITE_LIST: string[] = [
'/login',
'/auth-redirect',
'/help',
'/register',
'/quick-start',
];
/**全局路由-前置守卫 */
router.beforeEach((to, from, next) => {
router.beforeEach(async (to, from, next) => {
NProgress.start();
const token = getToken();
// 获取系统配置信息
const appStore = useAppStore();
if (!appStore.loginBackground) {
appStore.fnSysConf();
await appStore.fnSysConf();
}
// 需要系统引导跳转
if (appStore.bootloader && to.path !== '/quick-start') {
next({ name: 'QuickStart' });
}
const token = getToken();
// 没有token
if (!token) {
@@ -168,31 +187,27 @@ router.beforeEach((to, from, next) => {
// 判断当前用户是否有角色信息
const user = useUserStore();
if (user.roles && user.roles.length === 0) {
// 获取用户信息
user
.fnGetInfo()
.then(() => {
return useRouterStore().generateRoutes();
})
.then(accessRoutes => {
// 根据后台配置生成可访问的路由表
if (accessRoutes && accessRoutes.length !== 0) {
for (const route of accessRoutes) {
// 动态添加可访问路由表http开头会异常
if (!validHttp(route.path)) {
router.addRoute(route);
}
try {
// 获取用户信息
await user.fnGetInfo();
// 获取路由信息
const accessRoutes = await useRouterStore().generateRoutes();
// 根据后台配置生成可访问的路由表
if (accessRoutes && accessRoutes.length !== 0) {
for (const route of accessRoutes) {
// 动态添加可访问路由表http开头会异常
if (!validHttp(route.path)) {
router.addRoute(route);
}
}
// 刷新替换原先路由确保addRoutes已完成
next({ ...to, replace: true });
})
.catch(e => {
console.error(`[${to.path}]: ${e.message}`);
user.fnLogOut().finally(() => {
next({ name: 'Login' });
});
});
}
// 刷新替换原先路由确保addRoutes已完成
next({ ...to, replace: true });
} catch (error: any) {
console.error(`[${to.path}]: ${error.message}`);
await user.fnLogOut();
next({ name: 'Login' });
}
} else {
next();
}

View File

@@ -1,6 +1,7 @@
import { getSysConf } from '@/api';
import { CACHE_LOCAL_I18N } from '@/constants/cache-keys-constants';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { removeToken } from '@/plugins/auth-token';
import { parseUrlPath } from '@/plugins/file-static-url';
import { localGet, localSet } from '@/utils/cache-local-utils';
import { defineStore } from 'pinia';
@@ -17,6 +18,8 @@ type AppStore = {
/**服务版本 */
version: string;
buildTime: string;
/**系统引导使用 */
bootloader: boolean;
// 序列号
serialNum: string;
/**应用版权声明 */
@@ -48,6 +51,7 @@ const useAppStore = defineStore('app', {
version: `-`,
buildTime: `-`,
bootloader: false,
serialNum: `-`,
copyright: `Copyright ©2023 For ${import.meta.env.VITE_APP_NAME}`,
logoType: 'icon',
@@ -57,7 +61,7 @@ const useAppStore = defineStore('app', {
loginBackground: '',
helpDoc: '',
officialUrl: '',
i18nOpen: true,
i18nOpen: false,
i18nDefault: 'en_US',
}),
getters: {},
@@ -70,16 +74,17 @@ const useAppStore = defineStore('app', {
document.title = this.appName;
}
},
/**设置版权声明 */
setCopyright(text: string) {
this.copyright = text;
},
// 获取系统配置信息
async fnSysConf() {
const res = await getSysConf();
if (res.code === RESULT_CODE_SUCCESS && res.data) {
this.version = res.data.version;
this.buildTime = res.data.buildTime;
this.bootloader = res.data.bootloader === 'true';
// 引导时
if (this.bootloader) {
removeToken();
}
this.serialNum = res.data.serialNum;
this.appName = res.data.title;
this.copyright = res.data.copyright;

View File

@@ -7,14 +7,14 @@ import { defineStore } from 'pinia';
/**锁屏信息类型 */
type Locked = {
/**锁屏类型 */
type: 'none' | 'lock' | 'reload' | string;
type: 'none' | 'lock' | 'reload' | 'reset';
/**lock 超时锁屏时间,秒*/
lockTimeout: number;
};
const useLockedStore = defineStore('locked', {
state: (): Locked => ({
type: localGet(CACHE_LOCAL_LOCK) || 'none',
type: (localGet(CACHE_LOCAL_LOCK) || 'none') as Locked['type'],
lockTimeout: 0,
}),
getters: {},
@@ -24,8 +24,16 @@ const useLockedStore = defineStore('locked', {
try {
const res = await getSysConf();
if (res.code === RESULT_CODE_SUCCESS && res.data) {
this.fnLock('none');
window.location.reload();
// 延迟5秒
setTimeout(() => {
this.fnLock('none');
window.location.reload();
}, 2_000);
} else {
// 延迟5秒
setTimeout(() => {
this.relaodWait();
}, 5_000);
}
} catch (error) {
// 延迟5秒
@@ -35,7 +43,7 @@ const useLockedStore = defineStore('locked', {
}
},
// 设置锁定
async fnLock(type: 'none' | 'lock' | 'reload' | string) {
async fnLock(type: Locked['type']) {
this.type = type;
localSet(CACHE_LOCAL_LOCK, type);
if (type === 'reload') {

View File

@@ -9,8 +9,6 @@ import { parseUrlPath } from '@/plugins/file-static-url';
/**用户信息类型 */
type UserInfo = {
/**授权凭证 */
token: string;
/**登录账号 */
userName: string;
/**用户角色 字符串数组 */
@@ -27,13 +25,12 @@ type UserInfo = {
email: string;
/**用户性别 */
sex: string | undefined;
/**个人化设置 */
/**其他信息 */
profile: Record<string, any>;
};
const useUserStore = defineStore('user', {
state: (): UserInfo => ({
token: getToken(),
userName: '',
roles: [],
permissions: [],
@@ -104,7 +101,6 @@ const useUserStore = defineStore('user', {
if (res.code === RESULT_CODE_SUCCESS && res.data) {
const token = res.data[TOKEN_RESPONSE_FIELD];
setToken(token);
this.token = token;
}
return res;
},
@@ -154,7 +150,6 @@ const useUserStore = defineStore('user', {
} catch (error) {
throw error;
} finally {
this.token = '';
this.roles = [];
this.permissions = [];
removeToken();

View File

@@ -34,7 +34,7 @@ export const regExpPasswd =
/**
* 有效手机号格式
*/
export const regExpMobile = /^1[3|4|5|6|7|8|9][0-9]\d{8}$/;
export const regExpMobile = /^.{3,}$/; // /^1[3|4|5|6|7|8|9][0-9]\d{8}$/;
/**
* 有效邮箱格式
@@ -47,7 +47,7 @@ export const regExpEmail =
*
* 用户昵称只能包含字母、数字、中文和下划线且不少于2位
*/
export const regExpNick = /^[\w\u4e00-\u9fa5-]{2,}$/;
export const regExpNick = /^.{2,}$/; // /^[\w\u4e00-\u9fa5-]{2,}$/;
/**
* 是否为http(s)://开头
@@ -84,13 +84,14 @@ export function validURL(str: string) {
// http:// is appended so url.Parse will succeed, strTemp used so it does not impact rxURL.MatchString
strTemp = 'http://' + str;
}
debugger;
let u = { host: '', pathname: '' };
try {
new URL(strTemp);
} catch (error) {
return false;
}
const u = new URL(strTemp);
if (u.host.startsWith('.')) {
return false;
}

View File

@@ -166,12 +166,12 @@ onMounted(() => {
},
]"
>
<a-input
<IntlTelInput
v-model:value="stateForm.form.phonenumber"
allow-clear
:maxlength="11"
:maxlength="16"
:placeholder="t('views.account.settings.phonenumberPleace')"
></a-input>
></IntlTelInput>
</a-form-item>
<a-form-item

View File

@@ -227,7 +227,11 @@ function fnGetList(pageNum?: number) {
}
tablePagination.total = res.total;
tableState.data = res.rows;
if (tablePagination.total <=(queryParams.pageNum - 1) * tablePagination.pageSize &&queryParams.pageNum !== 1) {
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,157 @@
import useI18n from '@/hooks/useI18n';
import { regExpIPv4, regExpIPv6, validURL } from '@/utils/regular-utils';
export default function useOptions() {
const { t } = useI18n();
/**规则校验 */
function ruleVerification(row: Record<string, any>): (string | boolean)[] {
let result = [true, ''];
const type = row.type;
const value = row.value;
const filter = row.filter;
const display = row.display;
// 子嵌套的不检查
if (row.array) {
return result;
}
// 可选的同时没有值不检查
if (row.optional === 'true' && !value) {
return result;
}
switch (type) {
case 'int':
if (filter && filter.indexOf('~') !== -1) {
const filterArr = filter.split('~');
const minInt = parseInt(filterArr[0]);
const maxInt = parseInt(filterArr[1]);
const valueInt = parseInt(value);
if (valueInt < minInt || valueInt > maxInt) {
return [
false,
t('views.configManage.configParamForm.requireInt', {
display,
filter,
}),
];
}
}
break;
case 'ipv4':
if (!regExpIPv4.test(value)) {
return [
false,
t('views.configManage.configParamForm.requireIpv4', { display }),
];
}
break;
case 'ipv6':
if (!regExpIPv6.test(value)) {
return [
false,
t('views.configManage.configParamForm.requireIpv6', { display }),
];
}
break;
case 'enum':
if (filter && filter.indexOf('{') === 1) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
if (!Object.keys(filterJson).includes(`${value}`)) {
return [
false,
t('views.configManage.configParamForm.requireEnum', { display }),
];
}
}
break;
case 'bool':
if (filter && filter.indexOf('{') === 1) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
if (!Object.values(filterJson).includes(`${value}`)) {
return [
false,
t('views.configManage.configParamForm.requireBool', { display }),
];
}
}
break;
case 'string':
// 字符串长度判断
if (filter && filter.indexOf('~') !== -1) {
try {
const filterArr = filter.split('~');
let rule = new RegExp(
'^\\S{' + filterArr[0] + ',' + filterArr[1] + '}$'
);
if (!rule.test(value)) {
return [
false,
t('views.configManage.configParamForm.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
// 字符串http判断
if (value.startsWith('http')) {
try {
if (!validURL(value)) {
return [
false,
t('views.configManage.configParamForm.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
break;
case 'regex':
if (filter) {
try {
let regex = new RegExp(filter);
if (!regex.test(value)) {
return [
false,
t('views.configManage.configParamForm.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
break;
default:
return [
false,
t('views.configManage.configParamForm.requireUn', { display }),
];
}
return result;
}
return { ruleVerification };
}

View File

@@ -0,0 +1,22 @@
import { getParamConfigInfo } from '@/api/configManage/configParam';
import { ref } from 'vue';
export default function useSMFOptions() {
/**upfId可选择 */
const optionsUPFIds = ref<{ value: string; label: string }[]>([]);
/**初始加载upfId */
function initUPFIds() {
getParamConfigInfo('smf', 'upfConfig', '001').then(res => {
optionsUPFIds.value = [];
for (const s of res.data) {
optionsUPFIds.value.push({
value: s.id,
label: s.id,
});
}
});
}
return { initUPFIds, optionsUPFIds };
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { reactive, ref, onMounted, watch, toRaw, nextTick } from 'vue';
import { reactive, ref, onMounted, toRaw, nextTick, watch } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { Modal, message } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
@@ -13,11 +13,14 @@ import {
addParamConfigInfo,
} from '@/api/configManage/configParam';
import useNeInfoStore from '@/store/modules/neinfo';
import { regExpIPv4, regExpIPv6, validURL } from '@/utils/regular-utils';
import useOptions from './hooks/useOptions';
import useSMFOptions from './hooks/useSMFOptions';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { DataNode } from 'ant-design-vue/lib/tree';
const neInfoStore = useNeInfoStore();
const { t } = useI18n();
const { ruleVerification } = useOptions();
const { initUPFIds, optionsUPFIds } = useSMFOptions();
/**网元参数 */
let neCascaderOptions = ref<Record<string, any>[]>([]);
@@ -25,6 +28,14 @@ let neCascaderOptions = ref<Record<string, any>[]>([]);
/**网元类型选择 type,id */
let neTypeSelect = ref<string[]>(['', '']);
/**左侧导航是否可收起 */
let collapsible = ref<boolean>(true);
/**改变收起状态 */
function changeCollapsible() {
collapsible.value = !collapsible.value;
}
/**对象信息状态类型 */
type TreeStateType = {
/**网元 loading */
@@ -43,7 +54,7 @@ let treeState: TreeStateType = reactive({
selectNode: {
topDisplay: '' as string,
topTag: '' as string,
method: '' as string,
method: [] as string[],
//
title: '' as string,
key: '' as string,
@@ -57,7 +68,11 @@ function fnSelectConfigNode(_: any, info: any) {
const { title, key, method } = info.node;
treeState.selectNode.topDisplay = title;
treeState.selectNode.topTag = key;
treeState.selectNode.method = method;
if (method) {
treeState.selectNode.method = method.split(',');
} else {
treeState.selectNode.method = ['post', 'put', 'delete'];
}
treeState.selectNode.title = title;
treeState.selectNode.key = key;
fnActiveConfigNode(key);
@@ -329,14 +344,6 @@ let arrayState: ArrayStateType = reactive({
dataRule: {},
});
/**监听表格字段列排序变化关闭展开 */
watch(
() => arrayState.columnsDnd,
() => {
arrayEditClose();
}
);
/**多列表编辑 */
function arrayEdit(rowIndex: Record<string, any>) {
const item = arrayState.data.find((s: any) => s.key === rowIndex.value);
@@ -941,155 +948,6 @@ function arrayAddInit(data: any[], dataRule: any) {
return ruleFrom;
}
/**规则校验 */
function ruleVerification(row: Record<string, any>): (string | boolean)[] {
let result = [true, ''];
const type = row.type;
const value = row.value;
const filter = row.filter;
const display = row.display;
// 子嵌套的不检查
if (row.array) {
return result;
}
// 可选的同时没有值不检查
if (row.optional === 'true' && !value) {
return result;
}
switch (type) {
case 'int':
if (filter && filter.indexOf('~') !== -1) {
const filterArr = filter.split('~');
const minInt = parseInt(filterArr[0]);
const maxInt = parseInt(filterArr[1]);
const valueInt = parseInt(value);
if (valueInt < minInt || valueInt > maxInt) {
return [
false,
t('views.configManage.configParamForm.requireInt', {
display,
filter,
}),
];
}
}
break;
case 'ipv4':
if (!regExpIPv4.test(value)) {
return [
false,
t('views.configManage.configParamForm.requireIpv4', { display }),
];
}
break;
case 'ipv6':
if (!regExpIPv6.test(value)) {
return [
false,
t('views.configManage.configParamForm.requireIpv6', { display }),
];
}
break;
case 'enum':
if (filter && filter.indexOf('{') === 1) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
if (!Object.keys(filterJson).includes(`${value}`)) {
return [
false,
t('views.configManage.configParamForm.requireEnum', { display }),
];
}
}
break;
case 'bool':
if (filter && filter.indexOf('{') === 1) {
let filterJson: Record<string, any> = {};
try {
filterJson = JSON.parse(filter); //string---json
} catch (error) {
console.error(error);
}
if (!Object.values(filterJson).includes(`${value}`)) {
return [
false,
t('views.configManage.configParamForm.requireBool', { display }),
];
}
}
break;
case 'string':
// 字符串长度判断
if (filter && filter.indexOf('~') !== -1) {
try {
const filterArr = filter.split('~');
let rule = new RegExp(
'^\\S{' + filterArr[0] + ',' + filterArr[1] + '}$'
);
if (!rule.test(value)) {
return [
false,
t('views.configManage.configParamForm.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
// 字符串http判断
if (value.startsWith('http')) {
try {
if (!validURL(value)) {
return [
false,
t('views.configManage.configParamForm.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
break;
case 'regex':
if (filter) {
try {
let regex = new RegExp(filter);
if (!regex.test(value)) {
return [
false,
t('views.configManage.configParamForm.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
break;
default:
return [
false,
t('views.configManage.configParamForm.requireUn', { display }),
];
}
return result;
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**添加框是否显示 */
@@ -1151,6 +1009,25 @@ function fnModalCancel() {
modalState.data = [];
}
// 监听表格字段列排序变化关闭展开
watch(
() => arrayState.columnsDnd,
() => {
arrayEditClose();
}
);
// 监听新增编辑弹窗
watch(
() => modalState.visible,
val => {
// SMF需要选择配置的UPF id
if (val && neTypeSelect.value[0] === 'SMF') {
initUPFIds();
}
}
);
onMounted(() => {
// 获取网元网元列表
neInfoStore.fnNelist().then(res => {
@@ -1193,7 +1070,13 @@ onMounted(() => {
<template>
<PageContainer>
<a-row :gutter="16">
<a-col :lg="6" :md="6" :xs="24" style="margin-bottom: 24px">
<a-col
:lg="6"
:md="6"
:xs="24"
style="margin-bottom: 24px"
v-show="collapsible"
>
<!-- 网元类型 -->
<a-card
size="small"
@@ -1221,7 +1104,7 @@ onMounted(() => {
</a-form>
</a-card>
</a-col>
<a-col :lg="18" :md="18" :xs="24">
<a-col :lg="collapsible ? 18 : 24" :md="collapsible ? 18 : 24" :xs="24">
<a-card
size="small"
:bordered="false"
@@ -1229,6 +1112,13 @@ onMounted(() => {
:loading="treeState.selectLoading"
>
<template #title>
<a-button type="text" @click.prevent="changeCollapsible()">
<template #icon>
<MenuFoldOutlined v-show="collapsible" />
<MenuUnfoldOutlined v-show="!collapsible" />
</template>
</a-button>
<a-typography-text strong v-if="treeState.selectNode.topDisplay">
{{ treeState.selectNode.topDisplay }}
</a-typography-text>
@@ -1349,7 +1239,9 @@ onMounted(() => {
<EditOutlined
class="editable-cell__icon"
@click="listEdit(record)"
v-if="!['read-only', 'ro'].includes(record.access)"
v-if="
!['read-only', 'read', 'ro'].includes(record.access)
"
/>
</div>
</div>
@@ -1363,7 +1255,7 @@ onMounted(() => {
<a-table
class="table"
row-key="index"
:columns="treeState.selectNode.method === 'get' ? arrayState.columnsDnd.filter((s:any)=>s.key !== 'index') : arrayState.columnsDnd"
:columns="treeState.selectNode.method.includes('get') ? arrayState.columnsDnd.filter((s:any)=>s.key !== 'index') : arrayState.columnsDnd"
:data-source="arrayState.columnsData"
:size="arrayState.size"
:pagination="tablePagination"
@@ -1380,7 +1272,7 @@ onMounted(() => {
type="primary"
@click.prevent="arrayAdd()"
size="small"
v-if="treeState.selectNode.method !== 'get'"
v-if="treeState.selectNode.method.includes('post')"
>
<template #icon> <PlusOutlined /> </template>
{{ t('common.addText') }}
@@ -1388,7 +1280,7 @@ onMounted(() => {
<TableColumnsDnd
type="ghost"
:cache-id="treeState.selectNode.key"
:columns="treeState.selectNode.method === 'get' ? [...arrayState.columns.filter((s:any)=>s.key !== 'index')] : arrayState.columns"
:columns="treeState.selectNode.method.includes('get') ? [...arrayState.columns.filter((s:any)=>s.key !== 'index')] : arrayState.columns"
v-model:columns-dnd="arrayState.columnsDnd"
></TableColumnsDnd>
</a-space>
@@ -1398,13 +1290,17 @@ onMounted(() => {
<template #bodyCell="{ column, text, record }">
<template v-if="column?.key === 'index'">
<a-space :size="16" align="center">
<a-tooltip>
<a-tooltip
v-if="treeState.selectNode.method.includes('put')"
>
<template #title>{{ t('common.editText') }}</template>
<a-button type="link" @click.prevent="arrayEdit(text)">
<template #icon><FormOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<a-tooltip
v-if="treeState.selectNode.method.includes('delete')"
>
<template #title>{{ t('common.deleteText') }}</template>
<a-button type="link" @click.prevent="arrayDelete(text)">
<template #icon><DeleteOutlined /></template>
@@ -1509,9 +1405,9 @@ onMounted(() => {
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{
t('common.deleteText')
}}</template>
<template #title>
{{ t('common.deleteText') }}
</template>
<a-button
type="link"
@click.prevent="arrayChildDelete(text)"
@@ -1598,10 +1494,24 @@ onMounted(() => {
modalState.from[item.name] !== undefined
"
>
<a-input-number
v-if="item['type'] === 'int'"
<!-- 特殊SMF-upfid选择 -->
<a-select
v-if="
neTypeSelect[0] === 'SMF' &&
modalState.from[item.name]['name'] === 'upfId'
"
v-model:value="modalState.from[item.name]['value']"
:disabled="['read-only', 'ro'].includes(item.access)"
:options="optionsUPFIds"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
:allow-clear="true"
style="width: 100%"
>
</a-select>
<!-- 常规 -->
<a-input-number
v-else-if="item['type'] === 'int'"
v-model:value="modalState.from[item.name]['value']"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
style="width: 100%"
></a-input-number>
<a-switch
@@ -1609,12 +1519,12 @@ onMounted(() => {
v-model:checked="modalState.from[item.name]['value']"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
:disabled="['read-only', 'ro'].includes(item.access)"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
></a-switch>
<a-select
v-else-if="item['type'] === 'enum'"
v-model:value="modalState.from[item.name]['value']"
:disabled="['read-only', 'ro'].includes(item.access)"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
:allow-clear="true"
style="width: 100%"
>
@@ -1629,7 +1539,7 @@ onMounted(() => {
<a-input
v-else
v-model:value="modalState.from[item.name]['value']"
:disabled="['read-only', 'ro'].includes(item.access)"
:disabled="['read-only', 'read', 'ro'].includes(item.access)"
></a-input>
</div>
<div v-else>

View File

@@ -11,6 +11,7 @@ import useI18n from '@/hooks/useI18n';
import useNeInfoStore from '@/store/modules/neinfo';
import { FileType } from 'ant-design-vue/lib/upload/interface';
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import { parseDateToStr } from '@/utils/date-utils';
const neInfoStore = useNeInfoStore();
const { t } = useI18n();
@@ -74,21 +75,25 @@ let tableColumns: ColumnsType = [
width: 2,
},
{
title: t('views.configManage.license.fileName'),
dataIndex: 'fileName',
title: t('views.configManage.license.serialNum'),
dataIndex: 'serialNum',
align: 'center',
width: 3,
},
{
title: t('views.configManage.license.comment'),
dataIndex: 'comment',
dataIndex: 'remark',
align: 'center',
width: 5,
},
{
title: t('views.configManage.license.createTime'),
dataIndex: 'createdAt',
dataIndex: 'createTime',
align: 'center',
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value);
},
width: 2,
},
];
@@ -141,7 +146,11 @@ function fnGetList(pageNum?: number) {
}
tablePagination.total = res.total;
tableState.data = res.rows;
if (tablePagination.total <=(queryParams.pageNum - 1) * tablePagination.pageSize &&queryParams.pageNum !== 1) {
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
@@ -301,7 +310,7 @@ onMounted(() => {
fnGetList();
} else {
message.warning({
content: t('views.configManage.softwareManage.nullData'),
content: t('common.noData'),
duration: 2,
});
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw } from 'vue';
import { reactive, onMounted, toRaw, ref } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message, Modal, Form } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
@@ -22,8 +22,12 @@ import { updateNeConfigReload } from '@/api/configManage/configParam';
import useI18n from '@/hooks/useI18n';
import { FileType } from 'ant-design-vue/lib/upload/interface';
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import TableColumnsDnd from '@/components/TableColumnsDnd/index.vue';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
import useNeInfoStore from '@/store/modules/neinfo';
import useLockedStore from '@/store/modules/locked';
const lockedStore = useLockedStore();
const { t } = useI18n();
/**表格所需option */
@@ -86,71 +90,74 @@ let tableColumns: ColumnsType = [
title: t('views.configManage.neManage.neType'),
dataIndex: 'neType',
align: 'center',
width: 3,
width: 100,
},
{
title: t('views.configManage.neManage.neId'),
dataIndex: 'neId',
align: 'center',
width: 3,
},
{
title: t('views.configManage.neManage.uid'),
dataIndex: 'rmUid',
align: 'center',
width: 5,
},
{
title: t('views.configManage.neManage.neName'),
dataIndex: 'neName',
align: 'center',
width: 5,
width: 100,
},
{
title: t('views.configManage.neManage.ip'),
dataIndex: 'ip',
align: 'center',
width: 5,
width: 150,
},
{
title: t('views.configManage.neManage.port'),
dataIndex: 'port',
align: 'center',
width: 3,
width: 100,
},
{
title: t('views.configManage.neManage.neName'),
dataIndex: 'neName',
align: 'center',
width: 150,
},
{
title: t('views.configManage.neManage.uid'),
dataIndex: 'rmUid',
align: 'center',
width: 200,
},
{
title: t('views.configManage.neManage.pvflag'),
dataIndex: 'pvFlag',
align: 'center',
width: 5,
width: 100,
},
{
title: t('views.configManage.neManage.province'),
dataIndex: 'province',
align: 'center',
width: 5,
width: 100,
},
{
title: t('views.configManage.neManage.vendorName'),
dataIndex: 'vendorName',
align: 'center',
width: 5,
width: 150,
},
{
title: t('views.configManage.neManage.dn'),
dataIndex: 'dn',
align: 'center',
width: 5,
width: 200,
},
{
title: t('common.operate'),
key: 'id',
align: 'center',
fixed: 'right',
width: 5,
width: 150,
},
];
/**表格字段列排序 */
let tableColumnsDnd = ref<ColumnsType>([]);
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
@@ -215,11 +222,11 @@ let modalState: ModalStateType = reactive({
neAddress: '',
neId: '',
neName: '',
neType: 'OMC',
port: '3030',
neType: 'AMF',
port: '33030',
province: '',
pvFlag: 'PNF',
rmUid: '4400HX1OMC001',
rmUid: '4400HX1AMF001',
vendorName: '',
sync: true,
},
@@ -541,6 +548,11 @@ function fnRecordRestart(row: Record<string, any>) {
restartNf(row)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// OMC自升级
if (row.neType.toLowerCase() === 'omc') {
lockedStore.fnLock('reload');
return;
}
message.success({
content: t('common.msgSuccess', {
msg: t('views.configManage.neManage.restart'),
@@ -785,11 +797,12 @@ onMounted(() => {
:label="t('views.configManage.neManage.neType')"
name="neType "
>
<a-input
<a-auto-complete
v-model:value="queryParams.neType"
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
allow-clear
:placeholder="t('views.configManage.neManage.neTypePlease')"
></a-input>
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
@@ -839,6 +852,11 @@ onMounted(() => {
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<TableColumnsDnd
cache-id="neManageData"
:columns="tableColumns"
v-model:columns-dnd="tableColumnsDnd"
></TableColumnsDnd>
<a-tooltip>
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click" placement="bottomRight">
@@ -870,15 +888,15 @@ onMounted(() => {
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:columns="tableColumnsDnd"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: 2000, y: 480 }"
:scroll="{ y: 'calc(100vh - 480px)' }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'id'">
<template v-if="column?.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.editText') }}</template>
@@ -902,10 +920,7 @@ onMounted(() => {
</a-tooltip>
<a-tooltip placement="left">
<template #title>{{ t('common.moreText') }}</template>
<a-dropdown
placement="bottomRight"
:trigger="['hover', 'click']"
>
<a-dropdown placement="bottomRight" trigger="click">
<a-button type="link">
<template #icon><EllipsisOutlined /> </template>
</a-button>
@@ -919,11 +934,17 @@ onMounted(() => {
<ImportOutlined />
{{ t('views.configManage.neManage.import') }}
</a-menu-item>
<a-menu-item key="start">
<a-menu-item
key="start"
v-if="!['OMC'].includes(record.neType)"
>
<thunderbolt-outlined />
{{ t('views.configManage.neManage.start') }}
</a-menu-item>
<a-menu-item key="stop">
<a-menu-item
key="stop"
v-if="!['OMC'].includes(record.neType)"
>
<pause-outlined />
{{ t('views.configManage.neManage.stop') }}
</a-menu-item>
@@ -974,20 +995,21 @@ onMounted(() => {
name="neType"
v-bind="modalStateFrom.validateInfos.neType"
>
<a-input
<a-auto-complete
v-model:value="modalState.from.neType"
allow-clear
:placeholder="t('views.configManage.neManage.neTypePlease')"
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>{{
t('views.configManage.neManage.neTypeTip')
}}</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
<a-input allow-clear :placeholder="t('common.inputPlease')">
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>{{
t('views.configManage.neManage.neTypeTip')
}}</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-auto-complete>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
@@ -1135,7 +1157,7 @@ onMounted(() => {
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.configManage.neManage.sync')"
name="province"
name="sync"
>
<a-switch
v-model:checked="modalState.from.sync"

View File

@@ -82,7 +82,7 @@ let tableColumns: ColumnsType = [
},
{
title: t('views.configManage.softwareManage.fileName'),
dataIndex: 'fileName',
dataIndex: 'name',
align: 'center',
width: 2,
},
@@ -94,7 +94,7 @@ let tableColumns: ColumnsType = [
},
{
title: t('views.configManage.softwareManage.updateTime'),
dataIndex: 'updateTime',
dataIndex: 'createTime',
align: 'center',
customRender(opt) {
if (!opt.value) return '';
@@ -104,7 +104,7 @@ let tableColumns: ColumnsType = [
},
{
title: t('views.configManage.softwareManage.description'),
dataIndex: 'comment',
dataIndex: 'name',
align: 'center',
width: 2,
},
@@ -274,6 +274,7 @@ function fnFileModalVisible(type: string | number, row: Record<string, any>) {
* 进行表达规则校验
*/
function fnFileModalOk() {
if (fileModalState.confirmLoading) return;
fileModalStateFrom
.validate()
.then(e => {
@@ -730,13 +731,15 @@ onMounted(() => {
}
} else {
message.warning({
content: t('views.configManage.softwareManage.nullData'),
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
// 获取列表数据
fnGetList();
});
// 获取列表数据
fnGetList();
});
</script>
@@ -889,10 +892,7 @@ onMounted(() => {
</a-tooltip>
<a-tooltip placement="left">
<template #title>{{ t('common.moreText') }}</template>
<a-dropdown
placement="bottomRight"
:trigger="['hover', 'click']"
>
<a-dropdown placement="bottomRight" trigger="click">
<a-button type="link">
<template #icon><EllipsisOutlined /> </template>
</a-button>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, ref, onUnmounted } from 'vue';
import { reactive, onMounted, toRaw, ref, onBeforeUnmount } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
@@ -39,6 +39,7 @@ let queryParams = reactive({
neType: 'AMF',
neId: '001',
eventType: 'auth-result',
imsi: '',
sortField: 'timestamp',
sortOrder: 'desc',
/**当前页数 */
@@ -52,6 +53,7 @@ function fnQueryReset() {
eventTypes.value = ['auth-result'];
queryParams = Object.assign(queryParams, {
eventType: 'auth-result',
imsi: '',
pageNum: 1,
pageSize: 20,
});
@@ -76,8 +78,6 @@ type TabeStateType = {
loading: boolean;
/**紧凑型 */
size: SizeType;
/**斑马纹 */
striped: boolean;
/**搜索栏 */
seached: boolean;
/**记录数据 */
@@ -90,7 +90,6 @@ type TabeStateType = {
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
striped: false,
seached: true,
data: [],
selectedRowKeys: [],
@@ -108,7 +107,7 @@ let tableColumns: ColumnsType = [
title: 'IMSI',
dataIndex: 'eventJSON',
align: 'left',
width: 100,
width: 150,
customRender(opt) {
const eventJSON = opt.value;
return eventJSON.imsi;
@@ -119,20 +118,20 @@ let tableColumns: ColumnsType = [
dataIndex: 'eventType',
key: 'eventType',
align: 'left',
width: 100,
width: 150,
},
{
title: t('views.dashboard.ue.result'),
dataIndex: 'eventJSON',
key: 'result',
align: 'left',
width: 100,
width: 150,
},
{
title: t('views.dashboard.ue.time'),
dataIndex: 'eventJSON',
key: 'time',
align: 'center',
align: 'left',
width: 150,
},
{
@@ -175,11 +174,6 @@ function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**表格斑马纹 */
function fnTableStriped(_record: unknown, index: number): any {
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
}
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
@@ -374,8 +368,10 @@ onMounted(() => {
});
});
onUnmounted(() => {
ws.close();
onBeforeUnmount(() => {
if (ws.state() !== -1) {
ws.close();
}
});
</script>
@@ -389,20 +385,29 @@ onUnmounted(() => {
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.ue.eventType')"
name="eventType "
>
<a-select
v-model:value="eventTypes"
mode="tags"
mode="multiple"
:options="dict.ueEventType"
:placeholder="t('common.selectPlease')"
@change="fnQueryEventTypeChange"
></a-select>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="IMSI" name="imsi ">
<a-input
v-model:value="queryParams.imsi"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
@@ -471,15 +476,6 @@ onUnmounted(() => {
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.tableStripedText') }}</template>
<a-switch
v-model:checked="tableState.striped"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
@@ -522,7 +518,6 @@ onUnmounted(() => {
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:row-class-name="fnTableStriped"
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
:row-selection="{
type: 'checkbox',
@@ -658,10 +653,6 @@ onUnmounted(() => {
</template>
<style lang="less" scoped>
.table :deep(.table-striped) td {
background-color: #fafafa;
}
.table :deep(.ant-pagination) {
padding: 0 24px;
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, ref, onUnmounted } from 'vue';
import { reactive, onMounted, toRaw, ref, onBeforeUnmount } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
@@ -37,6 +37,8 @@ let queryParams = reactive({
neType: 'IMS',
neId: '001',
recordType: 'MOC',
callerParty: '',
calledParty: '',
sortField: 'timestamp',
sortOrder: 'desc',
/**当前页数 */
@@ -50,6 +52,8 @@ function fnQueryReset() {
recordTypes.value = ['MOC'];
queryParams = Object.assign(queryParams, {
recordType: 'MOC',
callerParty: '',
calledParty: '',
pageNum: 1,
pageSize: 20,
});
@@ -74,8 +78,6 @@ type TabeStateType = {
loading: boolean;
/**紧凑型 */
size: SizeType;
/**斑马纹 */
striped: boolean;
/**搜索栏 */
seached: boolean;
/**记录数据 */
@@ -88,7 +90,6 @@ type TabeStateType = {
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
striped: false,
seached: true,
data: [],
selectedRowKeys: [],
@@ -106,7 +107,7 @@ let tableColumns: ColumnsType = [
title: t('views.dashboard.cdr.recordType'),
dataIndex: 'cdrJSON',
align: 'left',
width: 100,
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.recordType;
@@ -124,7 +125,7 @@ let tableColumns: ColumnsType = [
dataIndex: 'cdrJSON',
key: 'calledParty',
align: 'left',
width: 100,
width: 120,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.calledParty;
@@ -135,7 +136,7 @@ let tableColumns: ColumnsType = [
dataIndex: 'cdrJSON',
key: 'callerParty',
align: 'left',
width: 100,
width: 120,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.callerParty;
@@ -159,7 +160,7 @@ let tableColumns: ColumnsType = [
dataIndex: 'cdrJSON',
key: 'cause',
align: 'left',
width: 100,
width: 150,
},
{
title: t('views.dashboard.cdr.time'),
@@ -211,11 +212,6 @@ function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**表格斑马纹 */
function fnTableStriped(_record: unknown, index: number): any {
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
}
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
@@ -402,8 +398,10 @@ onMounted(() => {
});
});
onUnmounted(() => {
ws.close();
onBeforeUnmount(() => {
if (ws.state() !== -1) {
ws.close();
}
});
</script>
@@ -417,14 +415,14 @@ onUnmounted(() => {
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-col :lg="8" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.recordType')"
name="recordType "
>
<a-select
v-model:value="recordTypes"
mode="tags"
mode="multiple"
:options="
['MOC', 'MTC', 'MOSM', 'MTSM'].map(v => ({ value: v }))
"
@@ -433,7 +431,31 @@ onUnmounted(() => {
></a-select>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-col :lg="4" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.called')"
name="calledParty "
>
<a-input
v-model:value="queryParams.calledParty"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="4" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.caller')"
name="callerParty "
>
<a-input
v-model:value="queryParams.callerParty"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="4" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
@@ -501,15 +523,6 @@ onUnmounted(() => {
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.tableStripedText') }}</template>
<a-switch
v-model:checked="tableState.striped"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
@@ -552,7 +565,6 @@ onUnmounted(() => {
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:row-class-name="fnTableStriped"
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
:row-selection="{
type: 'checkbox',
@@ -659,10 +671,6 @@ onUnmounted(() => {
</template>
<style lang="less" scoped>
.table :deep(.table-striped) td {
background-color: #fafafa;
}
.table :deep(.ant-pagination) {
padding: 0 24px;
}

View File

@@ -0,0 +1,202 @@
<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'),
},
]);
//
function initPicture() {
Promise.allSettled([origGet()])
.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);
}
}
}
})
.then(() => {
const optionData: EChartsOption = {
title: [
{
show: false,
},
],
tooltip: {
trigger: 'item',
formatter: '{b} : {c}',
},
legend: {
orient: 'vertical',
right: '2%',
top: '10%',
data: alarmTypeType.value.map((item: any) => item.name), //label数组
textStyle: {
color: '#A7D6F4', // 设置图例文字颜色
},
},
grid: [
{
top: '60%',
left: '15%',
right: '25%',
bottom: '10%',
},
],
series: [
//饼图:
{
type: 'pie',
radius: '60%',
color: ['#f5222d', '#fa8c16', '#fadb14', '#1677ff', '#13c2c2'],
label: {
show: true,
position: 'inner',
formatter: (params: any) => {
if (!params.value) return '';
return `${params.value}`;
},
},
labelLine: {
show: false,
},
center: ['30%', '40%'],
data: alarmTypeType.value,
zlevel: 2, // 设置zlevel为1使得柱状图在下层显示
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
],
};
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,90 @@
<script setup lang="ts">
import { reactive, toRaw, watch } from 'vue';
import { dbGetJSON, dbSetJSON } from '@/utils/cache-db-utils';
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({
title: {
type: String,
default: '标题',
},
visible: {
type: Boolean,
default: false,
},
});
/**数据参数 */
let dataState = reactive({
/**基站数 */
baseNum: 0,
/**核心网数 */
coreNetNum: 0,
/**在线用户数 */
onlineUserNum: 0,
});
/**弹框取消按钮事件 */
function fnModalOk() {
dbSetJSON('tbl_mocn', `tmp`, toRaw(dataState));
emit('ok');
emit('update:visible', false);
}
/**弹框取消按钮事件 */
function fnModalCancel() {
emit('cancel');
emit('update:visible', false);
}
/**显示弹框时初始数据 */
function init() {
// 读取数据
dbGetJSON('tbl_mocn', `tmp`).then(data => {
if (data) {
Object.assign(dataState, data);
}
});
}
/**监听是否显示,初始数据 */
watch(
() => props.visible,
val => {
if (val) init();
}
);
</script>
<template>
<a-modal
width="800px"
:title="props.title"
:visible="props.visible"
:keyboard="false"
:mask-closable="false"
@cancel="fnModalCancel"
@ok="fnModalOk"
>
<a-form
name="dataState"
layout="horizontal"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-form-item label="baseNum" name="baseNum">
<a-input-number v-model:value="dataState.baseNum"> </a-input-number>
</a-form-item>
<a-form-item label="coreNetNum" name="coreNetNum">
<a-input-number v-model:value="dataState.coreNetNum"> </a-input-number>
</a-form-item>
<a-form-item label="onlineUserNum" name="onlineUserNum">
<a-input-number v-model:value="dataState.onlineUserNum">
</a-input-number>
</a-form-item>
</a-form>
</a-modal>
</template>
<style lang="less" scoped></style>
<style lang="less" scoped></style>

View File

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

View File

@@ -70,10 +70,10 @@ const alarmTypeType = ref<any>([
value: 0,
name: t('views.index.Warning'),
},
{
value: 0,
name: t('views.index.Event'),
},
// {
// value: 0,
// name: t('views.index.Event'),
// },
]);
/**告警类型Top数据 */
@@ -105,9 +105,9 @@ function initPicture() {
case 'Warning':
index = 3;
break;
case 'Event':
index = 4;
break;
// case 'Event':
// index = 4;
// break;
}
alarmTypeType.value[index].value = Number(item.value);
}
@@ -149,7 +149,7 @@ function initPicture() {
legend: {
orient: 'vertical',
right: '2%',
top: '10%',
top: '12%',
data: alarmTypeType.value.map((item: any) => item.name), //label数组
textStyle: {
color: '#A7D6F4', // 设置图例文字颜色

View File

@@ -76,7 +76,7 @@ function fnGraphEvent(graph: Graph) {
// 节点点击
graph.on('node:click', evt => {
// 获得鼠标当前目标节点
const node = evt.item?.getModel();
const node = evt.item?.getModel();
if (node && node.id && !notNeNodes.includes(node.id)) {
graphNodeClickID.value = node.id;
}
@@ -129,6 +129,9 @@ function handleRanderGraph(
var observer = new ResizeObserver(function (entries) {
// 当元素大小发生变化时触发回调函数
entries.forEach(function (entry) {
if (!graphG6.value) {
return;
}
graphG6.value.changeSize(
entry.contentRect.width,
entry.contentRect.height - 30

View File

@@ -221,7 +221,7 @@
.upfFlowTotal .inner .chart {
width: 100%;
height: 100%;
margin-top: 1rem;
margin-top: 0.1rem;
}
.upfFlowTotal .inner .chart .data {
display: flex;

View File

@@ -158,3 +158,15 @@ export function neStateParse(neType: string, data: Record<string, any>) {
// 请求标记复位
neStateRequestMap.value.set(neType, false);
}
/**属性复位 */
export function topologyReset() {
graphState.data = {
combos: [],
edges: [],
nodes: [],
};
graphG6.value = null;
graphNodeClickID.value = 'UPF';
neStateRequestMap.value = new Map();
}

View File

@@ -1,8 +1,7 @@
import { parseSizeFromBits, parseSizeFromKbs } from '@/utils/parse-utils';
import { ref } from 'vue';
/**UPF-流量数据 */
export const upfFlowData = ref<{
type FDType = {
/**时间 */
lineXTime: string[];
/**上行 N3 */
@@ -11,7 +10,10 @@ export const upfFlowData = ref<{
lineYDown: number[];
/**容量 */
cap: number;
}>({
};
/**UPF-流量数据 */
export const upfFlowData = ref<FDType>({
lineXTime: [],
lineYUp: [],
lineYDown: [],
@@ -74,3 +76,32 @@ export function upfTFParse(data: Record<string, string>) {
/**UPF-总流量数 选中 */
export const upfTFActive = ref<number>(0);
/**属性复位 */
export function upfTotalFlowReset() {
upfFlowData.value = {
lineXTime: [],
lineYUp: [],
lineYDown: [],
cap: 0,
};
upfTotalFlow.value = [
// 0天 当天24小时
{
up: '0 B',
down: '0 B',
requestFlag: false,
},
{
up: '0 B',
down: '0 B',
requestFlag: false,
},
{
up: '0 B',
down: '0 B',
requestFlag: false,
},
];
upfTFActive.value = 0;
}

View File

@@ -1,14 +1,5 @@
import { ref } from 'vue';
/**UE事件数据 */
export const ueEventData = ref<Record<string, any>[]>([]);
/**UE事件总量 */
export const ueEventTotal = ref<number>(0);
/**UE事件推送id */
export const ueEventId = ref<string>('');
/**ueEvent UE会话事件 数据解析 */
export function ueEventParse(item: Record<string, any>) {
let evData: Record<string, any> = item.eventJSON;
@@ -23,21 +14,13 @@ export function ueEventParse(item: Record<string, any>) {
return {
eType: 'ue',
eId: `ue_${item.id}_${Date.now()}`,
eTime: +item.timestamp,
id: item.id,
type: item.eventType,
data: evData,
};
}
/**CDR事件数据 */
export const cdrEventData = ref<Record<string, any>[]>([]);
/**CDR事件总量 */
export const cdrEventTotal = ref<number>(0);
/**CDR事件推送id */
export const cdrEventId = ref<string>('');
/**cdrEvent CDR会话事件 数据解析 */
export function cdrEventParse(item: Record<string, any>) {
let evData: Record<string, any> = item.cdrJSON || item.CDR;
@@ -58,6 +41,7 @@ export function cdrEventParse(item: Record<string, any>) {
return {
eType: 'cdr',
eId: `cdr_${item.id}_${Date.now()}`,
eTime: +item.timestamp,
id: item.id,
data: evData,
};
@@ -69,3 +53,10 @@ export const eventData = ref<Record<string, any>[]>([]);
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

@@ -7,9 +7,15 @@ import {
eventData,
eventTotal,
eventId,
userActivityReset,
} from './useUserActivity';
import { upfTotalFlow, upfTFParse, upfFlowParse } from './useUPFTotalFlow';
import { neStateParse } from './useTopology';
import {
upfTotalFlow,
upfTFParse,
upfFlowParse,
upfTotalFlowReset,
} from './useUPFTotalFlow';
import { topologyReset, neStateParse } from './useTopology';
import PQueue from 'p-queue';
/**websocket连接 */
@@ -56,6 +62,11 @@ export default function useWS() {
eventData.value.push(v);
}
}
// 有数据进行排序
if (eventData.value.length > 10) {
eventData.value.sort((a, b) => b.eTime - a.eTime);
}
if (eventData.value.length > 0) {
eventId.value = eventData.value[0].eId;
}
@@ -71,6 +82,11 @@ export default function useWS() {
eventData.value.push(v);
}
}
// 有数据进行排序
if (eventData.value.length > 10) {
eventData.value.sort((a, b) => b.eTime - a.eTime);
}
if (eventData.value.length > 0) {
eventId.value = eventData.value[0].eId;
}
@@ -225,6 +241,9 @@ export default function useWS() {
onBeforeUnmount(() => {
ws.close();
userActivityReset();
upfTotalFlowReset();
topologyReset();
});
return {

View File

@@ -161,6 +161,7 @@ function loadData() {
upfTFSend(7);
upfTFSend(30);
clearInterval(interval10s.value);
interval10s.value = setInterval(() => {
upfTFActive.value = upfTFActive.value >= 2 ? 0 : upfTFActive.value + 1;
if (upfTFActive.value === 0) {
@@ -172,6 +173,7 @@ function loadData() {
}
}, 10_000);
clearInterval(interval5s.value);
interval5s.value = setInterval(() => {
fnGetSkim(); // 获取概览信息
fnGetNeState(); // 获取网元状态
@@ -191,7 +193,9 @@ onMounted(() => {
onBeforeUnmount(() => {
clearInterval(interval10s.value);
interval10s.value = null;
clearInterval(interval5s.value);
interval5s.value = null;
});
</script>

View File

@@ -0,0 +1,858 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/lib/table';
import { listAct, exportAll } from '@/api/faultManage/eventAlarm';
import useI18n from '@/hooks/useI18n';
import useDictStore from '@/store/modules/dict';
import saveAs from 'file-saver';
import TableColumnsDnd from '@/components/TableColumnsDnd/index.vue';
import { writeSheet } from '@/utils/execl-utils';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { readLoalXlsx } from '@/utils/execl-utils';
const { getDict } = useDictStore();
const { t, currentLocale } = useI18n();
/**字典数据 */
let dict: {
/**活动告警类型 */
activeAlarmType: DictType[];
/**告警清除类型 */
activeClearType: DictType[];
/**告警清除类型 */
activeAckState: DictType[];
/**原始严重程度 */
activeAlarmSeverity: DictType[];
} = reactive({
activeAlarmType: [],
activeClearType: [],
activeAckState: [],
activeAlarmSeverity: [],
});
/**表格字段列排序 */
let tableColumnsDnd = ref<ColumnsType>([]);
/**记录开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']);
/**查询参数 */
let queryParams = reactive({
/**告警设备类型 */
neType: '',
/**告警网元名称 */
neName: '',
/**告警网元标识 */
neId: '',
/**告警编号 */
alarmCode: '',
/**告警级别 */
origSeverity: undefined,
beginTime: '',
endTime: '',
/**告警产生时间 */
eventTime: '',
/**虚拟化标识 */
pvFlag: undefined,
/**告警类型 */
alarmType: undefined,
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
/**告警设备类型 */
neType: '',
/**告警网元名称 */
neName: '',
/**告警网元标识 */
neId: '',
/**告警编号 */
alarmCode: '',
/**告警级别 */
origSeverity: undefined,
/**告警产生时间 */
eventTime: '',
/**虚拟化标识 */
pvFlag: undefined,
/**告警类型 */
alarmType: undefined,
/**当前页数 */
});
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: object[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: false,
data: [],
selectedRowKeys: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('views.faultManage.activeAlarm.alarmId'),
dataIndex: 'alarmId',
align: 'center',
width: 5,
},
{
title: t('views.faultManage.activeAlarm.neId'),
dataIndex: 'neId',
align: 'center',
width: 5,
},
{
title: t('views.faultManage.activeAlarm.neName'),
dataIndex: 'neName',
align: 'center',
width: 5,
},
{
title: t('views.faultManage.activeAlarm.neType'),
dataIndex: 'neType',
align: 'center',
width: 5,
},
{
title: t('views.faultManage.activeAlarm.alarmCode'),
dataIndex: 'alarmCode',
align: 'center',
width: 5,
},
{
title: t('views.faultManage.activeAlarm.alarmTitle'),
dataIndex: 'alarmTitle',
align: 'left',
width: 5,
},
{
title: t('views.faultManage.activeAlarm.eventTime'),
dataIndex: 'eventTime',
align: 'center',
sorter: (a: any, b: any) => 1,
width: 5,
},
{
title: t('views.faultManage.activeAlarm.pvFlag'),
dataIndex: 'pvFlag',
align: 'center',
width: 5,
},
{
title: t('common.operate'),
key: 'alarm_id',
align: 'center',
fixed: 'right',
width: 5,
},
];
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**详情框是否显示 */
visibleByView: boolean;
/**新增框或修改框是否显示 */
visibleByEdit: boolean;
/**显示过滤设置是否显示 */
visibleByShowSet: boolean;
/**个性化设置置是否显示 */
visibleByMyselfSet: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**表单数据 */
showSetFrom: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
visibleByView: false,
visibleByEdit: false,
visibleByShowSet: false,
visibleByMyselfSet: false,
title: '全部信息',
from: {
alarmId: '',
alarmSeq: '',
neId: '',
neName: '',
neType: '',
alarmCode: '',
alarmTitle: '',
eventTime: '',
alarmType: '',
pvFlag: '',
objectName: '',
locationInfo: '',
province: '',
alarmStatus: '',
specificProblemId: '',
specificProblem: '',
addInfo: '',
clearType: '',
clearTime: '',
ackState: '',
ackUser: '',
ackTime: '',
origSeverity: '',
},
showSetFrom: {
ne_type: '',
ne_id: '',
alarm_type: '',
orig_severity: '',
alarm_code: '',
pv_flag: '',
},
// myselfSetFrom:{
// }
confirmLoading: false,
});
/**
* 对话框弹出显示为 查看
* @param row 单行记录信息
*/
function fnModalVisibleByVive(row: Record<string, any>) {
modalState.from = Object.assign(modalState.from, row);
modalState.from.clearType = `${modalState.from.clearType}`;
modalState.from.ackState = `${modalState.from.ackState}`;
modalState.title = t('views.faultManage.activeAlarm.viewIdInfo', {
alarmId: row.alarmId,
});
modalState.visibleByView = true;
}
/**表格状态 */
const state = reactive<{
selectedRowKeys: (string | number)[];
selectedRow: Record<string, any>;
loading: boolean;
}>({
selectedRowKeys: [], // Check here to configure the default column
selectedRow: {},
loading: false,
});
/**监听多选 */
const onSelectChange = (
keys: (string | number)[],
record: Record<string, any>
) => {
state.selectedRowKeys = keys;
state.selectedRow = record;
};
/**
* 导出全部
*/
function fnExportAll() {
Modal.confirm({
title: 'Tip',
content: t('views.faultManage.eventAlarm.exportSure'),
onOk() {
const key = 'exportAlarm';
message.loading({ content: t('common.loading'), key });
let sortArr: any = [];
tableColumnsDnd.value.forEach((item: any) => {
if (item.dataIndex) sortArr.push(item.dataIndex);
});
// 排序字段
const sortData = {
header: sortArr,
};
exportAll(queryParams).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
res.data = res.data.map((objA: any) => {
let filteredObj: any = {};
sortArr.forEach((key: any) => {
if (objA.hasOwnProperty(key)) {
filteredObj[key] = objA[key];
}
});
dict.activeAckState.map((item: any) => {
if (item.value === `${filteredObj.ackState}`)
filteredObj.ackState = item.label;
});
return filteredObj;
});
message.success({
content: t('common.msgSuccess', { msg: t('common.export') }),
key,
duration: 3,
});
writeSheet(res.data, 'alarm', sortData).then(fileBlob =>
saveAs(fileBlob, `evnet_${Date.now()}.xlsx`)
);
} else {
message.error({
content: `${res.msg}`,
key,
duration: 3,
});
}
});
},
});
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.visibleByEdit = false;
modalState.visibleByView = false;
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
if (!queryRangePicker.value) {
queryRangePicker.value = ['', ''];
}
queryParams.beginTime = queryRangePicker.value[0];
queryParams.endTime = queryRangePicker.value[1];
listAct(toRaw(queryParams)).then((res: any) => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选
if (state.selectedRowKeys.length > 0) {
state.selectedRowKeys = [];
}
////
tablePagination.total = res.total;
tableState.data = res.rows;
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
} else {
tablePagination.total = 0;
tableState.data = [];
}
tableState.loading = false;
});
}
onMounted(() => {
// 初始字典数据
Promise.allSettled([
getDict('active_alarm_type'),
getDict('active_clear_type'),
getDict('active_ack_state'),
getDict('active_alarm_severity'),
]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.activeAlarmType = resArr[0].value;
}
if (resArr[1].status === 'fulfilled') {
dict.activeClearType = resArr[1].value;
}
if (resArr[2].status === 'fulfilled') {
dict.activeAckState = resArr[2].value;
}
if (resArr[3].status === 'fulfilled') {
dict.activeAlarmSeverity = resArr[3].value;
}
});
fnGetList();
});
</script>
<template>
<PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.neType')"
name="ne_type"
>
<a-input v-model:value="queryParams.neType" allow-clear></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.neName')"
name="ne_name"
>
<a-input v-model:value="queryParams.neName" allow-clear></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.neId')"
name="ne_id"
>
<a-input v-model:value="queryParams.neId" allow-clear></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.alarmCode')"
name="alarm_code"
>
<a-input
v-model:value="queryParams.alarmCode"
allow-clear
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.pvFlag')"
name="pv_flag"
>
<a-select
v-model:value="queryParams.pvFlag"
:placeholder="t('common.selectPlease')"
:options="[
{ label: 'PNF', value: 'PNF' },
{ label: 'VNF', value: 'VNF' },
]"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.eventTime')"
name="eventTime"
>
<a-range-picker
v-model:value="queryRangePicker"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
show-time
style="width: 400px"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.alarmType')"
name="alarm_type"
>
<a-select
v-model:value="queryParams.alarmType"
:placeholder="t('common.selectPlease')"
:options="dict.activeAlarmType"
/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<div class="button-container">
<a-button
type="primary"
@click.prevent="fnExportAll()"
:disabled="tableState.data.length <= 0"
>
<template #icon> <export-outlined /> </template>
{{ t('views.faultManage.activeAlarm.exportAll') }}
</a-button>
</div>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<div class="button-container">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click">
<a-button type="text">
<template #icon><ColumnHeightOutlined /></template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
<a-menu-item key="middle">
{{ t('common.size.middle') }}
</a-menu-item>
<a-menu-item key="small">
{{ t('common.size.small') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
<TableColumnsDnd
cache-id="alarmActive"
:columns="tableColumns"
v-model:columns-dnd="tableColumnsDnd"
></TableColumnsDnd>
</div>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumnsDnd"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:row-selection="{
columnWidth: 2,
selectedRowKeys: state.selectedRowKeys,
onChange: onSelectChange,
}"
:pagination="tablePagination"
:scroll="{ x: 2500, y: 400 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'origSeverity'">
<DictTag
:options="dict.activeAlarmSeverity"
:value="record.origSeverity"
/>
</template>
<template v-if="column.key === 'alarmType'">
<DictTag
:options="dict.activeAlarmType"
:value="record.alarmType"
/>
</template>
<template v-if="column.key === 'clearType'">
<DictTag
:options="dict.activeClearType"
:value="record.clearType"
/>
</template>
<template v-if="column.key === 'ackState'">
<DictTag :options="dict.activeAckState" :value="record.ackState" />
</template>
<template v-if="column.key === 'alarm_id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.viewText') }}</template>
<a-button
type="link"
@click.prevent="fnModalVisibleByVive(record)"
>
<template #icon><InfoCircleOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
</a-table>
</a-card>
<!-- 详情框 -->
<a-modal
width="800px"
:body-style="{ height: '520px', overflowY: 'scroll' }"
:keyboard="false"
:mask-closable="false"
:visible="modalState.visibleByView"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@cancel="fnModalCancel"
>
<template v-slot:footer>
<a-button @click="fnModalCancel">{{
t('views.faultManage.activeAlarm.closeModal')
}}</a-button>
</template>
<a-form
name="modalStateFrom"
layout="horizontal"
:label-col="{ span: 8 }"
:label-wrap="true"
>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.alarmId')"
name="alarmId"
>
{{ modalState.from.alarmId }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.alarmSeq')"
name="alarmSeq"
>
{{ modalState.from.alarmSeq }}
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.neId')"
name="neId"
>
{{ modalState.from.neId }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.neName')"
name="neName"
>
{{ modalState.from.neName }}
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.neType')"
name="neType"
>
{{ modalState.from.neType }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.alarmCode')"
name="alarmCode"
>
{{ modalState.from.alarmCode }}
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.alarmTitle')"
name="alarmTitle"
>
{{ modalState.from.alarmTitle }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.eventTime')"
name="eventTime"
>
{{ modalState.from.eventTime }}
</a-form-item>
</a-col>
</a-row>
<a-form-item
:label="t('views.faultManage.activeAlarm.locationInfo')"
name="locationInfo"
:label-col="{ span: 4 }"
>
{{ modalState.from.locationInfo }}
</a-form-item>
<a-row :gutter="16"> </a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.province')"
name="province"
>
{{ modalState.from.province }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.alarmType')"
name="alarmType"
>
{{ modalState.from.alarmType }}
</a-form-item>
</a-col>
</a-row>
<a-form-item
:label="t('views.faultManage.activeAlarm.addInfo')"
name="addInfo"
:label-col="{ span: 4 }"
>
{{ modalState.from.addInfo }}
</a-form-item>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.specificProblemId')"
name="specificProblemId"
>
{{ modalState.from.specificProblemId }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.faultManage.activeAlarm.objectName')"
name="objectName"
>
{{ modalState.from.objectName }}
</a-form-item>
</a-col>
</a-row>
<a-form-item
:label="t('views.faultManage.activeAlarm.specificProblem')"
name="specificProblem"
:label-col="{ span: 4 }"
>
{{ modalState.from.specificProblem }}
</a-form-item>
</a-form>
</a-modal>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>
<style lang="less">
.full-modal {
.ant-modal {
max-width: 100%;
top: 0;
padding-bottom: 0;
margin: 0;
}
.ant-modal-content {
display: flex;
flex-direction: column;
height: calc(100vh);
}
.ant-modal-body {
flex: 1;
}
}
</style>

View File

@@ -2,7 +2,7 @@
import { PageContainer } from 'antdv-pro-layout';
import { ColumnsType } from 'ant-design-vue/lib/table';
import { message } from 'ant-design-vue/lib';
import { reactive, toRaw, ref, onMounted, onBeforeUnmount } from 'vue';
import { reactive, toRaw, ref, onMounted, onBeforeUnmount, markRaw } from 'vue';
import { listMain } from '@/api/index';
import useI18n from '@/hooks/useI18n';
import { TooltipComponent } from 'echarts/components';
@@ -30,6 +30,12 @@ echarts.use([
LabelLayout,
]);
/**图DOM节点实例对象 */
const statusBar = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
const statusBarChart = ref<any>(null);
/**网元状态字典数据 */
let indexColor = ref<DictType[]>([
{ label: 'Normal', value: 'normal', elTagType: '', elTagClass: '#91cc75' },
@@ -74,7 +80,7 @@ let tableColumns: ColumnsType = [
dataIndex: 'serialNum',
align: 'center',
},
{
{
title: t('views.index.expiryDate'),
dataIndex: 'expiryDate',
align: 'center',
@@ -92,8 +98,6 @@ type TabeStateType = {
loading: boolean;
/**紧凑型 */
size: string;
/**斑马纹 */
striped: boolean;
/**搜索栏 */
seached: boolean;
/**记录数据 */
@@ -106,7 +110,6 @@ type TabeStateType = {
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
striped: false,
seached: false,
data: [],
selectedRowKeys: [],
@@ -181,17 +184,7 @@ function fnGetList(one: boolean) {
}
}
var chartDom: any = document.getElementById('echarts-records');
var existingChart = echarts.getInstanceByDom(chartDom);
var myChart: any;
if (existingChart) {
myChart = existingChart;
myChart.clear(); // 清空图表,重新设置数据
} else {
myChart = echarts.init(chartDom);
}
var option = {
const optionData: any = {
title: {
text: '',
subtext: '',
@@ -228,14 +221,28 @@ function fnGetList(one: boolean) {
],
};
option && myChart.setOption(option);
window.onresize = function () {
// echarts 窗口缩放自适应 随着div--echarts-records的大小来适应
myChart.resize();
};
fnDesign(statusBar.value, optionData);
});
}
function fnDesign(container: HTMLElement | undefined, option: any) {
if (!container) return;
if (!statusBarChart.value) {
statusBarChart.value = markRaw(echarts.init(container, 'light'));
}
option && statusBarChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (statusBarChart.value) {
statusBarChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
/**抽屉 网元详细信息 */
const visible = ref(false);
const closeDrawer = () => {
@@ -255,6 +262,16 @@ function rowClick(record: any, index: any) {
return false;
} else {
let pronData = toRaw(record);
const totalMemInKB = pronData.memUsage?.totalMem;
const nfUsedMemInKB = pronData.memUsage?.nfUsedMem;
const sysMemUsageInKB = pronData.memUsage?.sysMemUsage;
// 将KB转换为MB
const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100;
const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
const sysMemUsageInMB =
Math.round((sysMemUsageInKB / 1024) * 100) / 100;
//渲染详细信息
pronInfo = {
hostName: pronData.hostName,
@@ -273,14 +290,14 @@ function rowClick(record: any, index: any) {
'%',
memoryUse:
'Total:' +
pronData.memUsage?.totalMem +
'KB; ' +
totalMemInMB +
'MB; ' +
pronData.name +
':' +
pronData.memUsage?.nfUsedMem +
'KB; SYS:' +
pronData.memUsage?.sysMemUsage +
'KB',
nfUsedMemInMB +
'MB; SYS:' +
sysMemUsageInMB +
'MB',
capability: pronData.capability,
serialNum: pronData.serialNum,
expiryDate: pronData.expiryDate,
@@ -394,8 +411,8 @@ onBeforeUnmount(() => {
<a-col :lg="10" :md="8" :xs="24">
<a-card :title="t('views.index.runStatus')" style="margin-bottom: 16px">
<div
id="echarts-records"
style="width: 100%; min-height: 200px"
ref="statusBar"
></div>
</a-card>
<a-card :title="t('views.index.mark')" style="margin-top: 16px">

View File

@@ -80,6 +80,24 @@ let tableColumns: ColumnsType = [
align: 'center',
width: 3,
},
{
title: t('views.logManage.forwarding.alarmInter'),
dataIndex: 'interface',
align: 'center',
width: 5,
},
{
title: t('views.logManage.forwarding.alarmObj'),
dataIndex: 'toUser',
align: 'center',
width: 5,
},
{
title: t('views.logManage.forwarding.alarmInfo'),
dataIndex: 'operResult',
align: 'center',
width: 6,
},
{
title: t('views.logManage.forwarding.type'),
dataIndex: 'neType',
@@ -104,24 +122,12 @@ let tableColumns: ColumnsType = [
align: 'center',
width: 4,
},
{
title: t('views.logManage.forwarding.alarmObj'),
dataIndex: 'toUser',
align: 'center',
width: 5,
},
{
title: t('views.logManage.forwarding.alarmTitle'),
dataIndex: 'alarmTitle',
align: 'left',
align: 'center',
width: 5,
},
{
title: t('views.logManage.forwarding.alarmInfo'),
dataIndex: 'operResult',
align: 'left',
width: 6,
},
{
title: t('views.logManage.forwarding.eventTime'),
dataIndex: 'eventTime',

View File

@@ -1,358 +0,0 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/lib/table';
import { parseDateToStr } from '@/utils/date-utils';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { listOperationLog } from '@/api/logManage/operation';
import useDictStore from '@/store/modules/dict';
import useI18n from '@/hooks/useI18n';
const { getDict } = useDictStore();
const { t } = useI18n();
/**字典数据 */
let dict: {
/**操作日志操作类型 */
operationLogType: DictType[];
} = reactive({
operationLogType: [],
});
/**查询参数 */
let queryParams = reactive({
/**登录账号 */
accountName: '',
/**操作类型 */
opType: undefined,
/**记录时间 */
beginTime: '',
endTime: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
accountName: '',
opType: undefined,
beginTime: '',
endTime: '',
pageNum: 1,
pageSize: 20,
});
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: object[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: true,
data: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('common.rowId'),
dataIndex: 'opId',
align: 'center',
},
{
title: '登录账号',
dataIndex: 'accountName',
align: 'center',
},
{
title: '用户类型',
dataIndex: 'accountType',
align: 'center',
},
{
title: '源IP地址',
dataIndex: 'opIp',
align: 'center',
},
{
title: '操作对象',
dataIndex: 'subsysTag',
align: 'center',
},
{
title: '操作类型',
dataIndex: 'opType',
key: 'opType',
align: 'center',
},
{
title: '操作内容',
dataIndex: 'opContent',
align: 'center',
},
{
title: '操作结果',
dataIndex: 'opResult',
align: 'center',
},
{
title: '开始时间',
dataIndex: 'beginTime',
align: 'center',
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value);
},
},
{
title: '结束时间',
dataIndex: 'endTime',
align: 'center',
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value);
},
},
{
title: '网元虚拟化标识',
dataIndex: 'vnfFlag',
align: 'center',
customRender(opt) {
if (opt.value === 0) return 'PNF';
if (opt.value === 1) return 'VNF';
return '';
},
},
];
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**查询备份信息列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if(pageNum){
queryParams.pageNum = pageNum;
}
listOperationLog(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
tablePagination.total = res.total;
tableState.data = res.rows;
if (tablePagination.total <=(queryParams.pageNum - 1) * tablePagination.pageSize &&queryParams.pageNum !== 1) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
}
tableState.loading = false;
});
}
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('operation_log_type')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.operationLogType = resArr[0].value;
}
});
// 获取列表数据
fnGetList();
});
</script>
<template>
<PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="登录账号" name="accountName">
<a-input
v-model:value="queryParams.accountName"
:allow-clear="true"
placeholder="查询登录账号"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="操作类型" name="opType">
<a-select
v-model:value="queryParams.opType"
allow-clear
placeholder="请选择操作类型"
:options="dict.operationLogType"
>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="开始时间" name="beginTime">
<a-date-picker
show-time
v-model:value="queryParams.beginTime"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
placeholder="查询结束时间"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="结束时间" name="endTime">
<a-date-picker
show-time
v-model:value="queryParams.endTime"
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
placeholder="查询结束时间"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title> </template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click" placement="bottomRight">
<a-button type="text">
<template #icon><ColumnHeightOutlined /></template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
<a-menu-item key="middle">
{{ t('common.size.middle') }}
</a-menu-item>
<a-menu-item key="small">
{{ t('common.size.small') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="opId"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: true }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'opType'">
<DictTag :options="dict.operationLogType" :value="record.opType" />
</template>
</template>
</a-table>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -1,337 +0,0 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/lib/table';
import { parseDateToStr } from '@/utils/date-utils';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { listSecurityLog } from '@/api/logManage/security';
import useDictStore from '@/store/modules/dict';
import useI18n from '@/hooks/useI18n';
const { getDict } = useDictStore();
const { t } = useI18n();
/**字典数据 */
let dict: {
/**安全日志操作类型 */
securityLogType: DictType[];
} = reactive({
securityLogType: [],
});
/**记录开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']);
/**查询参数 */
let queryParams = reactive({
/**登录账号 */
accountName: '',
/**操作类型 */
opType: undefined,
/**记录时间 */
beginTime: '',
endTime: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
accountName: '',
opType: undefined,
beginTime: '',
endTime: '',
pageNum: 1,
pageSize: 20,
});
queryRangePicker.value = ['', ''];
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: object[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: true,
data: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('common.rowId'),
dataIndex: 'id',
align: 'center',
},
{
title: '登录账号',
dataIndex: 'accountName',
align: 'center',
},
{
title: '用户类型',
dataIndex: 'accountType',
align: 'center',
},
{
title: '源IP地址',
dataIndex: 'opIp',
align: 'center',
},
{
title: '操作类型',
dataIndex: 'opType',
key: 'opType',
align: 'center',
},
{
title: '操作内容',
dataIndex: 'opContent',
align: 'center',
},
{
title: '操作结果',
dataIndex: 'opResult',
align: 'center',
customRender(opt) {
if (opt.value === 1) return '成功';
return '失败';
},
},
{
title: '记录时间',
dataIndex: 'opTime',
align: 'center',
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value);
},
},
];
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**查询备份信息列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if(pageNum){
queryParams.pageNum = pageNum;
}
if (!queryRangePicker.value) {
queryRangePicker.value = ['', ''];
}
queryParams.beginTime = queryRangePicker.value[0];
queryParams.endTime = queryRangePicker.value[1];
listSecurityLog(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
tablePagination.total = res.total;
tableState.data = res.rows;
if (tablePagination.total <=(queryParams.pageNum - 1) * tablePagination.pageSize &&queryParams.pageNum !== 1) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
}
tableState.loading = false;
});
}
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('security_log_type')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.securityLogType = resArr[0].value;
}
});
// 获取列表数据
fnGetList();
});
</script>
<template>
<PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="登录账号" name="accountName">
<a-input
v-model:value="queryParams.accountName"
:allow-clear="true"
placeholder="查询登录账号"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="操作类型" name="opType">
<a-select
v-model:value="queryParams.opType"
allow-clear
placeholder="请选择操作类型"
:options="dict.securityLogType"
>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-form-item label="记录时间" name="queryRangePicker">
<a-range-picker
v-model:value="queryRangePicker"
allow-clear
bordered
show-time
value-format="YYYY-MM-DD HH:mm:ss"
format="YYYY-MM-DD HH:mm:ss"
:placeholder="['记录开始', '记录结束']"
style="width: 100%"
></a-range-picker>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title> </template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click" placement="bottomRight">
<a-button type="text">
<template #icon><ColumnHeightOutlined /></template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
<a-menu-item key="middle">
{{ t('common.size.middle') }}
</a-menu-item>
<a-menu-item key="small">
{{ t('common.size.small') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: true }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'opType'">
<DictTag :options="dict.securityLogType" :value="record.opType" />
</template>
</template>
</a-table>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -14,10 +14,6 @@ const appStore = useAppStore();
const router = useRouter();
const route = useRoute();
/**登录后重定向页面 */
const redirectPath =
(route.query && (route.query.redirect as string)) || '/index';
let state = reactive({
/**表单属性 */
from: {
@@ -52,7 +48,9 @@ function fnFinish() {
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('views.login.loginSuccess'), 3);
router.push({ path: redirectPath });
/**登录后重定向页面 */
const redirectPath = route.query?.redirect || '/index';
router.push({ path: redirectPath as string });
} else {
message.error(`${res.msg}`, 3);
}
@@ -311,7 +309,7 @@ function fnClickHelpDoc(language?: string) {
v-if="appStore.i18nOpen"
>
<a-col :offset="18" :span="6">
<a-dropdown :trigger="['click', 'hover']">
<a-dropdown trigger="click">
<a-button size="small" type="default">
{{ t('i18n') }}
<DownOutlined />
@@ -344,7 +342,7 @@ function fnClickHelpDoc(language?: string) {
// background: url('./../assets/black_dot.png') 0% 0% / 14px 14px repeat;
background-image: url(./../assets/background.jpg);
background-image: url(@/assets/background.jpg);
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
@@ -436,7 +434,7 @@ function fnClickHelpDoc(language?: string) {
min-width: 260px;
margin: 0 auto;
margin-left: 60%;
border-radius: 4px;
border-radius: 6px;
& .desc {
text-align: center;

View File

@@ -78,8 +78,6 @@ type TabeStateType = {
loading: boolean;
/**紧凑型 */
size: SizeType;
/**斑马纹 */
striped: boolean;
/**搜索栏 */
seached: boolean;
/**记录数据 */
@@ -92,7 +90,6 @@ type TabeStateType = {
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
striped: false,
seached: false,
data: [],
selectedRowKeys: [],
@@ -185,11 +182,6 @@ function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**表格斑马纹 */
function fnTableStriped(_record: unknown, index: number): any {
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
}
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
@@ -714,15 +706,6 @@ onMounted(() => {
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.tableStripedText') }}</template>
<a-switch
v-model:checked="tableState.striped"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
@@ -764,7 +747,6 @@ onMounted(() => {
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:row-class-name="fnTableStriped"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 120 }"
:row-selection="{
@@ -1140,10 +1122,6 @@ onMounted(() => {
</template>
<style lang="less" scoped>
.table :deep(.table-striped) td {
background-color: #fafafa;
}
.table :deep(.ant-pagination) {
padding: 0 24px;
}

View File

@@ -96,8 +96,6 @@ type TabeStateType = {
loading: boolean;
/**紧凑型 */
size: SizeType;
/**斑马纹 */
striped: boolean;
/**搜索栏 */
seached: boolean;
/**记录数据 */
@@ -110,7 +108,6 @@ type TabeStateType = {
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
striped: false,
seached: true,
data: [],
selectedRowKeys: [],
@@ -122,6 +119,7 @@ let tableColumns: ColumnsType = [
title: t('common.rowId'),
dataIndex: 'jobLogId',
align: 'center',
width: 100,
},
{
title: t('views.monitor.jobLog.jobName'),
@@ -164,7 +162,7 @@ let tableColumns: ColumnsType = [
dataIndex: 'costTime',
key: 'costTime',
align: 'right',
width: 100,
width: 150,
customRender(opt) {
return `${opt.value} ms`;
},
@@ -210,11 +208,6 @@ function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**表格斑马纹 */
function fnTableStriped(_record: unknown, index: number): any {
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
}
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
@@ -478,7 +471,9 @@ onMounted(() => {
v-model:value="queryRangePicker"
allow-clear
bordered
value-format="YYYY-MM-DD"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
value-format="x"
style="width: 100%"
></a-range-picker>
</a-form-item>
@@ -551,15 +546,6 @@ onMounted(() => {
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.tableStripedText') }}</template>
<a-switch
v-model:checked="tableState.striped"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
@@ -601,7 +587,6 @@ onMounted(() => {
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:row-class-name="fnTableStriped"
:scroll="{ x: tableColumns.length * 120 }"
:pagination="tablePagination"
:row-selection="{
@@ -748,10 +733,6 @@ onMounted(() => {
</template>
<style lang="less" scoped>
.table :deep(.table-striped) td {
background-color: #fafafa;
}
.table :deep(.ant-pagination) {
padding: 0 24px;
}

View File

@@ -40,11 +40,11 @@ let rangePicker = reactive<RangePickerType>({
t('views.monitor.monitor.endTime'),
],
ranges: {
[t('views.monitor.monitor.today')]: [
[t('views.monitor.monitor.yesterday')]: [
dayjs().subtract(1, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'),
],
[t('views.monitor.monitor.yesterday')]: [dayjs().startOf('day'), dayjs()],
[t('views.monitor.monitor.today')]: [dayjs().startOf('day'), dayjs()],
[t('views.monitor.monitor.week')]: [
dayjs().startOf('week'),
dayjs().endOf('week'),
@@ -66,21 +66,21 @@ let rangePicker = reactive<RangePickerType>({
function fnGetList() {
let startTime = null;
let endTime = null;
const dateString = rangePicker.all;
if (dateString[0]) {
startTime = parseStrToDate(dateString[0], YYYY_MM_DD_HH_MM_SS);
endTime = parseStrToDate(dateString[1], YYYY_MM_DD_HH_MM_SS);
const dateNumber = rangePicker.all;
if (dateNumber[0]) {
startTime = dateNumber[0];
endTime = dateNumber[1];
} else {
const now = new Date();
now.setHours(0, 0, 0, 0);
startTime = now;
endTime = new Date();
startTime = `${now.getTime()}`;
endTime = `${new Date().getTime()}`;
}
getLoad({
type: 'all',
startTime: startTime.getTime(),
endTime: endTime.getTime(),
startTime: startTime,
endTime: endTime,
neType: '#',
neId: '#',
}).then(res => {
@@ -94,10 +94,7 @@ function fnGetList() {
});
// 设置初始时间段
const initRangePicker: [string, string] = [
parseDateToStr(startTime, YYYY_MM_DD_HH_MM_SS),
parseDateToStr(endTime, YYYY_MM_DD_HH_MM_SS),
];
const initRangePicker: [string, string] = [startTime, endTime];
rangePicker.all = initRangePicker;
rangePicker.load = initRangePicker;
rangePicker.cpu = initRangePicker;
@@ -164,7 +161,7 @@ function fnLoadChart(data: Record<string, any>[]) {
type: 'value',
name: `${t('views.monitor.monitor.resourceUsage')} ( % )`,
position: 'right',
alignTicks: false
alignTicks: false,
},
],
grid: { left: '5%', right: '5%', bottom: '20%' },
@@ -446,9 +443,9 @@ onMounted(() => {
v-model:value="rangePicker.all"
:allow-clear="false"
bordered
value-format="YYYY-MM-DD HH:mm:ss"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
show-time
value-format="x"
:placeholder="rangePicker.placeholder"
:ranges="rangePicker.ranges"
style="width: 100%"
@@ -480,9 +477,9 @@ onMounted(() => {
v-model:value="rangePicker.load"
:allow-clear="false"
bordered
value-format="YYYY-MM-DD HH:mm:ss"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
show-time
value-format="x"
:placeholder="rangePicker.placeholder"
style="width: 100%"
@change="(_:any, d:[string,string]) => fnGetQuery('load', d)"
@@ -510,9 +507,9 @@ onMounted(() => {
v-model:value="rangePicker.cpu"
:allow-clear="false"
bordered
value-format="YYYY-MM-DD HH:mm:ss"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
show-time
value-format="x"
:placeholder="rangePicker.placeholder"
style="width: 100%"
@change="(_:any, d:[string,string]) => fnGetQuery('cpu', d)"
@@ -538,9 +535,9 @@ onMounted(() => {
v-model:value="rangePicker.memory"
:allow-clear="false"
bordered
value-format="YYYY-MM-DD HH:mm:ss"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
show-time
value-format="x"
:placeholder="rangePicker.placeholder"
style="width: 100%"
@change="(_:any, d:[string,string]) => fnGetQuery('memory', d)"
@@ -568,9 +565,9 @@ onMounted(() => {
v-model:value="rangePicker.io"
:allow-clear="false"
bordered
value-format="YYYY-MM-DD HH:mm:ss"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
show-time
value-format="x"
:placeholder="rangePicker.placeholder"
style="width: 100%"
@change="fnGetQueryIO"
@@ -596,9 +593,9 @@ onMounted(() => {
v-model:value="rangePicker.network"
:allow-clear="false"
bordered
value-format="YYYY-MM-DD HH:mm:ss"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
show-time
value-format="x"
:placeholder="rangePicker.placeholder"
style="width: 100%"
@change="fnGetQueryNetwork"

View File

@@ -25,8 +25,6 @@ type TabeStateType = {
loading: boolean;
/**紧凑型 */
size: SizeType;
/**斑马纹 */
striped: boolean;
/**搜索栏 */
seached: boolean;
/**记录数据 */
@@ -37,7 +35,6 @@ type TabeStateType = {
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
striped: false,
seached: false,
data: [],
});
@@ -69,11 +66,11 @@ let tableColumns: ColumnsType = [
dataIndex: 'ipaddr',
align: 'center',
},
{
title: t('views.monitor.online.loginDes'),
dataIndex: 'loginLocation',
align: 'center',
},
// {
// title: t('views.monitor.online.loginDes'),
// dataIndex: 'loginLocation',
// align: 'center',
// },
{
title: t('views.monitor.online.os'),
dataIndex: 'os',
@@ -130,11 +127,6 @@ function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**表格斑马纹 */
function fnTableStriped(_record: unknown, index: number): any {
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
}
/**查询参数重置 */
function fnQueryReset() {
queryParams.ipaddr = '';
@@ -254,15 +246,6 @@ onMounted(() => {
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.zebra') }}</template>
<a-switch
v-model:checked="tableState.striped"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
@@ -304,7 +287,6 @@ onMounted(() => {
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:row-class-name="fnTableStriped"
:pagination="tablePagination"
:scroll="{ x: true }"
>
@@ -327,10 +309,6 @@ onMounted(() => {
</template>
<style lang="less" scoped>
.table :deep(.table-striped) td {
background-color: #fafafa;
}
.table :deep(.ant-pagination) {
padding: 0 24px;
}

View File

@@ -1,341 +0,0 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/lib/table';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { listSession, logoutSession } from '@/api/monitor/session';
import { diffValue, parseDateToStr } from '@/utils/date-utils';
const { t } = useI18n();
/**查询参数 */
let queryParams = reactive({
/**登录账号 */
accountId: '',
/**IP地址 */
ip: '',
/**排序字段 */
sortField: 'loginTime',
/**排序方式 */
sortOrder: 'desc',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
accountId: '',
ip: '',
sortField: 'loginTime',
sortOrder: 'desc',
pageNum: 1,
pageSize: 20,
});
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: object[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: true,
data: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('common.rowId'),
dataIndex: 'id',
align: 'center',
},
{
title: t('views.monitor.session.userName'),
dataIndex: 'accountId',
align: 'center',
},
{
title: t('views.monitor.session.ipaddr'),
dataIndex: 'host',
align: 'center',
},
{
title: t('views.monitor.session.loginTime'),
dataIndex: 'loginTime',
align: 'center',
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value);
},
},
{
title: t('views.monitor.session.shakeTime'),
dataIndex: 'shakeTime',
align: 'center',
customRender(opt) {
if (!opt.value) return '';
return diffValue(new Date(), opt.value, 'minute');
},
},
{
title: t('views.monitor.session.status'),
dataIndex: 'status',
align: 'center',
customRender(opt) {
if (opt.value === 'online') {
return t('views.monitor.session.online');
}
return t('views.monitor.session.offline');
},
},
{
title: t('common.operate'),
key: 'id',
align: 'center',
},
];
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/** 强退按钮操作 */
function fnForceLogout(row: Record<string, string>) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.monitor.session.logoutTip', { num: row.accountId }),
onOk() {
const hide = message.loading(t('common.loading'), 0);
logoutSession(row.id)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.monitor.session.logoutSuccess', {
num: row.accountId,
}),
duration: 3,
});
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
fnGetList();
});
},
});
}
/**查询网元列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if(pageNum){
queryParams.pageNum = pageNum;
}
listSession(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
tablePagination.total = res.total;
tableState.data = res.rows;
if (tablePagination.total <=(queryParams.pageNum - 1) * tablePagination.pageSize &&queryParams.pageNum !== 1) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
}
tableState.loading = false;
});
}
onMounted(() => {
// 获取列表数据
fnGetList();
});
</script>
<template>
<PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.monitor.session.userName')"
name="accountId "
>
<a-input
v-model:value="queryParams.accountId"
allow-clear
:placeholder="t('views.monitor.session.userNamePlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item :label="t('views.monitor.session.ipaddr')" name="ip">
<a-input
v-model:value="queryParams.ip"
allow-clear
:placeholder="t('views.monitor.session.ipaddrPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon>
<SearchOutlined />
</template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon>
<ClearOutlined />
</template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title></template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon>
<ReloadOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click" placement="bottomRight">
<a-button type="text">
<template #icon>
<ColumnHeightOutlined />
</template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
<a-menu-item key="middle">
{{ t('common.size.middle') }}
</a-menu-item>
<a-menu-item key="small">
{{ t('common.size.small') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: true }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'id'">
<a-button type="link" danger @click.prevent="fnForceLogout(record)">
<template #icon><LogoutOutlined /></template>
强退
</a-button>
</template>
</template>
</a-table>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -61,7 +61,7 @@ function fnGetList(refresh: boolean = false) {
res.data.length > 0
) {
// 根网管
let rootNodeInfo = { neName: undefined };
let rootNodeInfo = { neName: "OMC_001" };
const nodes = [];
const edges = [];
for (const item of res.data) {
@@ -70,7 +70,7 @@ function fnGetList(refresh: boolean = false) {
const nodeIndex = nodes.findIndex(v => v.id === item.neName);
if (nodeIndex === -1) {
if (item.neType === 'OMC') {
nodes.push({
nodes.unshift({
id: item.neName,
label: item.neName,
info: item,
@@ -120,7 +120,7 @@ function fnGetList(refresh: boolean = false) {
});
}
} else {
edges.push({
edges.unshift({
source: item.neName,
target: rootNodeInfo.neName,
label: `${item.neName}-${rootNodeInfo.neName}`,
@@ -140,6 +140,7 @@ function fnGetList(refresh: boolean = false) {
})
.then(hasNeList => {
if (!hasNeList) return;
console.log(graphG6Data)
if (refresh) {
// graphG6.value.get('canvas').set('localRefresh', true);
graphG6.value.destroy();

View File

@@ -230,6 +230,23 @@ function handleRanderGraph(
graphG6.value = graph;
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(function (entries) {
// 当元素大小发生变化时触发回调函数
entries.forEach(function (entry) {
if (!graphG6.value) {
return;
}
graphG6.value.changeSize(
entry.contentRect.width,
entry.contentRect.height - 30
);
graphG6.value.fitCenter();
});
});
// 监听元素大小变化
observer.observe(container);
return graph;
}
@@ -328,10 +345,13 @@ function fnGraphDataLoad(reload: boolean = false) {
} else {
handleRanderGraph(graphG6Dom.value, graphState.data);
}
fnGetState();
interval10s.value = setInterval(() => {
fnGetState(); // 获取网元状态
}, 10_000);
clearInterval(interval10s.value);
interval10s.value = null;
fnGetState().finally(() => {
interval10s.value = setInterval(() => {
fnGetState(); // 获取网元状态
}, 10_000);
});
});
}
@@ -473,6 +493,7 @@ onMounted(() => {
onBeforeUnmount(() => {
ws.close();
clearInterval(interval10s.value);
interval10s.value = null;
});
</script>

View File

@@ -8,7 +8,7 @@ import {
Menu,
Tooltip,
} from '@antv/g6';
import { ref } from 'vue';
import { onBeforeUnmount, ref } from 'vue';
import {
edgeCubicAnimateCircleMove,
edgeCubicAnimateLineDash,
@@ -29,7 +29,7 @@ export const graphEvent = ref<{
type: string;
target: HTMLElement | (IShapeBase & ICanvas);
item: Item | null;
}>();
} | null>(null);
/**图元素选择开始结束点 */
export const selectSourceTargetOptions = ref<Record<string, any>[]>([]);
@@ -592,6 +592,23 @@ export default function useGraph() {
graphG6.value = graph;
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(function (entries) {
// 当元素大小发生变化时触发回调函数
entries.forEach(function (entry) {
if (!graphG6.value) {
return;
}
graphG6.value.changeSize(
entry.contentRect.width,
entry.contentRect.height - 30
);
graphG6.value.fitCenter();
});
});
// 监听元素大小变化
observer.observe(container);
// 图元素选择开始结束点数据
fnSelectSourceTargetOptionsData();
@@ -605,6 +622,13 @@ export default function useGraph() {
graphMode.value = graphG6.value.getCurrentMode();
}
onBeforeUnmount(() => {
graphG6.value = null;
graphEvent.value = null;
selectSourceTargetOptions.value = [];
selectComboOptions.value = [];
});
return {
graphMode,
graphModeOptions,

View File

@@ -0,0 +1,507 @@
<script setup lang="ts">
import { reactive, onMounted, watch } from 'vue';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
const emit = defineEmits(['update:data']);
const props = defineProps({
/**表单数据 */
data: {
type: Object,
required: true,
},
/**根据网元显示配置项 */
ne: {
type: Object,
default: () => ({
amf: false,
upf: false,
ims: false,
mme: false,
}),
},
});
/**对话框对象信息状态类型 */
type StateType = {
/**表单数据 */
from: Record<string, any>;
/**根据网元显示配置项 */
hasNE: {
amf: boolean;
upf: boolean;
ims: boolean;
mme: boolean;
};
};
/**对话框对象信息状态 */
let state: StateType = reactive({
from: {
basic: {
plmnId: {
mcc: '001',
mnc: '01',
},
tac: '4388',
snssai: {
sst: '1',
sd: '000001',
},
dnn_data: 'internet',
dnn_ims: 'ims',
},
external: {
amfn2_ip: '192.168.8.120',
upfn3_ip: '192.168.8.190/24',
upfn3_gw: '192.168.1.1',
upfn6_ip: '192.168.8.191/24',
upfn6_gw: '192.168.1.1',
ue_pool: '10.2.1.0/24',
// 非指定属性
mmes1_ip: '192.168.8.220/20',
mmes10_ip: '172.16.5.221/24',
mmes11_ip: '172.16.5.220/24',
ims_sip_ip: '192.168.8.110',
upf_type: 'LightUPF',
upfn3_pci: '0000:00:00.0',
upfn3_mac: '00:00:00:00:00:00',
upfn6_pci: '0000:00:00.0',
upfn6_mac: '00:00:00:00:00:00',
},
sbi: {
omc_ip: '172.16.5.100',
ims_ip: '172.16.5.110',
amf_ip: '172.16.5.120',
ausf_ip: '172.16.5.130',
udm_ip: '172.16.5.140',
adb_ip: '0.0.0.0',
smf_ip: '172.16.5.150',
pcf_ip: '172.16.5.160',
nssf_ip: '172.16.5.170',
nrf_ip: '172.16.5.180',
upf_ip: '172.16.5.190',
lmf_ip: '172.16.5.200',
nef_ip: '172.16.5.210',
mme_ip: '172.16.5.220',
n3iwf_ip: '172.16.5.230',
},
},
hasNE: {
amf: false,
upf: false,
ims: false,
mme: false,
},
});
/**监听数据 */
watch(
() => props.data,
val => {
if (val) {
Object.assign(state.from, val);
}
},
{ deep: true }
);
watch(
() => props.ne,
val => {
if (val) {
Object.assign(state.hasNE, val);
}
},
{ deep: true }
);
watch(
() => state.from,
val => {
if (val) emit('update:data', val);
},
{ deep: true, immediate: true }
);
onMounted(() => {});
</script>
<template>
<a-form
name="para5GFileeFrom"
layout="horizontal"
:label-col="{ span: 8 }"
:label-wrap="true"
>
<a-row :gutter="16">
<a-col :lg="16" :md="16" :xs="24">
<a-divider orientation="left">Basic</a-divider>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="DNN_DATA" name="basic.dnn_data">
<a-input
v-model:value="state.from.basic.dnn_data"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> DNN </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-form-item label="MCC" name="basic.plmnId.mcc">
<a-input
v-model:value="state.from.basic.plmnId.mcc"
placeholder="1-65535"
></a-input>
</a-form-item>
<a-form-item label="SST" name="basic.snssai.sst">
<a-input-number
v-model:value="state.from.basic.snssai.sst"
:min="1"
:max="3"
placeholder="1-3"
style="width: 100%"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> 1-3 </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input-number>
</a-form-item>
<a-form-item label="TAC" name="basic.tac">
<a-input
v-model:value="state.from.basic.tac"
placeholder="1-65535"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="DNN_IMS" name="basic.dnn_ims">
<a-input
v-model:value="state.from.basic.dnn_ims"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
</a-input>
</a-form-item>
<a-form-item label="MNC" name="basic.plmnId.mnc">
<a-input
v-model:value="state.from.basic.plmnId.mnc"
placeholder="1-65535"
></a-input>
</a-form-item>
<a-form-item label="SD" name="basic.snssai.sd">
<a-input
v-model:value="state.from.basic.snssai.sd"
placeholder="1-65535"
></a-input>
</a-form-item>
</a-col>
</a-row>
</a-col>
<a-col :lg="8" :md="8" :xs="24">
<a-divider orientation="left">OMC</a-divider>
<a-row :gutter="16">
<a-col :lg="24" :md="24" :xs="24">
<a-form-item
label="EMS_IP"
name="sbi.omc_ip"
:required="true"
:validate-on-rule-change="false"
:validateTrigger="[]"
>
<a-input
v-model:value="state.from.sbi.omc_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
Network Elemment send data tu EMS IP
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
<template v-if="state.hasNE.amf">
<a-divider orientation="left">AMF</a-divider>
<a-row :gutter="16">
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="N2_IP" name="external.amfn2_ip">
<a-input
v-model:value="state.from.external.amfn2_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> AMF N2 </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
</template>
</a-col>
<a-col :lg="16" :md="16" :xs="24" v-if="state.hasNE.upf">
<a-divider orientation="left">UPF</a-divider>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="UPF_TYPE"
name="external.upf_type"
help="Install of Standard or Light"
>
<a-select
v-model:value="state.from.external.upf_type"
:placeholder="t('common.selectPlease')"
>
<a-select-option value="StandardUPF">
StandardUPF
</a-select-option>
<a-select-option value="LightUPF">LightUPF</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="UE_POOL" name="external.ue_pool">
<a-input
v-model:value="state.from.external.ue_pool"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> UE IP and maxk </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N3_IP" name="external.upfn3_ip">
<a-input
v-model:value="state.from.external.upfn3_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> netwrok ip </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-form-item label="N3_GW" name="external.upfn3_gw">
<a-input
v-model:value="state.from.external.upfn3_gw"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> geteway </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N3_PCI" name="external.upfn3_pci">
<a-input
v-model:value="state.from.external.upfn3_pci"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
use `ip a` show info, to inet ip
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-form-item label="N3_MAC" name="external.upfn3_mac">
<a-input
v-model:value="state.from.external.upfn3_mac"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
use `ip a` show info, to inet ip
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
<template v-if="state.from.external.upf_type === 'StandardUPF'">
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N6_IP" name="external.upfn6_ip">
<a-input
v-model:value="state.from.external.upfn6_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
</a-input>
</a-form-item>
<a-form-item label="N6_GW" name="external.upfn6_gw">
<a-input
v-model:value="state.from.external.upfn6_gw"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="N6_PCI" name="external.upfn6_pci">
<a-input
v-model:value="state.from.external.upfn6_pci"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
</a-input>
</a-form-item>
<a-form-item label="N6_MAC" name="external.upfn6_mac">
<a-input
v-model:value="state.from.external.upfn6_mac"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
</a-input>
</a-form-item>
</a-col>
</a-row>
</template>
</a-col>
<a-col :lg="8" :md="8" :xs="24">
<template v-if="state.hasNE.ims">
<a-divider orientation="left">IMS</a-divider>
<a-row :gutter="16">
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="SIP_IP" name="external.ims_sip_ip">
<a-input
v-model:value="state.from.external.ims_sip_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> IMS SIP </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
</template>
<template v-if="state.hasNE.mme">
<a-divider orientation="left">MME</a-divider>
<a-row :gutter="16">
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="S1_IP" name="external.mmes1_ip">
<a-input
v-model:value="state.from.external.mmes1_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
MME binded interface for S1-C or S1-MME communication
(S1AP), can be ethernet interface, virtual ethernet
interface, we don't advise wireless interfaces
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-form-item label="S10_IP" name="external.mmes10_ip">
<a-input
v-model:value="state.from.external.mmes10_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title> Interface </template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-form-item label="S11_IP" name="external.mmes11_ip">
<a-input
v-model:value="state.from.external.mmes11_ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
MME binded interface for S11-U communication (GTPV1-U)
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
</template>
</a-col>
</a-row>
</a-form>
</template>
<style lang="less" scoped></style>

View File

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

View File

@@ -78,7 +78,7 @@ type TabeStateType = {
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: true,
seached: false,
data: [],
});
@@ -135,7 +135,7 @@ let tableColumns: ColumnsType = [
width: 100,
},
{
title: t('views.ne.neHost.createTime'),
title: t('common.createTime'),
dataIndex: 'createTime',
align: 'left',
width: 150,
@@ -287,7 +287,7 @@ const modalStateFrom = Form.useForm(
{
required: true,
min: 1,
max: 128,
max: 3000,
message: t('views.ne.neHost.privateKeyPlease'),
},
],
@@ -414,7 +414,8 @@ function fnModalTest() {
const validateArr = ['title', 'addr', 'port', 'user'];
if (form.authMode === '0') {
validateArr.push('password');
} else {
}
if (form.authMode === '1') {
validateArr.push('privateKey');
}
modalStateFrom
@@ -710,6 +711,7 @@ onMounted(() => {
:min="10"
:max="65535"
:step="1"
:maxlength="5"
style="width: 100%"
></a-input-number>
</a-form-item>
@@ -726,7 +728,7 @@ onMounted(() => {
<a-input
v-model:value="modalState.from.user"
allow-clear
:maxlength="50"
:maxlength="32"
:placeholder="t('common.inputPlease')"
>
</a-input>
@@ -796,7 +798,7 @@ onMounted(() => {
</template>
<a-form-item
:label="t('views.ne.neHost.remark')"
:label="t('common.remark')"
name="remark"
:label-col="{ span: 3 }"
:label-wrap="true"

View File

@@ -71,7 +71,7 @@ type TabeStateType = {
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: true,
seached: false,
data: [],
});
@@ -103,7 +103,7 @@ let tableColumns: ColumnsType = [
width: 100,
},
{
title: t('views.ne.neHostCmd.createTime'),
title: t('common.createTime'),
dataIndex: 'createTime',
align: 'left',
width: 150,
@@ -572,7 +572,7 @@ onMounted(() => {
</a-form-item>
<a-form-item
:label="t('views.ne.neHostCmd.remark')"
:label="t('common.remark')"
name="remark"
:label-col="{ span: 3 }"
:label-wrap="true"
@@ -582,6 +582,7 @@ onMounted(() => {
:auto-size="{ minRows: 4, maxRows: 6 }"
:maxlength="450"
:show-count="true"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
</a-form>

View File

@@ -0,0 +1,337 @@
<script setup lang="ts">
import { reactive, toRaw, watch } from 'vue';
import { Form, Modal, Upload, message } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import { FileType } from 'ant-design-vue/lib/upload/interface';
import {
exportSet,
importFile,
listServerFile,
} from '@/api/configManage/neManage';
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
/**网元ID */
neId: {
type: String,
default: '',
},
neType: {
type: String,
default: '',
},
});
/**表格所需option */
const neManageOption = reactive({
importType: [
{ label: t('views.ne.neInfo.backConf.server'), value: 'server' },
{ label: t('views.ne.neInfo.backConf.local'), value: 'local' },
],
serverFileName: <any[]>[],
});
/**查询网元远程服务器备份文件 */
function typeChange(value: any) {
if (value === 'server') {
modalState.from.fileName = undefined;
listServerFile(modalState.from).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
neManageOption.serverFileName = [];
res.data.forEach((item: any) => {
neManageOption.serverFileName.push({
label: item.fileName,
value: item.fileName,
});
});
}
});
} else {
modalState.from.file = null;
}
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
visibleByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: {
neType: string;
neId: string;
importType: 'local' | 'server';
file: File | null;
fileName: string | undefined;
};
/**确定按钮 loading */
confirmLoading: boolean;
/**上传文件 */
uploadFiles: any[];
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
visibleByEdit: false,
title: '配置文件导入',
from: {
neType: '',
neId: '',
importType: 'local',
file: null,
fileName: undefined,
},
confirmLoading: false,
uploadFiles: [],
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
file: [
{
required: true,
message: t('views.ne.neInfo.backConf.filePlease'),
},
],
fileName: [
{
required: true,
message: t('views.ne.neInfo.backConf.fileNamePlease'),
},
],
})
);
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
if (modalState.confirmLoading) return;
const from = toRaw(modalState.from);
let validateName = ['importType'];
if (from.importType === 'local') {
validateName.push('file');
} else {
validateName.push('fileName');
}
modalStateFrom
.validate(validateName)
.then(e => {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
importFile(from)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
// 返回无引用信息
emit('ok', JSON.parse(JSON.stringify(from)));
fnModalCancel();
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
})
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
});
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.visibleByEdit = false;
modalState.confirmLoading = false;
modalStateFrom.resetFields();
modalState.uploadFiles = [];
emit('cancel');
emit('update:visible', false);
}
/**表单上传前检查或转换压缩 */
function fnBeforeUploadFile(file: FileType) {
if (modalState.confirmLoading) return false;
if (!file.name.endsWith('.zip')) {
const msg = `${t('components.UploadModal.onlyAllow')} .zip`;
message.error(msg, 3);
return Upload.LIST_IGNORE;
}
const isLt3M = file.size / 1024 / 1024 < 100;
if (!isLt3M) {
const msg = `${t('components.UploadModal.allowFilter')} 100MB`;
message.error(msg, 3);
return Upload.LIST_IGNORE;
}
return true;
}
/**表单上传文件 */
function fnUploadFile(up: UploadRequestOption) {
// 改为完成状态
const file = modalState.uploadFiles[0];
file.percent = 100;
file.status = 'done';
// 预置到表单
modalState.from.file = up.file as File;
}
/**监听是否显示,初始数据 */
watch(
() => props.visible,
val => {
if (val) {
if (props.neType && props.neId) {
modalState.from.neType = props.neType;
modalState.from.neId = props.neId;
modalState.title = t('views.ne.neInfo.backConf.title');
modalState.visibleByEdit = true;
}
}
}
);
/**
* 网元导出配置
* @param row 网元编号ID
*/
function fnExportConf(neType: string, neId: string) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neInfo.backConf.exportTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
exportSet({ neType, neId })
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('views.ne.neInfo.backConf.exportMsg'), 3);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
}
// 给组件设置属性 ref="xxxBackConf"
// setup内使用 const xxxBackConf = ref();
defineExpose({
/**导出文件 */
exportConf: fnExportConf,
});
</script>
<template>
<a-modal
width="800px"
:keyboard="false"
:mask-closable="false"
:visible="modalState.visibleByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form name="modalStateFrom" layout="horizontal" :label-col="{ span: 6 }">
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.common.neType')"
name="neType"
v-bind="modalStateFrom.validateInfos.neType"
>
{{ modalState.from.neType }}
</a-form-item>
<a-form-item
:label="t('views.ne.neInfo.backConf.importType')"
name="importType"
>
<a-select
v-model:value="modalState.from.importType"
default-value="server"
:options="neManageOption.importType"
@change="typeChange"
>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.common.neId')"
name="neId"
v-bind="modalStateFrom.validateInfos.neId"
>
{{ modalState.from.neId }}
</a-form-item>
<a-form-item
:label="t('views.ne.neInfo.backConf.server')"
name="fileName"
v-bind="modalStateFrom.validateInfos.fileName"
v-if="modalState.from.importType === 'server'"
>
<a-select
v-model:value="modalState.from.fileName"
:options="neManageOption.serverFileName"
:placeholder="t('common.selectPlease')"
>
</a-select>
</a-form-item>
<a-form-item
:label="t('views.ne.neInfo.backConf.local')"
name="file"
v-bind="modalStateFrom.validateInfos.file"
v-if="modalState.from.importType === 'local'"
>
<a-upload
name="file"
v-model:file-list="modalState.uploadFiles"
accept=".zip"
list-type="text"
:max-count="1"
:show-upload-list="{
showPreviewIcon: false,
showRemoveIcon: false,
showDownloadIcon: false,
}"
:before-upload="fnBeforeUploadFile"
:custom-request="fnUploadFile"
:disabled="modalState.confirmLoading"
>
<a-button type="primary">
<template #icon>
<UploadOutlined />
</template>
{{ t('views.ne.neInfo.backConf.localUpload') }}
</a-button>
</a-upload>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal>
</template>
<style lang="less" scoped></style>

View File

@@ -1,12 +1,13 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue';
import { message, Form } from 'ant-design-vue/lib';
import { message, Form, Modal } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useNeInfoStore from '@/store/modules/neinfo';
import { getNeInfo, addNeInfo, updateNeInfo } from '@/api/ne/neInfo';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
import { testNeHost } from '@/api/ne/neHost';
import { regExpIPv4, regExpIPv6 } from '@/utils/regular-utils';
import { getNeInfo, addNeInfo, updateNeInfo } from '@/api/ne/neInfo';
import { neHostAuthorizedRSA, testNeHost } from '@/api/ne/neHost';
import useDictStore from '@/store/modules/dict';
const { getDict } = useDictStore();
const { t } = useI18n();
@@ -37,10 +38,10 @@ let dict: {
});
/**
* 对话框弹出测试连接
* 测试主机连接
*/
function fnModalTest(row: Record<string, any>) {
if (modalState.confirmLoading) return;
function fnHostTest(row: Record<string, any>) {
if (modalState.confirmLoading || !row.addr) return;
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
testNeHost(row)
@@ -63,6 +64,27 @@ function fnModalTest(row: Record<string, any>) {
});
}
/**测试主机连接-免密直连 */
function fnHostAuthorized(row: Record<string, any>) {
if (modalState.confirmLoading) return;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neHost.authRSATip'),
onOk: () => {
modalState.confirmLoading = true;
neHostAuthorizedRSA(row).then(res => {
modalState.confirmLoading = false;
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
} else {
message.error(t('common.operateErr'), 3);
}
});
},
});
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
@@ -82,16 +104,17 @@ let modalState: ModalStateType = reactive({
from: {
id: undefined,
neId: '001',
neType: 'OMC',
neType: 'AMF',
neName: '',
ip: '',
port: 3030,
port: 33030,
pvFlag: 'PNF',
rmUid: '4400HX1OMC001',
rmUid: '4400HXAMF001',
neAddress: '',
dn: '',
vendorName: '',
province: '',
remark: '',
// 主机
hosts: [
{
@@ -132,36 +155,58 @@ const modalStateFrom = Form.useForm(
neType: [
{
required: true,
message: '请输入网元类型',
message: t('views.ne.common.neTypePlease'),
},
],
neId: [
{
required: true,
message: '请输入网元标识',
message: t('views.ne.common.neIdPlease'),
},
],
rmUid: [
{
required: true,
message: '请输入资源唯一标识',
message: t('views.ne.common.rmUidPlease'),
},
],
ip: [
{
required: true,
message: '请输入网元IP地址',
validator: modalStateFromEqualIPV4AndIPV6,
},
],
neName: [
{
required: true,
message: '请输入网元名称',
message: t('views.ne.common.neNamePlease'),
},
],
})
);
/**表单验证IP地址是否有效 */
function modalStateFromEqualIPV4AndIPV6(
rule: Record<string, any>,
value: string,
callback: (error?: string) => void
) {
if (!value) {
return Promise.reject(t('views.ne.common.ipAddrPlease'));
}
if (value.indexOf('.') === -1 && value.indexOf(':') === -1) {
return Promise.reject(t('valid.ipPlease'));
}
if (value.indexOf('.') !== -1 && !regExpIPv4.test(value)) {
return Promise.reject(t('valid.ipv4Reg'));
}
if (value.indexOf(':') !== -1 && !regExpIPv6.test(value)) {
return Promise.reject(t('valid.ipv6Reg'));
}
return Promise.resolve();
}
/**
* 对话框弹出显示为 新增或者修改
* @param editId 网元id, 不传为新增
@@ -169,7 +214,7 @@ const modalStateFrom = Form.useForm(
function fnModalVisibleByEdit(editId: string) {
if (!editId) {
modalStateFrom.resetFields();
modalState.title = t('views.configManage.neManage.addNe');
modalState.title = t('views.ne.neInfo.addTitle');
modalState.visibleByEdit = true;
} else {
if (modalState.confirmLoading) return;
@@ -179,8 +224,8 @@ function fnModalVisibleByEdit(editId: string) {
modalState.confirmLoading = false;
hide();
if (res.code === RESULT_CODE_SUCCESS) {
modalState.from = Object.assign(modalState.from, res.data);
modalState.title = '编辑网元信息';
Object.assign(modalState.from, res.data);
modalState.title = t('views.ne.neInfo.editTitle');
modalState.visibleByEdit = true;
} else {
message.error(t('common.getInfoFail'), 2);
@@ -204,17 +249,14 @@ function fnModalOk() {
result
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: '操作成功',
duration: 3,
});
message.success(t('common.operateOk'), 3);
// 刷新缓存的网元信息
useNeInfoStore().fnRefreshNelist();
emit('ok');
fnModalCancel();
} else {
message.error({
content: `${t('views.configManage.neManage.operFail')}`,
content: `${res.msg}`,
duration: 3,
});
}
@@ -235,8 +277,10 @@ function fnModalOk() {
*/
function fnModalCancel() {
modalState.visibleByEdit = false;
modalState.confirmLoading = false;
modalStateFrom.resetFields();
emit('cancel');
emit('update:visible', false);
}
/**
@@ -325,7 +369,7 @@ onMounted(() => {
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.configManage.neManage.neType')"
:label="t('views.ne.common.neType')"
name="neType"
v-bind="modalStateFrom.validateInfos.neType"
>
@@ -342,7 +386,7 @@ onMounted(() => {
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.configManage.neManage.neTypeTip') }}
{{ t('views.ne.common.neTypeTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
@@ -353,7 +397,7 @@ onMounted(() => {
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.configManage.neManage.pvflag')"
:label="t('views.ne.neInfo.pvflag')"
name="pvFlag"
v-bind="modalStateFrom.validateInfos.pvFlag"
>
@@ -361,10 +405,10 @@ onMounted(() => {
v-model:value="modalState.from.pvFlag"
default-value="PNF"
>
<a-select-opt-group :label="t('views.configManage.neManage.pnf')">
<a-select-opt-group :label="t('views.ne.neInfo.pnf')">
<a-select-option value="PNF">PNF</a-select-option>
</a-select-opt-group>
<a-select-opt-group :label="t('views.configManage.neManage.vnf')">
<a-select-opt-group :label="t('views.ne.neInfo.vnf')">
<a-select-option value="VNF">VNF</a-select-option>
</a-select-opt-group>
</a-select>
@@ -375,7 +419,7 @@ onMounted(() => {
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.configManage.neManage.neId')"
:label="t('views.ne.common.neId')"
name="neId"
v-bind="modalStateFrom.validateInfos.neId"
>
@@ -384,12 +428,21 @@ onMounted(() => {
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="32"
></a-input>
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.ne.common.neIdTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.configManage.neManage.neName')"
:label="t('views.ne.common.neName')"
name="neName"
v-bind="modalStateFrom.validateInfos.neName"
>
@@ -407,7 +460,7 @@ onMounted(() => {
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.configManage.neManage.ip')"
:label="t('views.ne.common.ipAddr')"
name="ip"
v-bind="modalStateFrom.validateInfos.ip"
>
@@ -422,7 +475,7 @@ onMounted(() => {
<a-tooltip placement="topLeft">
<template #title>
<div>
{{ t('views.ne.neInfo.ipAddr') }}
{{ t('views.ne.common.ipAddrTip') }}
</div>
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
@@ -433,7 +486,7 @@ onMounted(() => {
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.configManage.neManage.port')"
:label="t('views.ne.common.port')"
name="port"
v-bind="modalStateFrom.validateInfos.port"
>
@@ -442,12 +495,13 @@ onMounted(() => {
style="width: 100%"
:min="1"
:max="65535"
:maxlength="5"
placeholder="<=65535"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
<div>{{ t('views.configManage.neManage.portTip') }}</div>
<div>{{ t('views.ne.common.portTip') }}</div>
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
@@ -458,7 +512,7 @@ onMounted(() => {
</a-row>
<a-form-item
:label="t('views.configManage.neManage.uid')"
:label="t('views.ne.common.rmUid')"
name="rmUid"
v-bind="modalStateFrom.validateInfos.rmUid"
:label-col="{ span: 3 }"
@@ -474,7 +528,7 @@ onMounted(() => {
<a-tooltip placement="topLeft">
<template #title>
<div>
{{ t('views.ne.neInfo.rmUID') }}
{{ t('views.ne.common.rmUidTip') }}
</div>
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
@@ -485,10 +539,7 @@ onMounted(() => {
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.configManage.neManage.mac')"
name="neAddress"
>
<a-form-item :label="t('views.ne.neInfo.neAddress')" name="neAddress">
<a-input
v-model:value="modalState.from.neAddress"
allow-clear
@@ -498,7 +549,7 @@ onMounted(() => {
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
<div>{{ t('views.configManage.neManage.macTip') }}</div>
<div>{{ t('views.ne.neInfo.neAddressTip') }}</div>
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
@@ -507,7 +558,7 @@ onMounted(() => {
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.configManage.neManage.dn')" name="dn">
<a-form-item :label="t('views.ne.neInfo.dn')" name="dn">
<a-input
v-model:value="modalState.from.dn"
allow-clear
@@ -521,7 +572,7 @@ onMounted(() => {
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.configManage.neManage.vendorName')"
:label="t('views.ne.neInfo.vendorName')"
name="vendorName"
>
<a-input
@@ -534,10 +585,7 @@ onMounted(() => {
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.configManage.neManage.province')"
name="province"
>
<a-form-item :label="t('views.ne.neInfo.province')" name="province">
<a-input
v-model:value="modalState.from.province"
allow-clear
@@ -548,11 +596,24 @@ onMounted(() => {
</a-col>
</a-row>
<a-form-item
:label="t('common.remark')"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-textarea
v-model:value="modalState.from.remark"
:auto-size="{ minRows: 1, maxRows: 6 }"
:maxlength="450"
:show-count="true"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
<!-- 主机连接配置 -->
<a-divider orientation="left">
{{ t('views.ne.neInfo.hostConfig') }}
</a-divider>
<!-- 主机连接配置 -->
<a-collapse class="collapse" ghost>
<a-collapse-panel
v-for="host in modalState.from.hosts"
@@ -578,6 +639,7 @@ onMounted(() => {
:min="10"
:max="65535"
:step="1"
:maxlength="5"
style="width: 100%"
></a-input-number>
</a-form-item>
@@ -590,7 +652,7 @@ onMounted(() => {
<a-input
v-model:value="host.user"
allow-clear
:maxlength="50"
:maxlength="32"
:placeholder="t('common.inputPlease')"
>
</a-input>
@@ -653,7 +715,7 @@ onMounted(() => {
</template>
<a-form-item
:label="t('views.ne.neHost.remark')"
:label="t('common.remark')"
:label-col="{ span: 3 }"
:label-wrap="true"
>
@@ -675,11 +737,20 @@ onMounted(() => {
<a-button
type="primary"
shape="round"
@click="fnModalTest(host)"
@click="fnHostTest(host)"
:loading="modalState.confirmLoading"
>
<template #icon><LinkOutlined /></template>
</a-button>
<a-button
type="link"
@click="fnHostAuthorized(host)"
:loading="modalState.confirmLoading"
v-if="host.hostType === 'ssh' && host.authMode !== '2'"
>
{{ t('views.ne.neHost.authRSA') }}
</a-button>
</a-form-item>
</a-collapse-panel>
</a-collapse>

View File

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

View File

@@ -1,13 +1,15 @@
import { restartNf, startNf, stopNf } from '@/api/configManage/neManage';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { Modal, message } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { useRouter } from 'vue-router';
import { updateNeConfigReload } from '@/api/configManage/configParam';
import { serviceNeAction } from '@/api/ne/neInfo';
import useLockedStore from '@/store/modules/locked';
export default function useNeOptions() {
const router = useRouter();
const { t } = useI18n();
const lockedStore = useLockedStore();
/**
* 网元启动
@@ -16,30 +18,24 @@ export default function useNeOptions() {
function fnNeStart(row: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.configManage.neManage.totalSure', {
msg: row.neName,
oper: t('views.configManage.neManage.start'),
}),
content: t('views.ne.common.startTip'),
onOk() {
const key = 'startNf';
message.loading({ content: t('common.loading'), key });
startNf(row).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.msgSuccess', {
msg: t('views.configManage.neManage.start'),
}),
key,
duration: 2,
});
} else {
message.error({
content: `${res.msg}`,
key: key,
duration: 2,
});
}
});
const hide = message.loading(t('common.loading'), 0);
serviceNeAction({
neType: row.neType,
neId: row.neId,
action: 'start',
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
}
@@ -51,30 +47,36 @@ export default function useNeOptions() {
function fnNeRestart(row: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.configManage.neManage.totalSure', {
msg: row.neName,
oper: t('views.configManage.neManage.restart'),
}),
content: t('views.ne.common.restartTip'),
onOk() {
const key = 'restartNf';
message.loading({ content: t('common.loading'), key });
restartNf(row).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.msgSuccess', {
msg: t('views.configManage.neManage.restart'),
}),
key,
duration: 3,
});
} else {
message.error({
content: `${res.msg}`,
key: key,
duration: 3,
});
}
});
const hide = message.loading(t('common.loading'), 0);
serviceNeAction({
neType: row.neType,
neId: row.neId,
action: 'restart',
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// OMC自升级
if (row.neType.toUpperCase() === 'OMC') {
if (res.code === RESULT_CODE_SUCCESS) {
lockedStore.fnLock('reload');
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
return;
}
message.success(t('common.operateOk'), 3);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
}
@@ -86,30 +88,24 @@ export default function useNeOptions() {
function fnNeStop(row: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.configManage.neManage.totalSure', {
msg: row.neName,
oper: t('views.configManage.neManage.stop'),
}),
content: t('views.ne.common.stopTip'),
onOk() {
const key = 'restartNf';
message.loading({ content: t('common.loading'), key });
stopNf(row).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.msgSuccess', {
msg: t('views.configManage.neManage.stop'),
}),
key: key,
duration: 3,
});
} else {
message.error({
content: `${res.msg}`,
key: key,
duration: 3,
});
}
});
const hide = message.loading(t('common.loading'), 0);
serviceNeAction({
neType: row.neType,
neId: row.neId,
action: 'stop',
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
}
@@ -121,30 +117,20 @@ export default function useNeOptions() {
function fnNeReload(row: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.configManage.neManage.totalSure', {
msg: row.neName,
oper: t('views.configManage.neManage.reload'),
}),
content: t('views.ne.common.reloadTip'),
onOk() {
const key = 'stopNf';
message.loading({ content: t('common.loading'), key });
updateNeConfigReload(row.neType, row.neId).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.msgSuccess', {
msg: t('views.configManage.neManage.reload'),
}),
key,
duration: 2,
});
} else {
message.error({
content: `${res.msg}`,
key: key,
duration: 2,
});
}
});
const hide = message.loading(t('common.loading'), 0);
updateNeConfigReload(row.neType, row.neId)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
}

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw } from 'vue';
import { reactive, onMounted, toRaw, defineAsyncComponent, ref } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
@@ -7,7 +7,6 @@ import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/lib/table';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import EditModal from './components/EditModal.vue';
import useNeInfoStore from '@/store/modules/neinfo';
import { listNeInfo, delNeInfo } from '@/api/ne/neInfo';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
@@ -17,6 +16,18 @@ const { getDict } = useDictStore();
const { t } = useI18n();
const { fnNeStart, fnNeRestart, fnNeStop, fnNeReload, fnNeLogFile } =
useNeOptions();
// 异步加载组件
const EditModal = defineAsyncComponent(
() => import('./components/EditModal.vue')
);
const OAMModal = defineAsyncComponent(
() => import('./components/OAMModal.vue')
);
// 配置备份文件导入
const BackConfModal = defineAsyncComponent(
() => import('./components/BackConfModal.vue')
);
const backConf = ref(); // 引用句柄,取导出函数
/**字典数据 */
let dict: {
@@ -56,8 +67,6 @@ type TabeStateType = {
loading: boolean;
/**紧凑型 */
size: SizeType;
/**斑马纹 */
striped: boolean;
/**搜索栏 */
seached: boolean;
/**记录数据 */
@@ -70,8 +79,7 @@ type TabeStateType = {
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
striped: false,
seached: true,
seached: false,
data: [],
selectedRowKeys: [],
});
@@ -79,37 +87,37 @@ let tableState: TabeStateType = reactive({
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('views.configManage.neManage.neType'),
title: t('views.ne.common.neType'),
dataIndex: 'neType',
align: 'left',
width: 100,
},
{
title: t('views.configManage.neManage.neId'),
title: t('views.ne.common.neId'),
dataIndex: 'neId',
align: 'left',
width: 100,
},
{
title: t('views.configManage.neManage.uid'),
title: t('views.ne.common.rmUid'),
dataIndex: 'rmUid',
align: 'left',
width: 150,
},
{
title: t('views.configManage.neManage.neName'),
title: t('views.ne.common.neName'),
dataIndex: 'neName',
align: 'left',
width: 150,
},
{
title: t('views.configManage.neManage.ip'),
title: t('views.ne.common.ipAddr'),
dataIndex: 'ip',
align: 'left',
width: 150,
},
{
title: t('views.configManage.neManage.port'),
title: t('views.ne.common.port'),
dataIndex: 'port',
align: 'left',
width: 100,
@@ -161,11 +169,6 @@ function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**表格斑马纹 */
function fnTableStriped(_record: unknown, index: number): any {
return tableState.striped && index % 2 === 1 ? 'table-striped' : undefined;
}
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
@@ -173,18 +176,29 @@ function fnTableSelectedRowKeys(keys: (string | number)[]) {
/**对话框对象信息状态类型 */
type ModalStateType = {
/**配置备份框是否显示 */
visibleByBackConf: boolean;
/**OAM文件配置框是否显示 */
visibleByOAM: boolean;
/**新增框或修改框是否显示 */
visibleByEdit: boolean;
/**新增框或修改框ID */
editId: string;
/**OAM框网元类型ID */
neId: string;
neType: string;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
visibleByBackConf: false,
visibleByOAM: false,
visibleByEdit: false,
editId: '',
neId: '',
neType: '',
confirmLoading: false,
});
@@ -216,6 +230,8 @@ function fnModalEditOk() {
function fnModalEditCancel() {
modalState.editId = '';
modalState.visibleByEdit = false;
modalState.visibleByOAM = false;
modalState.visibleByBackConf = false;
}
/**
@@ -224,36 +240,21 @@ function fnModalEditCancel() {
*/
function fnRecordDelete(id: string) {
if (!id || modalState.confirmLoading) return;
let msg = '';
let msg = t('views.ne.neInfo.delTip');
if (id === '0') {
const neInfo: any = tableState.data.find(
(item: any) => item.id === tableState.selectedRowKeys[0]
);
if (neInfo) {
msg = neInfo.neName;
}
msg = `${msg}... ${tableState.selectedRowKeys.length}`;
id = tableState.selectedRowKeys.join(',');
} else {
const neInfo: any = tableState.data.find((item: any) => item.id === id);
if (neInfo) {
msg = neInfo.neName;
}
msg = `${msg} ...${tableState.selectedRowKeys.length}`;
}
Modal.confirm({
title: t('common.tipTitle'),
content: `确认删除网元名称为【${msg}】的数据项?`,
content: msg,
onOk() {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
delNeInfo(id)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: `操作成功`,
duration: 3,
});
message.success(t('common.operateOk'), 3);
fnGetList(1);
} else {
message.error({
@@ -293,6 +294,19 @@ function fnRecordMore(type: string | number, row: Record<string, any>) {
case 'log':
fnNeLogFile(row);
break;
case 'oam':
modalState.neId = row.neId;
modalState.neType = row.neType;
modalState.visibleByOAM = !modalState.visibleByOAM;
break;
case 'backConfExport':
backConf.value.exportConf(row.neType, row.neId);
break;
case 'backConfImport':
modalState.neId = row.neId;
modalState.neType = row.neType;
modalState.visibleByBackConf = !modalState.visibleByBackConf;
break;
default:
console.warn(type);
break;
@@ -402,10 +416,7 @@ onMounted(() => {
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.configManage.neManage.neType')"
name="neType "
>
<a-form-item :label="t('views.ne.common.neType')" name="neType ">
<a-auto-complete
v-model:value="queryParams.neType"
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
@@ -465,15 +476,6 @@ onMounted(() => {
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.tableStripedText') }}</template>
<a-switch
v-model:checked="tableState.striped"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
@@ -516,8 +518,7 @@ onMounted(() => {
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:row-class-name="fnTableStriped"
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
:scroll="{ x: tableColumns.length * 150 }"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',
@@ -542,7 +543,7 @@ onMounted(() => {
</a-tooltip>
<a-tooltip>
<template #title>
{{ t('views.configManage.neManage.restart') }}
{{ t('views.ne.common.restart') }}
</template>
<a-button
type="link"
@@ -551,23 +552,9 @@ onMounted(() => {
<template #icon><UndoOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>
{{ t('views.configManage.neManage.stop') }}
</template>
<a-button
type="link"
@click.prevent="fnRecordMore('stop', record)"
>
<template #icon><CloseSquareOutlined /> </template>
</a-button>
</a-tooltip>
<a-tooltip placement="left">
<template #title>{{ t('common.moreText') }}</template>
<a-dropdown
placement="bottomRight"
:trigger="['hover', 'click']"
>
<a-dropdown placement="bottomRight" trigger="click">
<a-button type="link">
<template #icon><EllipsisOutlined /> </template>
</a-button>
@@ -575,11 +562,15 @@ onMounted(() => {
<a-menu @click="({ key }:any) => fnRecordMore(key, record)">
<a-menu-item key="log">
<FileTextOutlined />
{{ t('views.configManage.neManage.log') }}
{{ t('views.ne.common.log') }}
</a-menu-item>
<a-menu-item key="start">
<ThunderboltOutlined />
{{ t('views.configManage.neManage.start') }}
{{ t('views.ne.common.start') }}
</a-menu-item>
<a-menu-item key="stop">
<CloseSquareOutlined />
{{ t('views.ne.common.stop') }}
</a-menu-item>
<a-menu-item
key="reload"
@@ -588,12 +579,25 @@ onMounted(() => {
"
>
<SyncOutlined />
{{ t('views.configManage.neManage.reload') }}
{{ t('views.ne.common.reload') }}
</a-menu-item>
<a-menu-item key="delete">
<DeleteOutlined />
{{ t('common.deleteText') }}
</a-menu-item>
<a-menu-item key="oam">
<FileTextOutlined />
{{ t('views.ne.common.oam') }}
</a-menu-item>
<!-- 配置备份 -->
<a-menu-item key="backConfExport">
<ExportOutlined />
{{ t('views.ne.neInfo.backConf.export') }}
</a-menu-item>
<a-menu-item key="backConfImport">
<ImportOutlined />
{{ t('views.ne.neInfo.backConf.import') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
@@ -602,117 +606,131 @@ onMounted(() => {
</template>
</template>
<template #expandedRowRender="{ record }">
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
<a-divider orientation="left">
{{ t('views.ne.neInfo.info') }}
</a-divider>
<div>
<span>{{ t('views.ne.neInfo.serviceState') }}</span>
<a-tag
:color="record.serverState.online ? 'processing' : 'error'"
>
{{
record.serverState.online
? t('views.ne.neInfo.normalcy')
: t('views.ne.neInfo.exceptions')
}}
</a-tag>
</div>
<div>
<span>{{ t('views.ne.neInfo.version') }}</span>
<span>{{ record.serverState.version }}</span>
</div>
<div>
<span>{{ t('views.ne.neInfo.serialNum') }}</span>
<span>{{ record.serverState.sn }}</span>
</div>
<div>
<span>{{ t('views.ne.neInfo.expiryDate') }}</span>
<span>{{ record.serverState.expire }}</span>
</div>
</div>
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
<a-divider orientation="left">
{{ t('views.ne.neInfo.resourceInfo') }}
</a-divider>
<div>
<span>{{ t('views.ne.neInfo.neCpu') }}</span>
<a-progress
status="normal"
:stroke-color="
record.resoures.nfCpuUsage < 30
? '#52c41a'
: record.resoures.nfCpuUsage > 70
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.nfCpuUsage"
/>
</div>
<div>
<span>{{ t('views.ne.neInfo.sysCpu') }}</span>
<a-progress
status="normal"
:stroke-color="
record.resoures.sysCpuUsage < 30
? '#52c41a'
: record.resoures.sysCpuUsage > 70
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.sysCpuUsage"
/>
</div>
<div>
<span>{{ t('views.ne.neInfo.sysMem') }}</span>
<a-progress
status="normal"
:stroke-color="
record.resoures.sysMemUsage < 30
? '#52c41a'
: record.resoures.sysMemUsage > 70
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.sysMemUsage"
/>
</div>
<div>
<span>{{ t('views.ne.neInfo.sysDisk') }}</span>
<a-progress
status="normal"
:stroke-color="
record.resoures.sysDiskUsage < 30
? '#52c41a'
: record.resoures.sysDiskUsage > 70
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.sysDiskUsage"
/>
</div>
</div>
<a-row :gutter="16">
<a-col :offset="2" :lg="8" :md="8" :xs="8">
<a-divider orientation="left">
{{ t('views.ne.neInfo.info') }}
</a-divider>
<div>
<span>{{ t('views.ne.neInfo.serviceState') }}</span>
<a-tag
:color="record.serverState.online ? 'processing' : 'error'"
>
{{
record.serverState.online
? t('views.ne.common.normalcy')
: t('views.ne.common.exceptions')
}}
</a-tag>
</div>
<div>
<span>{{ t('views.ne.neVersion.version') }}</span>
<span>{{ record.serverState.version }}</span>
</div>
<div>
<span>{{ t('views.ne.common.serialNum') }}</span>
<span>{{ record.serverState.sn }}</span>
</div>
<div>
<span>{{ t('views.ne.common.expiryDate') }}</span>
<span>{{ record.serverState.expire }}</span>
</div>
</a-col>
<a-col :offset="2" :lg="8" :md="8" :xs="8">
<a-divider orientation="left">
{{ t('views.ne.neInfo.resourceInfo') }}
</a-divider>
<div>
<span>{{ t('views.ne.neInfo.neCpu') }}</span>
<a-progress
status="normal"
:stroke-color="
record.resoures.nfCpuUsage < 30
? '#52c41a'
: record.resoures.nfCpuUsage > 70
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.nfCpuUsage"
/>
</div>
<div>
<span>{{ t('views.ne.neInfo.sysCpu') }}</span>
<a-progress
status="normal"
:stroke-color="
record.resoures.sysCpuUsage < 30
? '#52c41a'
: record.resoures.sysCpuUsage > 70
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.sysCpuUsage"
/>
</div>
<div>
<span>{{ t('views.ne.neInfo.sysMem') }}</span>
<a-progress
status="normal"
:stroke-color="
record.resoures.sysMemUsage < 30
? '#52c41a'
: record.resoures.sysMemUsage > 70
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.sysMemUsage"
/>
</div>
<div>
<span>{{ t('views.ne.neInfo.sysDisk') }}</span>
<a-progress
status="normal"
:stroke-color="
record.resoures.sysDiskUsage < 30
? '#52c41a'
: record.resoures.sysDiskUsage > 70
? '#ff4d4f'
: '#1890ff'
"
:percent="record.resoures.sysDiskUsage"
/>
</div>
</a-col>
</a-row>
</template>
</a-table>
</a-card>
<!-- 新增框或修改框 -->
<EditModal
:visible="modalState.visibleByEdit"
v-model:visible="modalState.visibleByEdit"
:edit-id="modalState.editId"
@ok="fnModalEditOk"
@cancel="fnModalEditCancel"
></EditModal>
<!-- OAM编辑框 -->
<OAMModal
v-model:visible="modalState.visibleByOAM"
:ne-id="modalState.neId"
:ne-type="modalState.neType"
@cancel="fnModalEditCancel"
></OAMModal>
<!-- 配置文件备份框 -->
<BackConfModal
ref="backConf"
v-model:visible="modalState.visibleByBackConf"
:ne-id="modalState.neId"
:ne-type="modalState.neType"
@cancel="fnModalEditCancel"
></BackConfModal>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.table-striped) td {
background-color: #fafafa;
}
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>
./components/useNeOptions

View File

@@ -0,0 +1,393 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue';
import { Form, Modal, Upload, message } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import {
changeNeLicense,
getNeLicense,
getNeLicenseByTypeAndID,
} from '@/api/ne/neLicense';
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import { FileType } from 'ant-design-vue/lib/upload/interface';
import { uploadFile } from '@/api/tool/file';
import { useClipboard } from '@vueuse/core';
import saveAs from 'file-saver';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
/**记录ID 优先级高于neId */
editId: {
type: String,
default: '',
},
/**网元ID */
neId: {
type: String,
default: '',
},
neType: {
type: String,
default: '',
},
/**是否重启服务 */
reload: {
type: Boolean,
default: false,
},
});
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
visibleByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: {
id: string;
neType: string;
neId: string;
activationRequestCode: string;
licensePath: string;
remark: string;
reload: boolean;
};
/**确定按钮 loading */
confirmLoading: boolean;
/**上传文件 */
uploadFiles: any[];
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
visibleByEdit: false,
title: '授权文件',
from: {
id: '',
neType: '',
neId: '',
activationRequestCode: '',
licensePath: '',
remark: '',
reload: false,
},
confirmLoading: false,
uploadFiles: [],
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
licensePath: [
{
required: true,
message: t('views.ne.neLicense.licensePathTip'),
},
],
})
);
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
if (modalState.confirmLoading || !modalState.from.id) return;
modalStateFrom
.validate()
.then(e => {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
const from = toRaw(modalState.from);
changeNeLicense(from).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
// 返回无引用信息
emit('ok', JSON.parse(JSON.stringify(from)));
fnModalCancel();
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
hide();
modalState.confirmLoading = false;
});
})
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
});
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.visibleByEdit = false;
modalState.confirmLoading = false;
modalStateFrom.resetFields();
modalState.uploadFiles = [];
emit('cancel');
emit('update:visible', false);
}
/**表单上传前检查或转换压缩 */
function fnBeforeUploadFile(file: FileType) {
if (modalState.confirmLoading) return false;
if (!file.name.endsWith('.ini')) {
const msg = `${t('components.UploadModal.onlyAllow')} .ini`;
message.error(msg, 3);
return Upload.LIST_IGNORE;
}
const isLt3M = file.size / 1024 / 1024 < 3;
if (!isLt3M) {
const msg = `${t('components.UploadModal.allowFilter')} 3MB`;
message.error(msg, 3);
return Upload.LIST_IGNORE;
}
return true;
}
/**表单上传文件 */
function fnUploadFile(up: UploadRequestOption) {
// 发送请求
const hide = message.loading(t('common.loading'), 0);
modalState.confirmLoading = true;
let formData = new FormData();
formData.append('file', up.file);
formData.append('subPath', 'license');
uploadFile(formData)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 改为完成状态
const file = modalState.uploadFiles[0];
file.percent = 100;
file.status = 'done';
// 预置到表单
const { fileName } = res.data;
modalState.from.licensePath = fileName;
} else {
message.error(res.msg, 3);
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
}
/**复制授权申请码 */
function fnCopyCode() {
const code = modalState.from.activationRequestCode;
if (!code) return;
copy(code).then(() => {
message.success(t('common.copyOk'), 3);
});
}
/**下载授权申请码文件 */
function fnDownCode() {
const { activationRequestCode, neType, neId } = modalState.from;
if (!activationRequestCode) return;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neLicense.downCodeTop'),
onOk() {
const blob = new Blob([activationRequestCode], {
type: 'text/plain',
});
saveAs(blob, `${neType}_${neId}_code.txt`);
},
});
}
/**
* 对话框弹出显示为 ID编辑
* @param id id
*/
function fnModalVisibleById(id: string) {
const hide = message.loading(t('common.loading'), 0);
getNeLicense(id)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
Object.assign(modalState.from, res.data);
modalState.from.licensePath = '';
modalState.from.reload = props.reload;
modalState.title = t('views.ne.neLicense.updateTtile');
modalState.visibleByEdit = true;
} else {
message.error(res.msg, 3);
}
})
.finally(() => {
modalState.confirmLoading = false;
hide();
});
}
/**
* 对话框弹出显示为 新增或者修改
* @param neType 网元类型
* @param neId 网元ID
*/
function fnModalVisibleByTypeAndId(neType: string, neId: string) {
const hide = message.loading(t('common.loading'), 0);
getNeLicenseByTypeAndID(neType, neId)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
Object.assign(modalState.from, res.data);
modalState.from.licensePath = '';
modalState.from.reload = props.reload;
modalState.title = t('views.ne.neLicense.updateTtile');
modalState.visibleByEdit = true;
} else {
message.error(res.msg, 3);
}
})
.finally(() => {
modalState.confirmLoading = false;
hide();
});
}
/**监听是否显示,初始数据 */
watch(
() => props.visible,
val => {
if (val) {
if (props.editId) {
fnModalVisibleById(props.editId);
}
if (props.neType && props.neId) {
fnModalVisibleByTypeAndId(props.neType, props.neId);
}
}
}
);
onMounted(() => {});
</script>
<template>
<a-modal
width="800px"
:keyboard="false"
:mask-closable="false"
:visible="modalState.visibleByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="modalStateFrom"
layout="horizontal"
:wrapper-col="{ span: 18 }"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label-col="{ span: 12 }"
:label="t('views.ne.common.neType')"
name="neType"
>
{{ modalState.from.neType }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label-col="{ span: 12 }"
:label="t('views.ne.common.neId')"
name="neId"
>
{{ modalState.from.neId }}
</a-form-item>
</a-col>
</a-row>
<a-form-item
:label="t('views.ne.neLicense.activationRequestCode')"
name="activationRequestCode"
v-bind="modalStateFrom.validateInfos.activationRequestCode"
>
<a-input-group compact>
<a-input
v-model:value="modalState.from.activationRequestCode"
:disabled="true"
style="width: calc(100% - 64px)"
/>
<a-tooltip :title="t('common.copyText')" placement="topRight">
<a-button type="default" @click="fnCopyCode()">
<template #icon><CopyOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip :title="t('common.downloadText')" placement="topRight">
<a-button type="primary" @click="fnDownCode()">
<template #icon><DownloadOutlined /></template>
</a-button>
</a-tooltip>
</a-input-group>
</a-form-item>
<a-form-item
:label="t('views.ne.neLicense.licensePath')"
name="file"
v-bind="modalStateFrom.validateInfos.licensePath"
>
<a-upload
name="file"
v-model:file-list="modalState.uploadFiles"
accept=".ini"
list-type="text"
:max-count="1"
:show-upload-list="{
showPreviewIcon: false,
showRemoveIcon: false,
showDownloadIcon: false,
}"
:before-upload="fnBeforeUploadFile"
:custom-request="fnUploadFile"
:disabled="modalState.confirmLoading"
>
<a-button type="primary">
<template #icon>
<UploadOutlined />
</template>
{{ t('views.ne.neLicense.upload') }}
</a-button>
</a-upload>
</a-form-item>
<!-- 网元授权不允许操作重启上传后根据情况去网元信息操作重启 -->
<a-form-item label="NE Reload" name="reload" v-if="false">
<a-switch v-model:checked="modalState.from.reload"> </a-switch>
</a-form-item>
<a-form-item :label="t('common.remark')" name="remark">
<a-textarea
v-model:value="modalState.from.remark"
:maxlength="200"
:show-count="true"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,242 @@
<script setup lang="ts">
import { reactive, onMounted, watch, PropType, h } from 'vue';
import { message, notification, Upload } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import { FileType } from 'ant-design-vue/lib/upload/interface';
import { uploadFile } from '@/api/tool/file';
import { changeNeLicense } from '@/api/ne/neLicense';
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
/**指定网元类型 */
licenseList: {
type: Array as PropType<Record<string, any>[]>,
default: [],
},
});
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
visibleByUploadFile: boolean;
/**标题 */
title: string;
/**授权文件路径 */
licensePath: string;
/**上传文件 */
uploadFiles: any[];
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
visibleByUploadFile: false,
licensePath: '',
uploadFiles: [],
title: '授权文件',
confirmLoading: false,
});
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
async function fnModalOk() {
if (!modalState.licensePath) {
message.warning(t('views.ne.neLicense.licensePathTip'), 3);
return;
}
if (modalState.confirmLoading) return;
modalState.confirmLoading = true;
const notificationKey = 'NE_LICENSE_MORE';
notification.info({
key: notificationKey,
message: modalState.title,
description: t('common.loading'),
duration: 0,
});
if (props.licenseList.length === 0) {
notification.close(notificationKey);
modalState.confirmLoading = false;
return;
}
const hasFailNeType: string[] = [];
for (const item of props.licenseList) {
try {
const res = await changeNeLicense({
neType: item.neType,
neId: item.neId,
licensePath: modalState.licensePath,
reload: true, // 重启网元
});
if (res.code !== RESULT_CODE_SUCCESS) {
hasFailNeType.push(item.neType);
}
} catch (error) {
console.error(error);
}
}
// 存在错误网元提示
if (hasFailNeType.length > 0) {
notification.warning({
message: modalState.title,
description: h('div', {}, [
h('p', t('views.ne.neLicense.uploadChangeFail')),
h('div', { style: { color: '#f5222d' } }, hasFailNeType.join('、')),
]),
duration: 4.5,
});
}
// 结束
emit('ok', hasFailNeType);
fnModalCancel();
notification.close(notificationKey);
modalState.confirmLoading = false;
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.visibleByUploadFile = false;
modalState.confirmLoading = false;
modalState.licensePath = '';
modalState.uploadFiles = [];
emit('cancel');
emit('update:visible', false);
}
/**表单上传前检查或转换压缩 */
function fnBeforeUploadFile(file: FileType) {
if (modalState.confirmLoading) return false;
if (!file.name.endsWith('.ini')) {
const msg = `${t('components.UploadModal.onlyAllow')} .ini`;
message.error(msg, 3);
return Upload.LIST_IGNORE;
}
const isLt3M = file.size / 1024 / 1024 < 3;
if (!isLt3M) {
const msg = `${t('components.UploadModal.allowFilter')} 3MB`;
message.error(msg, 3);
return Upload.LIST_IGNORE;
}
return true;
}
/**表单上传文件 */
function fnUploadFile(up: UploadRequestOption) {
// 发送请求
const hide = message.loading(t('common.loading'), 0);
modalState.confirmLoading = true;
let formData = new FormData();
formData.append('file', up.file);
formData.append('subPath', 'license');
uploadFile(formData)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 改为完成状态
const file = modalState.uploadFiles[0];
file.percent = 100;
file.status = 'done';
// 预置到表单
const { fileName } = res.data;
modalState.licensePath = fileName;
} else {
message.error(res.msg, 3);
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
}
/**监听是否显示,初始数据 */
watch(
() => props.visible,
val => {
if (val) {
modalState.title = t('views.ne.neLicense.updateTtile');
modalState.visibleByUploadFile = true;
}
}
);
onMounted(() => {});
</script>
<template>
<a-modal
width="500px"
:keyboard="false"
:mask-closable="false"
:visible="modalState.visibleByUploadFile"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
:cancel-button-props="{ disabled: modalState.confirmLoading }"
:closable="false"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="modalStateFrom"
layout="horizontal"
:wrapper-col="{ span: 18 }"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-form-item :label="t('views.ne.common.neType')">
<a-tag color="processing" v-for="s in props.licenseList">
{{ s.neType }}
</a-tag>
</a-form-item>
<a-form-item
:label="t('views.ne.neLicense.licensePath')"
name="file"
:required="true"
:validate-on-rule-change="false"
:validateTrigger="[]"
>
<a-upload
name="file"
v-model:file-list="modalState.uploadFiles"
accept=".ini"
list-type="text"
:multiple="true"
:max-count="1"
:show-upload-list="{
showPreviewIcon: false,
showRemoveIcon: false,
showDownloadIcon: false,
}"
:before-upload="fnBeforeUploadFile"
:custom-request="fnUploadFile"
:disabled="modalState.confirmLoading"
>
<a-button type="primary">
<template #icon>
<UploadOutlined />
</template>
{{ t('views.ne.neLicense.upload') }}
</a-button>
</a-upload>
</a-form-item>
</a-form>
</a-modal>
</template>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,543 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw, defineAsyncComponent } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { Modal, TableColumnsType, message } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import { MenuInfo } from 'ant-design-vue/lib/menu/src/interface';
import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n';
import useDictStore from '@/store/modules/dict';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { listNeLicense, stateNeLicense } from '@/api/ne/neLicense';
import { parseDateToStr } from '@/utils/date-utils';
const { t } = useI18n();
const { getDict } = useDictStore();
const EditModal = defineAsyncComponent(
() => import('./components/EditModal.vue')
);
/**字典数据-状态 */
let dictStatus = ref<DictType[]>([]);
/**网元参数 */
let neOtions = ref<Record<string, any>[]>([]);
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: undefined,
/**网元ID */
neId: '',
/**序列号 */
serialNum: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
neType: undefined,
neId: '',
serialNum: '',
pageNum: 1,
pageSize: 20,
});
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: any[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: false,
data: [],
selectedRowKeys: [],
});
/**表格字段列 */
let tableColumns = ref<TableColumnsType>([
{
title: t('common.rowId'),
dataIndex: 'id',
align: 'left',
width: 100,
},
{
title: t('views.ne.common.neType'),
dataIndex: 'neType',
align: 'left',
width: 100,
},
{
title: t('views.ne.common.neId'),
dataIndex: 'neId',
align: 'left',
width: 100,
},
{
title: t('views.ne.neLicense.status'),
dataIndex: 'status',
key: 'status',
align: 'left',
width: 120,
},
{
title: t('views.ne.common.serialNum'),
dataIndex: 'serialNum',
align: 'left',
width: 100,
},
{
title: t('views.ne.common.expiryDate'),
dataIndex: 'expiryDate',
align: 'left',
width: 120,
},
{
title: t('common.remark'),
dataIndex: 'remark',
key: 'remark',
align: 'left',
width: 150,
resizable: true,
minWidth: 100,
maxWidth: 300,
},
{
title: t('common.updateTime'),
dataIndex: 'updateTime',
align: 'center',
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(opt.value);
},
width: 150,
},
{
title: t('common.operate'),
key: 'id',
align: 'left',
},
]);
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
listNeLicense(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
tablePagination.total = res.total;
tableState.data = res.rows.filter(s => s.neType !== 'OMC');
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
} else {
tablePagination.total = 0;
tableState.data = [];
}
tableState.loading = false;
});
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
visibleByEdit: boolean;
/**授权记录ID */
licenseId: string;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
visibleByEdit: false,
licenseId: '',
confirmLoading: false,
});
/**
* 对话框弹出显示为 新增或者修改
* @param licenseId id
*/
function fnModalVisibleByEdit(licenseId: string) {
modalState.licenseId = licenseId;
modalState.visibleByEdit = true;
}
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
// 获取列表数据
fnGetList();
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.visibleByEdit = false;
modalState.licenseId = '';
}
/**刷新网元授权状态 */
function fnRecordState(row: Record<string, any>) {
if (modalState.confirmLoading) return;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neLicense.reloadTip'),
onOk() {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
stateNeLicense(row.neType, row.neId)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
row.status = '1';
row.serialNum = res.data.sn;
row.expiryDate = res.data.expire;
message.success(
`${row.neType} ${row.neId} ${dictStatus.value[1].label}`,
3
);
} else {
row.status = '0';
message.warning(
`${row.neType} ${row.neId} ${dictStatus.value[0].label}`,
3
);
}
})
.finally(() => {
modalState.confirmLoading = false;
hide();
});
},
});
}
/**刷新网元授权状态 勾选 */
function fnRecordStateBatch() {
if (modalState.confirmLoading) return;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neLicense.reloadBatchTip'),
onOk: async () => {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
// 勾选的网元数据
const selectRows = tableState.data.filter(item =>
tableState.selectedRowKeys.includes(item.id)
);
for (const row of selectRows) {
if (row.neType.toUpperCase() === 'OMC') {
continue;
}
const res = await stateNeLicense(row.neType, row.neId);
if (res.code === RESULT_CODE_SUCCESS && res.data) {
row.status = '1';
row.serialNum = res.data.sn;
row.expiryDate = res.data.expire;
} else {
row.status = '0';
}
tableState.selectedRowKeys = [];
}
message.success(t('common.operateOk'), 3);
hide();
modalState.confirmLoading = false;
},
});
}
onMounted(() => {
// 初始字典数据
getDict('ne_license_status').then(res => {
dictStatus.value = res;
});
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
neOtions.value = useNeInfoStore().getNeSelectOtions;
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
// 获取列表数据
fnGetList();
});
});
</script>
<template>
<PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neType')" name="neType ">
<a-auto-complete
v-model:value="queryParams.neType"
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
:allow-clear="true"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neId')" name="neId">
<a-input
v-model:value="queryParams.neId"
:allow-clear="true"
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.ne.common.serialNum')"
name="serialNum"
>
<a-input
v-model:value="queryParams.serialNum"
:allow-clear="true"
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
<a-button
type="default"
:disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading"
@click.prevent="fnRecordStateBatch()"
>
<template #icon><SecurityScanOutlined /></template>
{{ t('views.ne.neLicense.reloadBatch') }}
</a-button>
</a-space>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip placement="topRight">
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown placement="bottomRight" trigger="click">
<a-button type="text">
<template #icon><ColumnHeightOutlined /></template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
<a-menu-item key="middle">
{{ t('common.size.middle') }}
</a-menu-item>
<a-menu-item key="small">
{{ t('common.size.small') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 120 }"
@resizeColumn="(w:number, col:any) => (col.width = w)"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<DictTag :options="dictStatus" :value="record.status" />
</template>
<template v-if="column.key === 'remark'">
<a-tooltip placement="topLeft">
<template #title>{{ record.remark }}</template>
<div
style="
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: pointer;
width: 50px;
"
:style="{ width: column.width + 'px' }"
>
{{ record.remark }}
</div>
</a-tooltip>
</template>
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip placement="topRight">
<template #title>{{ t('views.ne.neLicense.change') }}</template>
<a-button
type="link"
@click.prevent="fnModalVisibleByEdit(record.id)"
>
<template #icon><InteractionOutlined /> </template>
</a-button>
</a-tooltip>
<a-tooltip placement="topRight">
<template #title>{{ t('views.ne.neLicense.reload') }}</template>
<a-button type="link" @click.prevent="fnRecordState(record)">
<template #icon><SecurityScanOutlined /> </template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
</a-table>
</a-card>
<!-- 文件上传框 -->
<EditModal
v-model:visible="modalState.visibleByEdit"
:edit-id="modalState.licenseId"
@ok="fnModalOk"
@cancel="fnModalCancel"
></EditModal>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -0,0 +1,484 @@
<script setup lang="ts">
import { Form, Modal, message } from 'ant-design-vue/lib';
import { onMounted, reactive, toRaw } from 'vue';
import { addNeInfo, getNeInfoByTypeAndID, updateNeInfo } from '@/api/ne/neInfo';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { NE_TYPE_LIST } from '@/constants/ne-constants';
import { regExpIPv4, regExpIPv6 } from '@/utils/regular-utils';
import { fnRestStepState, fnToStepName, stepState } from '../hooks/useStep';
import useI18n from '@/hooks/useI18n';
import useDictStore from '@/store/modules/dict';
import useNeInfoStore from '@/store/modules/neinfo';
const { getDict } = useDictStore();
const { t } = useI18n();
/**字典数据 */
let dict: {
/**主机类型 */
neHostType: DictType[];
/**分组 */
neHostGroupId: DictType[];
/**认证模式 */
neHostAuthMode: DictType[];
} = reactive({
neHostType: [],
neHostGroupId: [],
neHostAuthMode: [],
});
/**对话框对象信息状态类型 */
type ModalStateType = {
/**是否可以下一步 */
stepNext: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
stepNext: false,
title: '网元',
from: {
id: undefined,
neId: '',
neType: '',
neName: '',
ip: '',
port: 33030,
pvFlag: 'PNF',
rmUid: '4400HXAMF001',
neAddress: '',
dn: '-',
vendorName: '-',
province: '-',
// 主机
hosts: [
{
hostId: undefined,
hostType: 'ssh',
groupId: '1',
title: 'SSH_NE_22',
addr: '',
port: 22,
user: 'user',
authMode: '0',
password: 'user',
privateKey: '',
passPhrase: '',
remark: '',
},
{
hostId: undefined,
hostType: 'telnet',
groupId: '1',
title: 'Telnet_NE_4100',
addr: '',
port: 4100,
user: 'admin',
authMode: '0',
password: 'admin',
remark: '',
},
],
},
confirmLoading: false,
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
neType: [
{
required: true,
message: t('views.ne.common.neTypePlease'),
},
],
neId: [
{
required: true,
message: t('views.ne.common.neIdPlease'),
},
],
ip: [
{
required: true,
validator: modalStateFromEqualIPV4AndIPV6,
},
],
})
);
/**表单验证IP地址是否有效 */
function modalStateFromEqualIPV4AndIPV6(
rule: Record<string, any>,
value: string,
callback: (error?: string) => void
) {
if (!value) {
return Promise.reject(t('views.ne.common.ipAddrPlease'));
}
if (value.indexOf('.') === -1 && value.indexOf(':') === -1) {
return Promise.reject(t('valid.ipPlease'));
}
if (value.indexOf('.') !== -1 && !regExpIPv4.test(value)) {
return Promise.reject(t('valid.ipv4Reg'));
}
if (value.indexOf(':') !== -1 && !regExpIPv6.test(value)) {
return Promise.reject(t('valid.ipv6Reg'));
}
return Promise.resolve();
}
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
const from = toRaw(modalState.from);
modalStateFrom
.validate()
.then(e => {
modalState.confirmLoading = true;
return getNeInfoByTypeAndID(from.neType, from.neId);
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 补充更新必要信息
from.id = res.data.id;
from.hostIds = res.data.hostIds;
const hostIds = res.data.hostIds.split(',');
if (hostIds.length == 2) {
from.hosts[0].hostId = hostIds[0];
from.hosts[1].hostId = hostIds[1];
}
return true;
} else {
// 清除更新必要信息
from.id = undefined;
from.hostIds = '';
for (let index = 0; index < from.hosts.length; index++) {
from.hosts[index].hostId = undefined;
}
return false;
}
})
.then(isUpdate => {
let confirmTitle = t('views.ne.neQuickSetup.configAddTitle');
let confirmContent = t('views.ne.neQuickSetup.configAddTip');
if (isUpdate) {
confirmTitle = t('views.ne.neQuickSetup.configUpdateTitle');
confirmContent = t('views.ne.neQuickSetup.configUpdateTip');
}
Modal.confirm({
title: confirmTitle,
content: confirmContent,
onCancel: () => {
// 清除更新必要信息
from.id = undefined;
from.hostIds = '';
for (let index = 0; index < from.hosts.length; index++) {
from.hosts[index].hostId = undefined;
}
},
onOk: () => {
from.rmUid = `4400HX${from.neType}${from.neId}`; // 4400HX1AMF001
from.neName = `${from.neType}_${from.neId}`; // AMF_001
const hide = message.loading(t('common.loading'), 0);
const result =
isUpdate && from.id ? updateNeInfo(from) : addNeInfo(from);
result
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: `${t('common.operateOk')}`,
duration: 3,
});
// 刷新缓存的网元信息
useNeInfoStore().fnRefreshNelist();
stepState.neInfo = from; // 保存网元信息
modalState.stepNext = true; // 开启下一步
} else {
message.error({
content: res.msg,
duration: 3,
});
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
},
});
})
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
})
.finally(() => {
modalState.confirmLoading = false;
});
}
/**
* 表单修改网元类型
*/
function fnNeTypeChange(v: any) {
const hostsLen = modalState.from.hosts.length;
// 网元默认只含22和4100
if (hostsLen === 3 && v !== 'UPF') {
modalState.from.hosts.pop();
}
// UPF标准版本可支持5002
if (hostsLen === 2 && v === 'UPF') {
modalState.from.hosts.push({
hostId: undefined,
hostType: 'telnet',
groupId: '1',
title: 'Telnet_NE_5002',
addr: modalState.from.ip,
port: 5002,
user: 'admin',
authMode: '0',
password: 'admin',
remark: '',
});
}
}
/**
* 表单修改网元IP
*/
function fnNeIPChange(e: any) {
const v = e.target.value;
if (v.length < 7) return;
for (const host of modalState.from.hosts) {
host.addr = v;
}
}
/**返回上一步 */
function fnStepPrev() {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neQuickSetup.stepPrevTip'),
onOk() {
fnRestStepState();
fnToStepName('Start');
},
});
}
/**下一步操作 */
function fnStepNext() {
if (!modalState.stepNext) return;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neQuickSetup.configStepNext'),
onOk() {
fnToStepName('NeInfoSoftwareInstall');
},
});
}
onMounted(() => {
// 初始字典数据
Promise.allSettled([
getDict('ne_host_type'),
getDict('ne_host_groupId'),
getDict('ne_host_authMode'),
])
.then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.neHostType = resArr[0].value;
}
if (resArr[1].status === 'fulfilled') {
dict.neHostGroupId = resArr[1].value;
}
if (resArr[2].status === 'fulfilled') {
dict.neHostAuthMode = resArr[2].value;
}
})
.finally(() => {
if (stepState.neInfo.id) {
Object.assign(modalState.from, stepState.neInfo);
} else {
modalState.from.ip = stepState.neHost.addr;
Object.assign(modalState.from.hosts[0], stepState.neHost);
Object.assign(modalState.from.hosts[1], {
addr: modalState.from.ip,
user: 'admin',
password: 'admin',
});
}
});
});
</script>
<template>
<div class="ne">
<a-form
name="modalStateFrom"
layout="horizontal"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-row :gutter="16">
<a-col :lg="6" :md="6" :xs="24" :offset="6">
<a-form-item
:label="t('views.ne.common.neType')"
name="neType"
v-bind="modalStateFrom.validateInfos.neType"
>
<a-auto-complete
v-model:value="modalState.from.neType"
:options="
NE_TYPE_LIST.filter(s => s !== 'OMC').map(v => ({ value: v }))
"
@change="fnNeTypeChange"
>
<a-input
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="32"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.ne.common.neTypeTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-auto-complete>
</a-form-item>
</a-col>
<a-col :lg="6" :md="6" :xs="24">
<a-form-item
:label="t('views.ne.common.neId')"
name="neId"
v-bind="modalStateFrom.validateInfos.neId"
>
<a-input
v-model:value="modalState.from.neId"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="24"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.ne.common.neIdTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="6" :md="6" :xs="24" :offset="6">
<a-form-item
:label="t('views.ne.common.ipAddr')"
name="ip"
v-bind="modalStateFrom.validateInfos.ip"
>
<a-input
v-model:value="modalState.from.ip"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="128"
@change="fnNeIPChange"
:disabled="true"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
<div>
{{ t('views.ne.common.ipAddrTip') }}
</div>
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="6" :xs="24">
<a-form-item
:label="t('views.ne.common.port')"
name="port"
v-bind="modalStateFrom.validateInfos.port"
>
<a-input-number
v-model:value="modalState.from.port"
style="width: 100%"
:min="1"
:max="65535"
:maxlength="5"
placeholder="<=65535"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
<div>{{ t('views.ne.common.portTip') }}</div>
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
</a-input-number>
</a-form-item>
</a-col>
</a-row>
</a-form>
<div class="ne-oper">
<a-space direction="horizontal" :size="18">
<a-button @click="fnStepPrev()">
{{ t('views.ne.neQuickSetup.stepPrev') }}
</a-button>
<a-button
type="primary"
ghost
@click="fnModalOk()"
:loading="modalState.confirmLoading"
>
{{ t('views.ne.neQuickSetup.stepSave') }}
</a-button>
<a-button
type="primary"
@click="fnStepNext()"
:disabled="!modalState.stepNext"
>
{{ t('views.ne.neQuickSetup.stepNext') }}
</a-button>
</a-space>
</div>
</div>
</template>
<style lang="less" scoped>
.ne {
min-height: 400px;
display: flex;
flex-direction: column;
& .ant-form {
flex: 1;
}
&-oper {
text-align: end;
}
}
</style>

View File

@@ -0,0 +1,441 @@
<script setup lang="ts">
import { Modal, TableColumnsType, message } from 'ant-design-vue/lib';
import { defineAsyncComponent, onMounted, reactive, ref, toRaw } from 'vue';
import { fnToStepName, stepState } from '../hooks/useStep';
import useI18n from '@/hooks/useI18n';
import { listNeVersion, operateNeVersion } from '@/api/ne/neVersion';
import {
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import {
addNeSoftware,
listNeSoftware,
newNeVersion,
} from '@/api/ne/neSoftware';
import { parseDateToStr } from '@/utils/date-utils';
import { ColumnsType } from 'ant-design-vue/lib/table';
const { t } = useI18n();
const EditModal = defineAsyncComponent(
() => import('../../../ne/neSoftware/components/EditModal.vue')
);
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('common.rowId'),
dataIndex: 'id',
align: 'left',
width: 50,
},
{
title: t('views.ne.common.neType'),
dataIndex: 'neType',
align: 'left',
width: 100,
},
{
title: t('views.ne.neSoftware.version'),
dataIndex: 'version',
align: 'left',
width: 150,
},
{
title: t('views.ne.neSoftware.name'),
dataIndex: 'name',
key: 'name',
align: 'left',
width: 300,
},
{
title: t('common.description'),
dataIndex: 'description',
key: 'description',
align: 'left',
ellipsis: true,
},
{
title: t('common.createTime'),
dataIndex: 'createTime',
align: 'left',
width: 150,
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(+opt.value);
},
},
];
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 10,
/**默认的每页条数 */
defaultPageSize: 10,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
tableState.queryParams.pageNum = page;
tableState.queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格状态类型 */
type TabeStateType = {
/**查询参数 */
queryParams: Record<string, any>;
/**加载等待 */
loading: boolean;
/**记录数据 */
data: object[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
queryParams: {
neType: '',
pageNum: 1,
pageSize: 10,
},
loading: false,
data: [],
selectedRowKeys: [],
});
/**表格多选 */
function fnTableSelectedRowKeys(
keys: (string | number)[],
selectedRows: any[]
) {
tableState.selectedRowKeys = keys;
// 选择的表单数据填充
Object.assign(state.from, selectedRows[0], { id: '' });
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
tableState.queryParams.pageNum = pageNum;
}
listNeSoftware(toRaw(tableState.queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选
if (tableState.selectedRowKeys.length > 0) {
tableState.selectedRowKeys = [];
}
tablePagination.total = res.total;
// 遍历处理资源情况数值
tableState.data = res.rows;
}
tableState.loading = false;
});
}
/**对象信息信息状态类型 */
type StateType = {
/**是否可以下一步 */
stepNext: boolean;
/**文件操作类型 上传 or 选择 */
optionType: 'upload' | 'option';
/**文件上传 */
visibleByFile: boolean;
/**
* 依赖包类型
* IMS-rtproxy/mf/adb
* UDM-adb
*/
depType: string[];
/**软件包信息数据 */
from: {
neType: string; // 版本需要
neId: string; // 版本需要
name: string; // 软件需要
path: string;
version: string; // 软件需要
description: string;
};
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对象信息状态 */
let state: StateType = reactive({
stepNext: false,
optionType: 'option',
visibleByFile: false,
depType: [],
from: {
id: undefined,
neType: '',
neId: '',
name: '',
path: '',
version: '',
description: '',
},
confirmLoading: false,
});
/**
* 表单修改文件操作类型
*/
function fnOptionTypeChange() {
state.from.name = '';
state.from.version = '';
tableState.selectedRowKeys = [];
if (state.optionType === 'option') {
fnGetList(1);
}
}
/**对话框弹出 */
function fnModalOpen() {
state.visibleByFile = !state.visibleByFile;
}
/**对话框弹出确认执行函数*/
function fnModalOk(e: any) {
Object.assign(state.from, e, { id: '' });
}
/**对话框弹出关闭执行函数*/
function fnModalCancel() {
state.visibleByFile = false;
}
/**版本安装 */
function fnRecordInstall() {
const from = toRaw(state.from);
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neQuickSetup.installConfirmTip', {
name: from.name,
}),
onOk: async () => {
if (state.confirmLoading) return;
state.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
// 选择的软件需要更新版本
if (state.optionType === 'option') {
const res = await newNeVersion(from);
if (res.code === RESULT_CODE_ERROR) {
message.error(res.msg, 3);
hide();
state.confirmLoading = false;
return;
}
}
// 进行安装
let preinput = {};
if (from.neType.toUpperCase() === 'IMS') {
preinput = { pisCSCF: 'y', updateMFetc: 'No', updateMFshare: 'No' };
}
const res = await operateNeVersion({
neType: from.neType,
neId: from.neId,
action: 'install',
preinput: preinput,
});
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
state.stepNext = true;
} else {
message.error(res.msg, 3);
}
// 非选择的重置
state.optionType = 'option';
fnOptionTypeChange();
hide();
state.confirmLoading = false;
},
});
}
/**返回上一步 */
function fnStepPrev() {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neQuickSetup.stepPrevTip'),
onOk() {
fnToStepName('NeInfoConfig');
},
});
}
/**下一步操作 */
function fnStepNext() {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neQuickSetup.installStepNext'),
onOk() {
fnToStepName('NeInfoSoftwareLicense');
},
});
}
onMounted(() => {
const { neType, neId } = stepState.neInfo;
if (neId) {
tableState.queryParams.neType = neType;
state.from.neType = neType;
state.from.neId = neId;
fnGetList(1);
}
});
</script>
<template>
<div class="ne">
<a-form
name="installStateFrom"
layout="horizontal"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 16 }"
:label-wrap="true"
>
<a-form-item
:label="t('views.ne.neQuickSetup.installSource')"
name="optionType"
>
<a-radio-group
v-model:value="state.optionType"
button-style="solid"
:disabled="state.confirmLoading"
@change="fnOptionTypeChange"
>
<a-radio-button value="option">
{{ t('views.ne.neQuickSetup.installSourceOption') }}
</a-radio-button>
<a-radio-button value="upload">
{{ t('views.ne.neQuickSetup.installSourceUpload') }}
</a-radio-button>
</a-radio-group>
</a-form-item>
<!-- 选择已上传 -->
<template v-if="state.optionType === 'option'">
<a-form-item
:label="t('views.ne.neQuickSetup.installSelect')"
name="option"
>
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:pagination="tablePagination"
size="small"
:scroll="{ x: tableColumns.length * 100, y: '400px' }"
:row-selection="{
type: 'radio',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'">
<a-tooltip placement="topLeft">
<template #title>{{ record.path }}</template>
<div style="cursor: pointer">{{ record.name }}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'description'">
<a-tooltip placement="topLeft">
<template #title>{{ record.description }}</template>
<div style="cursor: pointer">{{ record.description }}</div>
</a-tooltip>
</template>
</template>
</a-table>
</a-form-item>
</template>
<!-- 重新上传 -->
<template v-if="state.optionType === 'upload'">
<a-form-item
:label="t('views.ne.neQuickSetup.installUpload')"
name="upload"
:help="state.from.name"
>
<a-button type="primary" @click.prevent="fnModalOpen()">
<template #icon><UploadOutlined /></template>
{{ t('views.ne.neSoftware.upload') }}
</a-button>
</a-form-item>
</template>
</a-form>
<!-- 文件上传框 -->
<EditModal
v-model:visible="state.visibleByFile"
@ok="fnModalOk"
@cancel="fnModalCancel"
></EditModal>
<div class="ne-oper">
<a-space direction="horizontal" :size="18">
<a-button @click="fnStepPrev()">
{{ t('views.ne.neQuickSetup.stepPrev') }}
</a-button>
<a-button
type="primary"
ghost
:disabled="!state.from.version"
:loading="state.confirmLoading"
@click.prevent="fnRecordInstall()"
>
<template #icon><ThunderboltOutlined /></template>
{{ t('views.ne.neQuickSetup.installText') }}
</a-button>
<a-button
type="primary"
@click="fnStepNext()"
:disabled="!state.stepNext"
>
{{ t('views.ne.neQuickSetup.stepNext') }}
</a-button>
</a-space>
</div>
</div>
</template>
<style lang="less" scoped>
.ne {
min-height: 400px;
display: flex;
flex-direction: column;
& .ant-form {
flex: 1;
}
&-oper {
text-align: end;
}
}
</style>

View File

@@ -0,0 +1,180 @@
<script setup lang="ts">
import { Modal } from 'ant-design-vue/lib';
import { defineAsyncComponent, onMounted, onUnmounted, reactive } from 'vue';
import { fnRestStepState, stepState } from '../hooks/useStep';
import useI18n from '@/hooks/useI18n';
import { codeNeLicense, stateNeLicense } from '@/api/ne/neLicense';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
const { t } = useI18n();
const EditModal = defineAsyncComponent(
() => import('../../../ne/neLicense/components/EditModal.vue')
);
/**对象信息信息状态类型 */
type StateType = {
/**文件上传 */
visibleByFile: boolean;
/**授权信息数据 */
from: {
neType: string;
neId: string;
// 下面是状态检查结果
expire: string;
sn: string;
};
/**确定按钮 loading */
confirmLoading: boolean;
/**定时调度 */
timeInterval: any;
timeCount: number;
};
/**对象信息状态 */
let state: StateType = reactive({
visibleByFile: false,
from: {
neType: '',
neId: '',
expire: '',
sn: '',
},
confirmLoading: false,
timeInterval: null,
timeCount: 30,
});
/**对话框弹出确认执行函数*/
function fnModalOk(e: any) {
state.timeInterval = setInterval(() => {
if (state.timeCount <= 0) {
state.from.sn = '';
state.from.expire = '';
clearInterval(state.timeInterval);
state.timeInterval = null;
state.timeCount = 30;
return;
}
if (state.timeCount % 5 === 0) {
stateNeLicense(e.neType, e.neId).then(res => {
if (res.code === RESULT_CODE_SUCCESS && res.data) {
state.from.sn = res.data.sn;
state.from.expire = res.data.expire;
clearInterval(state.timeInterval);
state.timeInterval = null;
state.timeCount = 30;
}
});
}
state.timeCount--;
}, 1_000);
}
/**对话框弹出关闭执行函数*/
function fnModalCancel() {
state.visibleByFile = false;
state.confirmLoading = false;
}
/**结束操作 */
function fnStepEnd() {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neQuickSetup.licenseEndTip'),
onOk() {
fnRestStepState();
},
});
}
onMounted(() => {
const { neType, neId } = stepState.neInfo;
if (neId) {
state.from.neType = neType;
state.from.neId = neId;
state.confirmLoading = true;
stateNeLicense(neType, neId)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && res.data) {
state.from.sn = res.data.sn;
state.from.expire = res.data.expire;
} else {
return codeNeLicense(neType, neId);
}
})
.finally(() => {
state.confirmLoading = false;
});
}
});
onUnmounted(() => {
clearInterval(state.timeInterval);
state.timeInterval = null;
state.timeCount = 30;
});
</script>
<template>
<a-result
:status="!state.from.sn ? 'info' : 'success'"
:title="
t(
!state.from.sn
? 'views.ne.neQuickSetup.licenseResultTitle'
: 'views.ne.neQuickSetup.licenseResultTitleOk'
)
"
>
<template #extra>
<a-button
type="primary"
:disabled="state.from.sn !== ''"
:loading="state.timeCount < 30 || state.confirmLoading"
@click="() => (state.visibleByFile = !state.visibleByFile)"
>
{{ t('views.ne.neQuickSetup.licenseUpload') }}
</a-button>
<a-button
type="default"
:disabled="state.timeCount < 30 || state.confirmLoading"
@click="fnStepEnd()"
>
{{ t('views.ne.neQuickSetup.licenseEnd') }}
</a-button>
</template>
<div
v-if="
state.timeInterval === null && state.timeCount === 30 && !state.from.sn
"
>
<p>{{ t('views.ne.neQuickSetup.licenseTip1') }}</p>
<p>{{ t('views.ne.neQuickSetup.licenseTip2') }}</p>
</div>
<div v-if="state.timeInterval !== null">
<a-space direction="horizontal" :size="16">
<a-spin />
{{ t('views.ne.neQuickSetup.licenseCheack') }} {{ state.timeCount }}s
</a-space>
</div>
<div v-if="state.from.sn !== ''" style="font-size: 16px">
<p>{{ t('views.ne.common.neType') }}{{ state.from.neType }}</p>
<p>{{ t('views.ne.common.neId') }}{{ state.from.neId }}</p>
<p>{{ t('views.ne.common.serialNum') }}{{ state.from.sn }}</p>
<p>{{ t('views.ne.common.expiryDate') }}{{ state.from.expire }}</p>
</div>
</a-result>
<!-- 许可证上传框 -->
<EditModal
v-model:visible="state.visibleByFile"
:ne-type="state.from.neType"
:ne-id="state.from.neId"
:reload="true"
@ok="fnModalOk"
@cancel="fnModalCancel"
></EditModal>
</template>
<style lang="less" scoped></style>

View File

@@ -3,9 +3,10 @@ import { reactive, onMounted, toRaw } from 'vue';
import { message, Form, Modal } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { regExpIPv4, regExpIPv6 } from '@/utils/regular-utils';
import { neHostAuthorizedRSA, neHostCheckInfo } from '@/api/ne/neHost';
import useDictStore from '@/store/modules/dict';
import { stepState } from '../hooks/useStep';
import { fnToStepName, stepState } from '../hooks/useStep';
const { getDict } = useDictStore();
const { t } = useI18n();
@@ -17,20 +18,22 @@ let dict: {
neHostAuthMode: [],
});
/**检查对象信息状态类型 */
type CheckStateType = {
/**对象信息状态类型 */
type StateType = {
/**服务器信息 */
info: Record<string, any>;
/**表单数据 */
from: Record<string, any>;
/**是否可以下一步 */
stepNext: boolean;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**检查对象信息状态 */
let checkState: CheckStateType = reactive({
/**对象信息状态 */
let state: StateType = reactive({
info: {
addr: '未连接',
addr: '0.0.0.0',
kernelName: '-',
kernelRelease: '-',
machine: '-',
@@ -44,28 +47,29 @@ let checkState: CheckStateType = reactive({
hostType: 'ssh',
groupId: '1',
title: 'SSH_NE_22',
addr: '192.168.5.57',
addr: '',
port: 22,
user: 'agtuser',
authMode: '0',
password: 'QWERqwer',
user: '',
authMode: '2',
password: '',
privateKey: '',
passPhrase: '',
remark: '',
},
stepNext: false,
confirmLoading: false,
});
/**表单属性和校验规则 */
const checkStateFrom = Form.useForm(
checkState.from,
state.from,
reactive({
addr: [
{
required: true,
min: 1,
max: 128,
message: t('views.ne.neHost.addrPlease'),
validator: modalStateFromEqualIPV4AndIPV6,
},
],
port: [
@@ -94,31 +98,54 @@ const checkStateFrom = Form.useForm(
{
required: true,
min: 1,
max: 128,
max: 3000,
message: t('views.ne.neHost.privateKeyPlease'),
},
],
})
);
/**表单验证IP地址是否有效 */
function modalStateFromEqualIPV4AndIPV6(
rule: Record<string, any>,
value: string,
callback: (error?: string) => void
) {
if (!value) {
return Promise.reject(t('views.ne.common.ipAddrPlease'));
}
if (value.indexOf('.') === -1 && value.indexOf(':') === -1) {
return Promise.reject(t('valid.ipPlease'));
}
if (value.indexOf('.') !== -1 && !regExpIPv4.test(value)) {
return Promise.reject(t('valid.ipv4Reg'));
}
if (value.indexOf(':') !== -1 && !regExpIPv6.test(value)) {
return Promise.reject(t('valid.ipv6Reg'));
}
return Promise.resolve();
}
/**测试连接检查信息 */
function fnCheckInfo() {
if (checkState.confirmLoading) return;
const form = toRaw(checkState.from);
if (state.confirmLoading) return;
const form = toRaw(state.from);
const validateArr = ['addr', 'port', 'user'];
if (form.authMode === '0') {
validateArr.push('password');
} else {
}
if (form.authMode === '1') {
validateArr.push('privateKey');
}
checkState.confirmLoading = true;
state.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
checkStateFrom
.validate(validateArr)
.then(() => {
Object.assign(checkState.info, {
addr: '未连接',
Object.assign(state.info, {
addr: '0.0.0.0',
kernelName: '-',
kernelRelease: '-',
machine: '-',
@@ -127,36 +154,29 @@ function fnCheckInfo() {
sshLink: false,
sudo: false,
});
stepState.stepNext = false;
return neHostCheckInfo(form);
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
checkState.info = res.data;
state.info = res.data;
if (!res.data.sudo) {
message.warning({
content: `请配置服务器授予当前用户无密码 sudo 权限,确保有权限进行软件包安装`,
duration: 2,
});
return;
}
if (!res.data.sshLink) {
message.warning({
content: `请配置服务器间免密信任关系,确保服务器间文件传输功能`,
duration: 2,
});
message.warning(t('views.ne.neQuickSetup.sudoErr'), 3);
return;
}
// if (!res.data.sshLink) {
// message.warning({
// content: ``,
// duration: 2,
// });
// return;
// }
stepState.neHost = form; //
state.stepNext = true; //
message.success({
content: `${form.addr}:${form.port} ${t('views.ne.neHost.testOk')}`,
duration: 2,
});
//
stepState.states[stepState.current] = {
info: checkState.info,
from: checkState.from,
};
stepState.stepNext = true;
} else {
message.error({
content: `${form.addr}:${form.port} ${res.msg}`,
@@ -169,41 +189,60 @@ function fnCheckInfo() {
})
.finally(() => {
hide();
checkState.confirmLoading = false;
state.confirmLoading = false;
});
}
/**测试连接检查信息表单重置 */
function fnCheckInfoReset() {
Object.assign(state.info, {
addr: '0.0.0.0',
kernelName: '-',
kernelRelease: '-',
machine: '-',
nodename: '-',
prettyName: '-',
sshLink: false,
sudo: false,
});
state.stepNext = false;
checkStateFrom.resetFields();
}
/**SSH连接-免密直连 */
function fnSSHLink() {
if (checkState.info.sshLink) return;
/**测试主机连接-免密直连 */
function fnHostAuthorized() {
if (state.confirmLoading) return;
Modal.confirm({
title: '提示',
content: '是否要配置免密直连?',
title: t('common.tipTitle'),
content: t('views.ne.neHost.authRSATip'),
onOk: () => {
const form = toRaw(checkState.from);
const form = toRaw(state.from);
state.confirmLoading = true;
neHostAuthorizedRSA(form).then(res => {
state.confirmLoading = false;
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: `操作成功`,
duration: 2,
});
checkState.info.sshLink = true;
message.success(t('common.operateOk'), 3);
} else {
message.error({
content: `操作失败`,
duration: 2,
});
message.error(t('common.operateErr'), 3);
}
});
},
});
}
/**下一步操作 */
function fnStepNext() {
if (!state.stepNext) return;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.ne.neQuickSetup.startStepNext'),
onOk() {
fnToStepName('NeInfoConfig');
},
});
}
onMounted(() => {
//
Promise.allSettled([getDict('ne_host_authMode')]).then(resArr => {
@@ -211,75 +250,61 @@ onMounted(() => {
dict.neHostAuthMode = resArr[0].value;
}
});
//
const state = stepState.states[stepState.current];
if (state) {
if (state.info) {
const info = toRaw(state.info);
Object.assign(checkState.info, info);
}
if (state.from) {
const from = toRaw(state.from);
Object.assign(checkState.from, from);
}
}
});
</script>
<template>
<a-descriptions :column="{ lg: 3, md: 2, sm: 2, xs: 1 }" bordered>
<a-descriptions-item label="服务器IP" :span="3">
{{ checkState.info.addr }}
<a-descriptions-item :label="t('views.ne.neQuickSetup.addr')" :span="3">
{{ state.info.addr }}
</a-descriptions-item>
<a-descriptions-item label="系统">
{{ checkState.info.kernelName }}
<a-descriptions-item :label="t('views.ne.neQuickSetup.kernelName')">
{{ state.info.kernelName }}
</a-descriptions-item>
<a-descriptions-item label="架构">
{{ checkState.info.machine }}
<a-descriptions-item :label="t('views.ne.neQuickSetup.machine')">
{{ state.info.machine }}
</a-descriptions-item>
<a-descriptions-item label="内核">
{{ checkState.info.kernelRelease }}
<a-descriptions-item :label="t('views.ne.neQuickSetup.kernelRelease')">
{{ state.info.kernelRelease }}
</a-descriptions-item>
<a-descriptions-item>
<template #label>
平台
{{ t('views.ne.neQuickSetup.prettyName') }}
<a-tooltip placement="topLeft">
<template #title> 支持 UbuntuUbuntu </template>
<template #title>
{{ t('views.ne.neQuickSetup.prettyNameTip') }}
</template>
<InfoCircleOutlined style="color: rgba(0, 0, 0, 0.45)" />
</a-tooltip>
</template>
{{ checkState.info.prettyName }}
{{ state.info.prettyName }}
</a-descriptions-item>
<a-descriptions-item label="主机名">
{{ checkState.info.nodename }}
<a-descriptions-item :label="t('views.ne.neQuickSetup.nodename')">
{{ state.info.nodename }}
</a-descriptions-item>
<a-descriptions-item label="授予权限">
<a-tag :color="checkState.info.sudo ? 'success' : 'error'">
<a-descriptions-item :label="t('views.ne.neQuickSetup.auth')">
<a-tag :color="state.info.sudo ? 'success' : 'error'">
<template #icon>
<CheckCircleOutlined v-if="checkState.info.sudo" />
<CheckCircleOutlined v-if="state.info.sudo" />
<CloseCircleOutlined v-else />
</template>
可提权
{{ t('views.ne.neQuickSetup.sudo') }}
</a-tag>
<a-tag
:color="checkState.info.sshLink ? 'success' : 'error'"
style="cursor: pointer"
@click="fnSSHLink()"
>
<a-tag :color="state.info.sshLink ? 'success' : 'error'">
<template #icon>
<CheckCircleOutlined v-if="checkState.info.sshLink" />
<CheckCircleOutlined v-if="state.info.sshLink" />
<CloseCircleOutlined v-else />
</template>
可免密直连
{{ t('views.ne.neQuickSetup.sshLink') }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="连接检查" :span="2">
<a-descriptions-item :span="2">
<a-form
name="checkStateFrom"
layout="horizontal"
:label-col="{ span: 6 }"
:label-wrap="true"
:label-wrap="false"
>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
@@ -289,7 +314,7 @@ onMounted(() => {
v-bind="checkStateFrom.validateInfos.addr"
>
<a-input
v-model:value="checkState.from.addr"
v-model:value="state.from.addr"
allow-clear
:maxlength="128"
:placeholder="t('common.inputPlease')"
@@ -304,10 +329,11 @@ onMounted(() => {
v-bind="checkStateFrom.validateInfos.port"
>
<a-input-number
v-model:value="checkState.from.port"
v-model:value="state.from.port"
:min="10"
:max="65535"
:step="1"
:maxlength="5"
style="width: 100%"
></a-input-number>
</a-form-item>
@@ -322,9 +348,9 @@ onMounted(() => {
v-bind="checkStateFrom.validateInfos.user"
>
<a-input
v-model:value="checkState.from.user"
v-model:value="state.from.user"
allow-clear
:maxlength="50"
:maxlength="32"
:placeholder="t('common.inputPlease')"
>
</a-input>
@@ -333,7 +359,7 @@ onMounted(() => {
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.neHost.authMode')">
<a-select
v-model:value="checkState.from.authMode"
v-model:value="state.from.authMode"
default-value="0"
:options="dict.neHostAuthMode"
>
@@ -343,7 +369,7 @@ onMounted(() => {
</a-row>
<a-form-item
v-if="checkState.from.authMode === '0'"
v-if="state.from.authMode === '0'"
:label="t('views.ne.neHost.password')"
:label-col="{ span: 3 }"
:label-wrap="true"
@@ -351,14 +377,14 @@ onMounted(() => {
v-bind="checkStateFrom.validateInfos.password"
>
<a-input-password
v-model:value="checkState.from.password"
v-model:value="state.from.password"
:maxlength="128"
:placeholder="t('common.inputPlease')"
>
</a-input-password>
</a-form-item>
<template v-if="checkState.from.authMode === '1'">
<template v-if="state.from.authMode === '1'">
<a-form-item
:label="t('views.ne.neHost.privateKey')"
:label-col="{ span: 3 }"
@@ -367,7 +393,7 @@ onMounted(() => {
v-bind="checkStateFrom.validateInfos.privateKey"
>
<a-textarea
v-model:value="checkState.from.privateKey"
v-model:value="state.from.privateKey"
:auto-size="{ minRows: 4, maxRows: 6 }"
:maxlength="3000"
:show-count="true"
@@ -381,7 +407,7 @@ onMounted(() => {
:label-wrap="true"
>
<a-input-password
v-model:value="checkState.from.passPhrase"
v-model:value="state.from.passPhrase"
:maxlength="128"
:placeholder="t('common.inputPlease')"
>
@@ -389,18 +415,42 @@ onMounted(() => {
</a-form-item>
</template>
<a-form-item :wrapper-col="{ span: 8, offset: 3 }">
<a-button
type="primary"
html-type="submit"
@click="fnCheckInfo()"
:loading="checkState.confirmLoading"
>
进行连接
</a-button>
<a-button style="margin-left: 12px" @click="fnCheckInfoReset()">
重置
</a-button>
<a-form-item :wrapper-col="{ span: 10, offset: 3 }">
<a-space direction="horizontal" :size="18">
<a-button
type="primary"
ghost
html-type="submit"
@click="fnCheckInfo()"
:loading="state.confirmLoading"
>
{{ t('views.ne.neHost.test') }}
</a-button>
<a-button
type="primary"
@click="fnStepNext()"
:disabled="!state.stepNext"
>
{{ t('views.ne.neQuickSetup.stepNext') }}
</a-button>
<a-button
type="dashed"
@click="fnHostAuthorized()"
:disabled="state.confirmLoading"
v-if="state.from.authMode !== '2'"
>
{{ t('views.ne.neHost.authRSA') }}
</a-button>
<a-button
type="link"
@click="fnCheckInfoReset()"
:disabled="state.confirmLoading"
>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-form>
</a-descriptions-item>

View File

@@ -0,0 +1,82 @@
import { reactive } from 'vue';
/**步骤信息状态类型 */
type StepStateType = {
/**步骤名称 */
stepName: string;
/**安装网元步骤信息 */
steps: any[];
/**安装网元步骤当前选中 */
current: number;
/**连接主机 */
neHost: Record<string, any>;
/**网元信息 */
neInfo: Record<string, any>;
};
/**步骤信息状态 */
export const stepState: StepStateType = reactive({
stepName: 'Start',
steps: [
{
title: '服务器环境',
description: '服务端与网元服务',
},
{
title: '配置网元信息',
description: '网元信息设置',
},
{
title: '网元软件安装',
description: '软件安装到服务端',
},
{
title: '网元授权激活',
description: '网元服务授权激活',
},
],
current: 0,
neHost: {},
neInfo: {},
});
/**步骤信息状态复位 */
export function fnRestStepState(t?: any) {
stepState.stepName = 'Start';
stepState.current = 0;
stepState.neHost = {};
stepState.neInfo = {};
// 多语言翻译
if (t) {
stepState.steps = [
{
title: t('views.ne.neQuickSetup.startTitle'),
description: t('views.ne.neQuickSetup.startDesc'),
},
{
title: t('views.ne.neQuickSetup.configTitle'),
description: t('views.ne.neQuickSetup.configDesc'),
},
{
title: t('views.ne.neQuickSetup.installTitle'),
description: t('views.ne.neQuickSetup.installDesc'),
},
{
title: t('views.ne.neQuickSetup.licenseTitle'),
description: t('views.ne.neQuickSetup.licenseDesc'),
},
];
}
}
/**跳转步骤组件 */
export function fnToStepName(stepName: string) {
stepState.current = [
'Start',
'NeInfoConfig',
'NeInfoSoftwareInstall',
'NeInfoSoftwareLicense',
].indexOf(stepName);
stepState.stepName = stepName;
}

View File

@@ -0,0 +1,52 @@
<script lang="ts" setup>
import { PageContainer } from 'antdv-pro-layout';
import { defineAsyncComponent, watch, shallowRef, onMounted } from 'vue';
import { stepState, fnRestStepState } from './hooks/useStep';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
// 异步加载组件
const Start = defineAsyncComponent(() => import('./components/Start.vue'));
// 当前组件
const currentComponent = shallowRef(Start);
watch(
() => stepState.stepName,
v => {
const loadComponent = defineAsyncComponent(
() => import(`./components/${v}.vue`)
);
currentComponent.value = loadComponent;
}
);
onMounted(() => {
fnRestStepState(t);
});
</script>
<template>
<PageContainer>
<a-card :bordered="false">
<!-- 插槽-卡片左侧 -->
<template #title>
<!-- 步骤进度 -->
<a-steps :current="stepState.current" direction="horizontal">
<a-step
v-for="s in stepState.steps"
:key="s.title"
:title="s.title"
:description="s.description"
:disabled="true"
/>
</a-steps>
</template>
<!-- 步骤页面 -->
<component :is="currentComponent" />
</a-card>
</PageContainer>
</template>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,470 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue';
import { message, Form, Upload, notification } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { NE_EXPAND_LIST, NE_TYPE_LIST } from '@/constants/ne-constants';
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import {
addNeSoftware,
getNeSoftware,
updateNeSoftware,
} from '@/api/ne/neSoftware';
import { FileType } from 'ant-design-vue/lib/upload/interface';
import { uploadFileChunk } from '@/api/tool/file';
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
editId: {
type: String,
default: '',
},
});
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
visibleByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: {
id: string;
neType: string;
name: string;
path: string;
version: string;
description: string;
};
/**确定按钮 loading */
confirmLoading: boolean;
/**上传文件 */
uploadFiles: any[];
/**上传文件-依赖包 */
uploadFilesDep: any[];
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
visibleByEdit: false,
title: '软件包文件',
from: {
id: '',
neType: '',
name: '',
path: '',
version: '',
description: '',
},
confirmLoading: false,
uploadFiles: [],
uploadFilesDep: [],
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
neType: [
{
required: true,
min: 1,
max: 32,
message: t('views.ne.common.neTypePlease'),
},
],
version: [
{
required: true,
min: 1,
max: 64,
message: t('views.ne.neSoftware.versionPlease'),
},
],
path: [
{
required: true,
message: t('views.ne.neSoftware.pathPlease'),
},
],
})
);
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
if (modalState.confirmLoading) return;
modalStateFrom
.validate()
.then(e => {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
const from = toRaw(modalState.from);
// 安装带依赖包
if (modalState.uploadFilesDep.length > 0) {
const depFiles = [];
for (const depFile of modalState.uploadFilesDep) {
if (depFile.status === 'done' && depFile.path) {
depFiles.push(depFile.path);
}
}
depFiles.push(from.path);
from.path = depFiles.join(',');
}
const software = from.id ? updateNeSoftware(from) : addNeSoftware(from);
software.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
// 返回无引用信息
emit('ok', JSON.parse(JSON.stringify(from)));
fnModalCancel();
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
hide();
modalState.confirmLoading = false;
});
})
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
});
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.visibleByEdit = false;
modalState.confirmLoading = false;
modalStateFrom.resetFields();
modalState.uploadFiles = [];
modalState.uploadFilesDep = [];
emit('cancel');
emit('update:visible', false);
}
/**表单上传前检查或转换压缩 */
function fnBeforeUploadFile(file: FileType) {
if (modalState.confirmLoading) return false;
const fileName = file.name;
const suff = fileName.substring(fileName.lastIndexOf('.'));
if (!['.deb', '.rpm'].includes(suff)) {
message.error(
t('views.configManage.softwareManage.onlyAble', {
fileText: '(.deb、.rpm)',
}),
3
);
return Upload.LIST_IGNORE;
}
// 取网元类型判断是否支持
let neType = '';
const neTypeIndex = fileName.indexOf('-');
if (neTypeIndex !== -1) {
neType = fileName.substring(0, neTypeIndex).toUpperCase();
}
// 主包类型
if (!NE_TYPE_LIST.includes(neType)) {
notification.warning({
message: fileName,
description: t('views.ne.neSoftware.fileCheckType'),
});
return Upload.LIST_IGNORE;
}
modalState.from.neType = neType;
// 根据给定的软件名取版本号 ims-r2.2312.x-ub22.deb
const matches = fileName.match(/([0-9.]+[0-9x]+)/);
if (matches) {
modalState.from.version = matches[0];
}
return true;
}
/**表单上传文件 */
function fnUploadFile(up: UploadRequestOption) {
const uploadFile = modalState.uploadFiles.find(
item => item.uid === (up.file as any).uid
);
if (!uploadFile) return;
// 发送请求
uploadFileChunk(up.file as File, 5, 'software')
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 改为完成状态
uploadFile.percent = 100;
uploadFile.status = 'done';
uploadFile.path = res.data.fileName;
// 预置到表单
const { fileName, originalFileName } = res.data;
modalState.from.name = originalFileName;
modalState.from.path = fileName;
} else {
message.error(res.msg, 3);
}
})
.catch(error => {
uploadFile.percent = 0;
uploadFile.status = 'error';
uploadFile.response = error.message;
})
.finally(() => {
modalState.confirmLoading = false;
});
}
/**表单上传前检查或转换压缩-依赖包 */
function fnBeforeUploadFileDep(file: FileType) {
if (modalState.confirmLoading) return false;
const fileName = file.name;
const suff = fileName.substring(fileName.lastIndexOf('.'));
if (!['.deb', '.rpm'].includes(suff)) {
message.error(
t('views.configManage.softwareManage.onlyAble', {
fileText: '(.deb、.rpm)',
}),
3
);
return Upload.LIST_IGNORE;
}
// 已存在同名文件
const hasItem = modalState.uploadFilesDep.find(
item => item.name === fileName
);
if (hasItem) {
notification.warning({
message: fileName,
description: t('views.ne.neSoftware.fileNameExists'),
});
return Upload.LIST_IGNORE;
}
// 取网元类型判断是否支持
let neType = '';
const neTypeIndex = fileName.indexOf('-');
if (neTypeIndex !== -1) {
neType = fileName.substring(0, neTypeIndex).toUpperCase();
}
// 依赖包类型
if (!NE_EXPAND_LIST.includes(neType)) {
notification.warning({
message: fileName,
description: t('views.ne.neSoftware.fileCheckTypeDep'),
});
return Upload.LIST_IGNORE;
}
return true;
}
/**表单上传文件-依赖包 */
function fnUploadFileDep(up: UploadRequestOption) {
const uploadFile = modalState.uploadFilesDep.find(
item => item.uid === (up.file as any).uid
);
if (!uploadFile) return;
// 发送请求
uploadFileChunk(up.file as File, 5, 'software')
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 改为完成状态
uploadFile.percent = 100;
uploadFile.status = 'done';
uploadFile.path = res.data.fileName;
} else {
message.error(res.msg, 3);
}
})
.catch(error => {
uploadFile.percent = 0;
uploadFile.status = 'error';
uploadFile.response = error.message;
})
.finally(() => {
modalState.confirmLoading = false;
});
}
/**
* 对话框弹出显示为 新增或者修改
* @param id id
*/
function fnModalVisibleByEdit(id?: string) {
if (id) {
const hide = message.loading(t('common.loading'), 0);
getNeSoftware(id)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
Object.assign(modalState.from, res.data);
modalState.title = t('views.ne.neSoftware.uploadTitle');
modalState.visibleByEdit = true;
} else {
message.error(res.msg, 3);
}
})
.finally(() => {
modalState.confirmLoading = false;
hide();
});
return;
}
modalState.title = t('views.ne.neSoftware.uploadTitle');
modalState.visibleByEdit = true;
}
/**监听是否显示,初始数据 */
watch(
() => props.visible,
val => {
if (val) fnModalVisibleByEdit(props.editId);
}
);
onMounted(() => {});
</script>
<template>
<a-modal
width="550px"
:keyboard="false"
:mask-closable="false"
:visible="modalState.visibleByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="modalStateFrom"
layout="horizontal"
:wrapper-col="{ span: 16 }"
:label-col="{ span: 8 }"
:labelWrap="true"
>
<template v-if="modalState.from.id === ''">
<a-form-item
:label="t('views.ne.neSoftware.path')"
:help="t('views.ne.neSoftware.uploadFileName')"
name="file"
v-bind="modalStateFrom.validateInfos.path"
>
<a-upload
name="file"
v-model:file-list="modalState.uploadFiles"
accept=".rpm,.deb"
list-type="text"
:max-count="1"
:show-upload-list="{
showPreviewIcon: false,
showRemoveIcon: false,
showDownloadIcon: false,
}"
:before-upload="fnBeforeUploadFile"
:custom-request="fnUploadFile"
:disabled="modalState.confirmLoading"
>
<a-button type="primary">
<template #icon>
<UploadOutlined />
</template>
{{ t('views.ne.neSoftware.upload') }}
</a-button>
</a-upload>
</a-form-item>
<a-form-item
name="dep"
:label="t('views.ne.neSoftware.dependFile')"
:help="t('views.ne.neSoftware.dependFileTip')"
>
<a-upload
name="file"
v-model:file-list="modalState.uploadFilesDep"
accept=".rpm,.deb"
list-type="text"
:multiple="true"
:max-count="5"
:show-upload-list="{
showPreviewIcon: false,
showRemoveIcon: true,
showDownloadIcon: false,
}"
:before-upload="fnBeforeUploadFileDep"
:custom-request="fnUploadFileDep"
:disabled="modalState.confirmLoading"
>
<a-button type="dashed">
<template #icon>
<UploadOutlined />
</template>
{{ t('views.ne.neSoftware.upload') }}
</a-button>
</a-upload>
</a-form-item>
</template>
<a-form-item
:label="t('views.ne.common.neType')"
name="neType"
v-bind="modalStateFrom.validateInfos.neType"
>
<a-auto-complete
v-model:value="modalState.from.neType"
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
:disabled="modalState.from.id !== ''"
>
<a-input
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="32"
:disabled="modalState.from.id !== ''"
>
</a-input>
</a-auto-complete>
</a-form-item>
<a-form-item
:label="t('views.ne.neSoftware.version')"
name="version"
v-bind="modalStateFrom.validateInfos.version"
>
<a-input
v-model:value="modalState.from.version"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
<a-form-item :label="t('common.description')" name="description">
<a-textarea
v-model:value="modalState.from.description"
:maxlength="500"
:show-count="true"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,484 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue';
import { message, Upload, notification } from 'ant-design-vue/lib';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { NE_TYPE_LIST, NE_EXPAND_LIST } from '@/constants/ne-constants';
import { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface';
import { addNeSoftware } from '@/api/ne/neSoftware';
import { FileType } from 'ant-design-vue/lib/upload/interface';
import { uploadFileChunk } from '@/api/tool/file';
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:visible']);
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
/**网元类型,指定上传 */
neType: {
type: String,
},
});
/**网元支持 */
const NE_TYPE_EXP = NE_EXPAND_LIST.concat(NE_TYPE_LIST);
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
visibleByMoreFile: boolean;
/**标题 */
title: string;
/**表单数据 */
from: {
neType: string;
name: string;
path: string;
version: string;
description: string;
}[];
/**确定按钮 loading */
confirmLoading: boolean;
/**上传文件 */
uploadFiles: any[];
/**上传文件-依赖包 */
uploadFilesDep: any[];
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
visibleByMoreFile: false,
title: '软件包文件',
from: [],
confirmLoading: false,
uploadFiles: [],
uploadFilesDep: [],
});
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
async function fnModalOk() {
if (modalState.confirmLoading) return;
if (modalState.uploadFiles.length < 1) {
message.warning({
content: t('views.ne.neSoftware.uploadNotFile'),
duration: 3,
});
return;
}
// 处理上传文件过滤
const uploadFiles = toRaw(modalState.uploadFiles);
const from = toRaw(modalState.from);
const expandFile: Record<string, string> = {};
for (const item of uploadFiles) {
if (item.status !== 'done' || !item.path) continue;
const neSoftware = from.find(s => s.name === item.name);
if (neSoftware && NE_TYPE_LIST.includes(neSoftware.neType)) {
neSoftware.path = item.path;
}
if (neSoftware && NE_EXPAND_LIST.includes(neSoftware.neType)) {
expandFile[neSoftware.neType] = item.path;
}
}
// IMS拼接拓展包
const ims = from.find(s => s.neType === 'IMS');
if (ims) {
const pkgArr = [];
if (expandFile['ADB']) {
pkgArr.push(expandFile['ADB']);
}
if (expandFile['RTPROXY']) {
pkgArr.push(expandFile['RTPROXY']);
}
if (expandFile['MF']) {
pkgArr.push(expandFile['MF']);
}
pkgArr.push(ims.path);
ims.path = pkgArr.join(',');
}
// UDM拼接拓展包
const udm = from.find(s => s.neType === 'UDM');
if (udm && expandFile['ADB']) {
udm.path = [expandFile['ADB'], udm.path].join(',');
}
// 安装带依赖包-指定网元时
if (props.neType && modalState.uploadFilesDep.length > 0) {
const neInfo = from.find(s => s.neType === props.neType);
if (neInfo) {
const depFiles = [];
for (const depFile of modalState.uploadFilesDep) {
if (depFile.status === 'done' && depFile.path) {
depFiles.push(depFile.path);
}
}
depFiles.push(neInfo.path);
neInfo.path = depFiles.join(',');
}
}
// 开始添加到软件包数据
const rows: any[] = [];
modalState.confirmLoading = true;
for (const item of from.filter(s => NE_TYPE_LIST.includes(s.neType))) {
try {
const res = await addNeSoftware(item);
if (res.code === RESULT_CODE_SUCCESS) {
rows.push(item);
} else {
message.error({
content: `${item.neType} ${res.msg}`,
duration: 3,
});
}
} catch (error) {
console.error(error);
}
}
emit('ok', rows);
fnModalCancel();
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.visibleByMoreFile = false;
modalState.confirmLoading = false;
modalState.from = [];
modalState.uploadFiles = [];
modalState.uploadFilesDep = [];
emit('cancel');
emit('update:visible', false);
}
/**表单上传前检查或转换压缩 */
function fnBeforeUploadFile(file: FileType) {
if (modalState.confirmLoading) return false;
const fileName = file.name;
const suff = fileName.substring(fileName.lastIndexOf('.'));
if (!['.deb', '.rpm'].includes(suff)) {
message.error(
t('views.configManage.softwareManage.onlyAble', {
fileText: '(.deb、.rpm)',
}),
3
);
return Upload.LIST_IGNORE;
}
// 取网元类型判断是否支持
let neType = '';
const neTypeIndex = fileName.indexOf('-');
if (neTypeIndex !== -1) {
neType = fileName.substring(0, neTypeIndex).toUpperCase();
}
if (!NE_TYPE_EXP.includes(neType)) {
notification.warning({
message: fileName,
description: t('views.ne.neSoftware.fileCheckType'),
});
return Upload.LIST_IGNORE;
}
// 根据给定的软件名取版本号 amf-r2.2404.xx-ub22.deb
let version = '';
const matches = fileName.match(/([0-9.]+[0-9x]+)/);
if (matches) {
version = matches[0];
} else {
notification.warning({
message: fileName,
description: t('views.ne.neSoftware.fileCheckVer'),
});
return Upload.LIST_IGNORE;
}
// 单网元上传
if (props.neType && props.neType !== neType) {
notification.warning({
message: fileName,
description: t('views.ne.neSoftware.fileTypeNotEq', {
txt: props.neType,
}),
});
return Upload.LIST_IGNORE;
} else {
// 多文件上传时检查是否有同类型网元包
const hasItem = modalState.from.find(item => item.neType === neType);
if (hasItem) {
notification.warning({
message: fileName,
description: t('views.ne.neSoftware.fileTypeExists'),
});
return Upload.LIST_IGNORE;
}
}
modalState.from.push({
name: fileName,
neType: neType,
version: version,
path: '', // 上传完成后提交注入
description: '',
});
return true;
}
/**表单上传文件 */
function fnUploadFile(up: UploadRequestOption) {
const uploadFile = modalState.uploadFiles.find(
item => item.uid === (up.file as any).uid
);
if (!uploadFile) return;
// 发送请求
uploadFileChunk(up.file as File, 5, 'software')
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 改为完成状态
uploadFile.percent = 100;
uploadFile.status = 'done';
uploadFile.path = res.data.fileName;
} else {
message.error(res.msg, 3);
}
})
.catch(error => {
uploadFile.percent = 0;
uploadFile.status = 'error';
uploadFile.response = error.message;
})
.finally(() => {
modalState.confirmLoading = false;
});
}
/**表单上传前检查或转换压缩-依赖包 */
function fnBeforeUploadFileDep(file: FileType) {
if (modalState.confirmLoading) return false;
const fileName = file.name;
const suff = fileName.substring(fileName.lastIndexOf('.'));
if (!['.deb', '.rpm'].includes(suff)) {
message.error(
t('views.configManage.softwareManage.onlyAble', {
fileText: '(.deb、.rpm)',
}),
3
);
return Upload.LIST_IGNORE;
}
// 已存在同名文件
const hasItem = modalState.uploadFilesDep.find(
item => item.name === fileName
);
if (hasItem) {
notification.warning({
message: fileName,
description: t('views.ne.neSoftware.fileNameExists'),
});
return Upload.LIST_IGNORE;
}
// 取网元类型判断是否支持
let neType = '';
const neTypeIndex = fileName.indexOf('-');
if (neTypeIndex !== -1) {
neType = fileName.substring(0, neTypeIndex).toUpperCase();
}
// 依赖包类型
if (!NE_EXPAND_LIST.includes(neType)) {
notification.warning({
message: fileName,
description: t('views.ne.neSoftware.fileCheckTypeDep'),
});
return Upload.LIST_IGNORE;
}
return true;
}
/**表单上传文件-依赖包 */
function fnUploadFileDep(up: UploadRequestOption) {
const uploadFile = modalState.uploadFilesDep.find(
item => item.uid === (up.file as any).uid
);
if (!uploadFile) return;
// 发送请求
uploadFileChunk(up.file as File, 5, 'software')
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 改为完成状态
uploadFile.percent = 100;
uploadFile.status = 'done';
uploadFile.path = res.data.fileName;
} else {
message.error(res.msg, 3);
}
})
.catch(error => {
uploadFile.percent = 0;
uploadFile.status = 'error';
uploadFile.response = error.message;
})
.finally(() => {
modalState.confirmLoading = false;
});
}
/**监听是否显示,初始数据 */
watch(
() => props.visible,
val => {
if (val) {
modalState.title = t('views.ne.neSoftware.uploadBatch');
modalState.visibleByMoreFile = true;
}
}
);
onMounted(() => {});
</script>
<template>
<a-modal
width="800px"
:keyboard="false"
:mask-closable="false"
:visible="modalState.visibleByMoreFile"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="modalStateFrom"
layout="horizontal"
:wrapper-col="{ span: 18 }"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<template v-if="props.neType">
<a-form-item :label="t('views.ne.common.neType')" name="type">
<a-tag color="processing">
{{ props.neType }}
</a-tag>
</a-form-item>
<a-form-item
:label="t('views.ne.neSoftware.path')"
:help="t('views.ne.neSoftware.uploadFileName')"
:required="true"
:validate-on-rule-change="false"
:validateTrigger="[]"
name="file"
>
<a-upload
name="file"
v-model:file-list="modalState.uploadFiles"
accept=".rpm,.deb"
list-type="text"
:max-count="1"
:show-upload-list="{
showPreviewIcon: false,
showRemoveIcon: false,
showDownloadIcon: false,
}"
:before-upload="fnBeforeUploadFile"
:custom-request="fnUploadFile"
:disabled="modalState.confirmLoading"
>
<a-button type="primary">
<template #icon>
<UploadOutlined />
</template>
{{ t('views.ne.neSoftware.upload') }}
</a-button>
</a-upload>
</a-form-item>
<a-form-item
name="dep"
:label="t('views.ne.neSoftware.dependFile')"
:help="t('views.ne.neSoftware.dependFileTip')"
>
<a-upload
name="file"
v-model:file-list="modalState.uploadFilesDep"
accept=".rpm,.deb"
list-type="text"
:multiple="true"
:max-count="5"
:show-upload-list="{
showPreviewIcon: false,
showRemoveIcon: true,
showDownloadIcon: false,
}"
:before-upload="fnBeforeUploadFileDep"
:custom-request="fnUploadFileDep"
:disabled="modalState.confirmLoading"
>
<a-button type="dashed">
<template #icon>
<UploadOutlined />
</template>
{{ t('views.ne.neSoftware.upload') }}
</a-button>
</a-upload>
</a-form-item>
</template>
<template v-else>
<a-form-item :label="t('common.description')">
{{
t('views.ne.neSoftware.uploadBatchMax', {
txt: NE_TYPE_EXP.length,
})
}}
<br />
{{ t('views.ne.neSoftware.uploadFileName') }}
</a-form-item>
<a-form-item
:label="t('views.ne.neSoftware.path')"
:required="true"
:validate-on-rule-change="false"
:validateTrigger="[]"
name="file"
>
<a-upload
name="file"
v-model:file-list="modalState.uploadFiles"
accept=".rpm,.deb"
list-type="text"
:multiple="true"
:max-count="NE_TYPE_EXP.length"
:show-upload-list="{
showPreviewIcon: false,
showRemoveIcon: true,
showDownloadIcon: false,
}"
:before-upload="fnBeforeUploadFile"
:custom-request="fnUploadFile"
:disabled="modalState.confirmLoading"
>
<a-button type="primary">
<template #icon>
<UploadOutlined />
</template>
{{ t('views.ne.neSoftware.upload') }}
</a-button>
</a-upload>
</a-form-item>
</template>
</a-form>
</a-modal>
</template>
<style lang="less" scoped></style>

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