Compare 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 13979 additions and 10292 deletions

No files matched your search

+1 -1
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"
+1 -1
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"
+14 -13
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"
}
}
+2
View File
@@ -4,6 +4,7 @@ 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';
@@ -11,6 +12,7 @@ import useI18n from '@/hooks/useI18n';
const { t, currentLocale } = useI18n();
const appStore = useAppStore();
dayjs.extend(advancedFormat)
dayjs.locale('zh-cn'); // 默认中文
usePrimaryColor(); // 载入用户自定义主题色
+177 -158
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 };
});
}
+1 -1
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({
+4 -21
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>) {
+3
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) {
+4 -4
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({
+139
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;
}
+6 -21
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,
});
}
-93
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;
}
+51 -10
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,
});
+8 -33
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>) {
+5 -5
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,
});
}
+5 -30
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,
});
}
+7 -2
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,
});
}
+22 -19
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;
+52
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,
},
});
}
+7
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;
}
@@ -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 d’Ivoire",
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",
};
@@ -0,0 +1,4 @@
import countryTranslations from './countries';
import interfaceTranslations from './interface';
export default { ...countryTranslations, ...interfaceTranslations };
@@ -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",
};
@@ -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: "中国香港特别行政区",
};
@@ -0,0 +1,4 @@
import countryTranslations from './countries';
import interfaceTranslations from './interface';
export default { ...countryTranslations, ...interfaceTranslations };
@@ -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: "科索沃",
};
+145
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>
+32 -19
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);
}
});
// 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>
+2 -1
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)"
>
+3 -3
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();
+15 -3
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,
+97
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>
+14 -6
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'];
+326 -64
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",
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",
+306 -44
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: "异常",
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: "参数面板",
+3 -3
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"
+2 -2
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>
+31 -16
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,18 +142,31 @@ 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) {
if (WHITE_LIST.includes(to.path)) {
@@ -168,13 +187,11 @@ router.beforeEach((to, from, next) => {
// 判断当前用户是否有角色信息
const user = useUserStore();
if (user.roles && user.roles.length === 0) {
try {
// 获取用户信息
user
.fnGetInfo()
.then(() => {
return useRouterStore().generateRoutes();
})
.then(accessRoutes => {
await user.fnGetInfo();
// 获取路由信息
const accessRoutes = await useRouterStore().generateRoutes();
// 根据后台配置生成可访问的路由表
if (accessRoutes && accessRoutes.length !== 0) {
for (const route of accessRoutes) {
@@ -186,13 +203,11 @@ router.beforeEach((to, from, next) => {
}
// 刷新替换原先路由,确保addRoutes已完成
next({ ...to, replace: true });
})
.catch(e => {
console.error(`[${to.path}]: ${e.message}`);
user.fnLogOut().finally(() => {
} catch (error: any) {
console.error(`[${to.path}]: ${error.message}`);
await user.fnLogOut();
next({ name: 'Login' });
});
});
}
} else {
next();
}
+10 -5
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;
+11 -3
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) {
// 延迟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') {
+1 -6
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();
+5 -4
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;
}
+3 -3
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
@@ -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
@@ -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 };
}
@@ -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 };
}
@@ -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>
+15 -6
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,
});
}
+63 -41
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,11 +995,11 @@ 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 }))"
>
<a-input allow-clear :placeholder="t('common.inputPlease')">
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>{{
@@ -988,6 +1009,7 @@ onMounted(() => {
</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"
@@ -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();
});
});
</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>
+21 -30
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(() => {
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;
}
+39 -31
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(() => {
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;
}
@@ -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>
@@ -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>
+567
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>
@@ -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', // 设置图例文字颜色
@@ -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
+1 -1
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;
@@ -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();
}
@@ -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;
}
@@ -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 = '';
}
+21 -2
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 {
+4
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>
+858
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>
+44 -27
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' },
@@ -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">
+19 -13
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',
-358
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>
-337
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>
+6 -8
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;
-22
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;
}
+5 -24
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;
}
+24 -27
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"
+5 -27
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;
}
-341
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>
+4 -3
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();
@@ -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,11 +345,14 @@ function fnGraphDataLoad(reload: boolean = false) {
} else {
handleRanderGraph(graphG6Dom.value, graphState.data);
}
fnGetState();
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>
@@ -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,
@@ -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>
+288
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>
+8 -6
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"
+4 -3
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>
@@ -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>
+123 -52
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>
+307
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>
+59 -73
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,29 +18,23 @@ 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 => {
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({
content: t('common.msgSuccess', {
msg: t('views.configManage.neManage.start'),
}),
key,
duration: 2,
});
message.success(t('common.operateOk'), 3);
} else {
message.error({
content: `${res.msg}`,
key: key,
duration: 2,
});
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
@@ -51,29 +47,35 @@ 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 => {
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) {
message.success({
content: t('common.msgSuccess', {
msg: t('views.configManage.neManage.restart'),
}),
key,
duration: 3,
});
// OMC自升级
if (row.neType.toUpperCase() === 'OMC') {
if (res.code === RESULT_CODE_SUCCESS) {
lockedStore.fnLock('reload');
} else {
message.error({
content: `${res.msg}`,
key: key,
duration: 3,
});
}
return;
}
message.success(t('common.operateOk'), 3);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
@@ -86,29 +88,23 @@ 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 => {
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({
content: t('common.msgSuccess', {
msg: t('views.configManage.neManage.stop'),
}),
key: key,
duration: 3,
});
message.success(t('common.operateOk'), 3);
} else {
message.error({
content: `${res.msg}`,
key: key,
duration: 3,
});
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
@@ -121,29 +117,19 @@ 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 => {
const hide = message.loading(t('common.loading'), 0);
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,
});
message.success(t('common.operateOk'), 3);
} else {
message.error({
content: `${res.msg}`,
key: key,
duration: 2,
});
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
},
});
+103 -85
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,7 +606,8 @@ onMounted(() => {
</template>
</template>
<template #expandedRowRender="{ record }">
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
<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>
@@ -613,25 +618,25 @@ onMounted(() => {
>
{{
record.serverState.online
? t('views.ne.neInfo.normalcy')
: t('views.ne.neInfo.exceptions')
? t('views.ne.common.normalcy')
: t('views.ne.common.exceptions')
}}
</a-tag>
</div>
<div>
<span>{{ t('views.ne.neInfo.version') }}:</span>
<span>{{ t('views.ne.neVersion.version') }}:</span>
<span>{{ record.serverState.version }}</span>
</div>
<div>
<span>{{ t('views.ne.neInfo.serialNum') }}:</span>
<span>{{ t('views.ne.common.serialNum') }}:</span>
<span>{{ record.serverState.sn }}</span>
</div>
<div>
<span>{{ t('views.ne.neInfo.expiryDate') }}:</span>
<span>{{ t('views.ne.common.expiryDate') }}:</span>
<span>{{ record.serverState.expire }}</span>
</div>
</div>
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
</a-col>
<a-col :offset="2" :lg="8" :md="8" :xs="8">
<a-divider orientation="left">
{{ t('views.ne.neInfo.resourceInfo') }}
</a-divider>
@@ -691,28 +696,41 @@ onMounted(() => {
:percent="record.resoures.sysDiskUsage"
/>
</div>
</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
@@ -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>
@@ -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>
+543
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>
@@ -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>
@@ -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>
@@ -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>
@@ -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> 支持 Ubuntu、Ubuntu </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-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="checkState.confirmLoading"
:loading="state.confirmLoading"
>
进行连接
{{ t('views.ne.neHost.test') }}
</a-button>
<a-button style="margin-left: 12px" @click="fnCheckInfoReset()">
重置
<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>
@@ -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;
}
+52
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>
@@ -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>
@@ -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>
Loaded 100 of 144 files, more files were not shown because too many files have changed in this diff. Show more