Compare commits
60 Commits
2.2410.4-2
...
practical-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dac294b48 | ||
|
|
20007f4732 | ||
|
|
0cb892e7f3 | ||
|
|
88d4b0cbb6 | ||
|
|
729d5518e8 | ||
|
|
cb510d9fdb | ||
|
|
f310ae4f38 | ||
|
|
f070764b69 | ||
|
|
0c96b4d130 | ||
|
|
285c9b660b | ||
|
|
2fb3467fb5 | ||
|
|
63d7d11350 | ||
|
|
2bd98540be | ||
|
|
630c63e23f | ||
|
|
ada1f388fc | ||
|
|
b9cf34714f | ||
|
|
53200d5f41 | ||
|
|
52d5b9b732 | ||
|
|
292488f20c | ||
|
|
b0b844f0f6 | ||
|
|
0819fbdb44 | ||
|
|
a88ae826cc | ||
|
|
76499d98e0 | ||
|
|
94ccb45f8c | ||
|
|
79e2b4ac26 | ||
|
|
b2510c0eb3 | ||
|
|
f977b6ea53 | ||
|
|
1e9c6a51be | ||
|
|
d68a773214 | ||
|
|
f60f26ae89 | ||
|
|
c35a5a9c33 | ||
|
|
4d755cea5d | ||
|
|
c87458bc23 | ||
|
|
cfd04ba1b6 | ||
|
|
4af02693a4 | ||
|
|
66eb27813f | ||
|
|
65c8c6f809 | ||
|
|
ecaa0ce077 | ||
|
|
99140f0641 | ||
|
|
f3c46976ae | ||
|
|
efa8764bd1 | ||
|
|
bc0e463191 | ||
|
|
45bf9fe115 | ||
|
|
4c85c61f05 | ||
|
|
318f84504f | ||
|
|
53fa36eace | ||
|
|
9123484bf5 | ||
|
|
e7e4557e96 | ||
|
|
6b1fe4a582 | ||
|
|
2610ab17ad | ||
|
|
b84a08d825 | ||
|
|
824c38593b | ||
|
|
69fb3df7ec | ||
|
|
42827796c7 | ||
|
|
eb38211e3b | ||
|
|
fd1d2a5bad | ||
|
|
2d5bfb9842 | ||
|
|
c0a7f28958 | ||
|
|
5f4e47e998 | ||
|
|
19a91936de |
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
|||||||
VITE_APP_CODE = "OMC"
|
VITE_APP_CODE = "OMC"
|
||||||
|
|
||||||
# 应用版本
|
# 应用版本
|
||||||
VITE_APP_VERSION = "2.240801"
|
VITE_APP_VERSION = "2.241123-fix"
|
||||||
|
|
||||||
# 接口基础URL地址-不带/后缀
|
# 接口基础URL地址-不带/后缀
|
||||||
VITE_API_BASE_URL = "/omc-api"
|
VITE_API_BASE_URL = "/omc-api"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
|||||||
VITE_APP_CODE = "OMC"
|
VITE_APP_CODE = "OMC"
|
||||||
|
|
||||||
# 应用版本
|
# 应用版本
|
||||||
VITE_APP_VERSION = "2.240801"
|
VITE_APP_VERSION = "2.241123-fix"
|
||||||
|
|
||||||
# 接口基础URL地址-不带/后缀
|
# 接口基础URL地址-不带/后缀
|
||||||
VITE_API_BASE_URL = "/omc-api"
|
VITE_API_BASE_URL = "/omc-api"
|
||||||
|
|||||||
26
README.md
@@ -5,19 +5,6 @@
|
|||||||
- 图标来源 [@ant-design/icons-vue](https://ant.design/components/icon)
|
- 图标来源 [@ant-design/icons-vue](https://ant.design/components/icon)
|
||||||
- 菜单图标使用自定义 iconfont `font_8d5l8fzk5b87iudi.js`图标文件
|
- 菜单图标使用自定义 iconfont `font_8d5l8fzk5b87iudi.js`图标文件
|
||||||
|
|
||||||
## 测试环境
|
|
||||||
|
|
||||||
```text
|
|
||||||
Jenkins: http://192.168.2.166:3185/
|
|
||||||
Nginx: http://192.168.2.166:3188/#/index
|
|
||||||
后端暴露端口: http://192.168.2.166:33030
|
|
||||||
|
|
||||||
|
|
||||||
新网管:192.168.5.13
|
|
||||||
旧网管:192.168.5.14
|
|
||||||
登录账户:manager/manager
|
|
||||||
```
|
|
||||||
|
|
||||||
## 程序命令
|
## 程序命令
|
||||||
|
|
||||||
项目目录下 `.env.[环境]` 文件对应环境的一些配置,启动前请检查文件内是否配置正确。
|
项目目录下 `.env.[环境]` 文件对应环境的一些配置,启动前请检查文件内是否配置正确。
|
||||||
@@ -59,16 +46,5 @@ export NODE_OPTIONS=--max-old-space-size=50000
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
https://192.168.5.23/
|
https://192.168.5.23/
|
||||||
admin
|
admin / admin
|
||||||
admin
|
|
||||||
```
|
|
||||||
|
|
||||||
## k8s
|
|
||||||
|
|
||||||
master 192.168.5.27 agtuser/admin123
|
|
||||||
|
|
||||||
https://192.168.5.27:31325/#/workloads?namespace=default
|
|
||||||
|
|
||||||
```text
|
|
||||||
eyJhbGciOiJSUzI1NiIsImtpZCI6ImZFVUhIb1puLW04M1dfSUYyRU8zWlZueXBpNUh4T0hTRVlzU19jNlVGQ0kifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLW44ZzRtIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI2M2NmYjAyNS01ZmQ0LTQ0ZTgtOTdiNC0yYWRiYWIxNzc5M2MiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.R3GRygFOjngTj-mEMBAHDeBxm3lpsXZYvC6cdTxByONtLrcMXDebwNVeKtAZ1V9qh2OrjD8n9CIygjULGPdfV6S520vjMh7Oa2q68nOyW49DNWQyYD8xLo-dQ6sX07fI7X_I3H35YUWW80jJAXjJawqIGXBSMG5intlo4tLTUSXmjCfhoQvFsgeRWu0j76pDvhMAvLPcgEXfTCi9tyL3yqJBIKONcKwmMlJeaKSR3pQk3KiibqrBO0MZclRozpke6J0ulfzTemwDDyCqBZmLsRPZ2yDd5hVBIJ9bHEcK0a25NmSFFzmd8XWQPZwg3Y4IbbY-8UhByGq0p9xS-7pGCQ
|
|
||||||
```
|
```
|
||||||
|
|||||||
37
package.json
@@ -16,43 +16,46 @@
|
|||||||
"@antv/g6": "~4.8.24",
|
"@antv/g6": "~4.8.24",
|
||||||
"@codemirror/lang-javascript": "^6.2.2",
|
"@codemirror/lang-javascript": "^6.2.2",
|
||||||
"@codemirror/lang-yaml": "^6.1.1",
|
"@codemirror/lang-yaml": "^6.1.1",
|
||||||
"@codemirror/merge": "^6.6.3",
|
"@codemirror/merge": "^6.7.2",
|
||||||
"@codemirror/theme-one-dark": "^6.1.2",
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
"@tato30/vue-pdf": "~1.9.7",
|
"@tato30/vue-pdf": "^1.11.2",
|
||||||
"@vueuse/core": "~10.10.1",
|
"@vueuse/core": "11.2.0",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"ant-design-vue": "^3.2.20",
|
"ant-design-vue": "^4.2.5",
|
||||||
"antdv-pro-layout": "~3.3.5",
|
"antdv-pro-layout": "^4.1.9",
|
||||||
"antdv-pro-modal": "^3.1.0",
|
"antdv-pro-modal": "^4.0.5",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.11",
|
"dayjs": "^1.11.11",
|
||||||
"echarts": "~5.5.0",
|
"echarts": "~5.5.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"intl-tel-input": "~23.0.12",
|
"grid-layout-plus": "^1.0.5",
|
||||||
|
"intl-tel-input": "^24.6.0",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"p-queue": "~8.0.1",
|
"p-queue": "~8.0.1",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "2.2.6",
|
||||||
"vue": "~3.3.13",
|
"vue": "^3.5.12",
|
||||||
"vue-i18n": "^9.13.1",
|
"vue-i18n": "^10.0.4",
|
||||||
"vue-router": "^4.4.0",
|
"vue-router": "^4.4.5",
|
||||||
"vue3-smooth-dnd": "^0.0.6",
|
"vue3-smooth-dnd": "^0.0.6",
|
||||||
"xlsx": "~0.18.5"
|
"xlsx": "~0.18.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/node": "^18.0.0",
|
"@types/node": "^22.7.7",
|
||||||
"@types/nprogress": "^0.2.3",
|
"@types/nprogress": "^0.2.3",
|
||||||
"@vitejs/plugin-vue": "^5.0.5",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"less": "^4.2.0",
|
"less": "^4.2.0",
|
||||||
"typescript": "~5.4.5",
|
"typescript": "^5.6.3",
|
||||||
"unplugin-vue-components": "~0.26.0",
|
"unplugin-vue-components": "^0.27.4",
|
||||||
"vite": "~5.3.1",
|
"vite": "5.4.10",
|
||||||
"vite-plugin-compression": "~0.5.1",
|
"vite-plugin-compression": "~0.5.1",
|
||||||
"vue-tsc": "~2.0.22"
|
"vue-tsc": "^2.1.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
practical_training/README.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 实训教学模块
|
||||||
|
|
||||||
|
网元固定一套,ne_id 默认使用`001`
|
||||||
|
|
||||||
|
## 静态资源
|
||||||
|
|
||||||
|
将目录下文件放置到对应目录 替换约18个文件
|
||||||
|
|
||||||
|
- i18n 对应覆盖 src\i18n
|
||||||
|
- views 对应覆盖 src\views
|
||||||
|
|
||||||
|
|
||||||
|
## 涉及文件
|
||||||
|
|
||||||
|
- src\i18n\locales\en-US.ts
|
||||||
|
- src\i18n\locales\zh-CN.ts
|
||||||
|
- src\views\monitor\topologyArchitecture\index.vue
|
||||||
|
- src\views\configManage\configParamTreeTable
|
||||||
|
- src\views\configManage\configParamApply
|
||||||
|
- src\views\ne\neInfo\index.vue
|
||||||
|
- src\plugins\auth-user.ts
|
||||||
|
- src\views\dashboard\amfUE\index.vue
|
||||||
|
- src\views\dashboard\mmeUE\index.vue
|
||||||
|
- src\views\dashboard\imsCDR\index.vue
|
||||||
|
- src\views\dashboard\smfCDR\index.vue
|
||||||
|
- src\views\dashboard\smscCDR\index.vue
|
||||||
|
- src\store\modules\user.ts
|
||||||
120
practical_training/api/pt/neConfig.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存为示例配置 (仅管理员操作)
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function ptSaveAsDefault(neType: string, neid: string) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData/saveAsDefault`,
|
||||||
|
method: 'post',
|
||||||
|
data: { neType, neid },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置为示例配置 (仅学生/教师操作)
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function ptResetAsDefault(neType: string) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData/resetAsDefault`,
|
||||||
|
method: 'post',
|
||||||
|
data: { neType },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据比较示例
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function ptContrastAsDefault(params: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData/contrast`,
|
||||||
|
params,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置数据导出Excel
|
||||||
|
* @param student 仅教师 student
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function ptExport(student: string | undefined) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData/export`,
|
||||||
|
method: 'get',
|
||||||
|
params: { student },
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 180_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置数据导出Excel (仅教师全量)
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function ptExportAll() {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData/export-all`,
|
||||||
|
method: 'get',
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 180_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置信息
|
||||||
|
* @param params 数据 {neType,paramName}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function getPtNeConfigData(params: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData`,
|
||||||
|
params,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置数据更新
|
||||||
|
* @param data 数据 {neType,paramName:"参数名",paramData:{参数},loc:"层级index仅array"}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function editPtNeConfigData(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData`,
|
||||||
|
method: 'put',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置新增(array)
|
||||||
|
* @param data 数据 {neType,paramName:"参数名",paramData:{参数},loc:"层级index"}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function addPtNeConfigData(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData`,
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置删除(array)
|
||||||
|
* @param params 数据 {neType,paramName:"参数名",loc:"层级index"}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function delPtNeConfigData(params: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData`,
|
||||||
|
method: 'delete',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
53
practical_training/api/pt/neConfigApply.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班级学生列表 (仅教师操作)
|
||||||
|
* @param params 数据 {userName}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function getPtClassStudents(params?: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigApply/students`,
|
||||||
|
params,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置应用申请列表
|
||||||
|
* @param params 数据 {neType,paramName}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function getPtNeConfigApplyList(params: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigApply/list`,
|
||||||
|
params,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置应用申请提交(仅学生操作)
|
||||||
|
* @param data 数据 { "neType": "MME", "status": "1" }
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function stuPtNeConfigApply(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigApply`,
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置应用申请状态变更(仅管理员/教师操作)
|
||||||
|
* @param data 数据 { "applyId": "1", "neType": "MME", "status": "3", "backInfo": "sgw参数错误" }
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function updatePtNeConfigApply(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigApply`,
|
||||||
|
method: 'put',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
27
practical_training/api/pt/neConfigDataLog.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置数据变更日志信息
|
||||||
|
* @param params 数据 {neType,paramName}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function getPtNeConfigDataLogList(params: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigDataLog`,
|
||||||
|
params,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置数据变更日志还原到数据
|
||||||
|
* @param data 数据 { "id": "1", "value": "old" }
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function restorePtNeConfigDataLog(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigDataLog/restore`,
|
||||||
|
method: 'put',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
42
practical_training/api/pt/user.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入用户模板数据
|
||||||
|
* @param data 表单数据对象
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function importData(data: FormData) {
|
||||||
|
return request({
|
||||||
|
url: '/pt/system/user/importData',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
dataType: 'form-data',
|
||||||
|
timeout: 180_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入用户模板下载
|
||||||
|
* @returns bolb
|
||||||
|
*/
|
||||||
|
export function importTemplate() {
|
||||||
|
return request({
|
||||||
|
url: '/pt/system/user/importTemplate',
|
||||||
|
method: 'get',
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户列表导出
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns bolb
|
||||||
|
*/
|
||||||
|
export function exportUser(query: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/pt/system/user/export',
|
||||||
|
method: 'post',
|
||||||
|
data: query,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
}
|
||||||
2201
practical_training/i18n/locales/en-US.ts
Normal file
2201
practical_training/i18n/locales/zh-CN.ts
Normal file
66
practical_training/plugins/auth-user.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { ADMIN_PERMISSION, ADMIN_ROLE_KEY } from '@/constants/admin-constants';
|
||||||
|
import useUserStore from '@/store/modules/user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否系统管理员
|
||||||
|
* @returns true | false
|
||||||
|
*/
|
||||||
|
export function isSystemAdmin(): boolean {
|
||||||
|
const userPermissions = useUserStore().permissions;
|
||||||
|
if (userPermissions.includes(ADMIN_PERMISSION)) return true;
|
||||||
|
const userRoles = useUserStore().roles;
|
||||||
|
if (userRoles.includes(ADMIN_ROLE_KEY)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只需含有其中权限
|
||||||
|
* @param role 权限字符数组
|
||||||
|
* @returns true | false
|
||||||
|
*/
|
||||||
|
export function hasPermissions(permissions: string[]): boolean {
|
||||||
|
if (!permissions || permissions.length === 0) return false;
|
||||||
|
const userPermissions = useUserStore().permissions;
|
||||||
|
if (!userPermissions || userPermissions.length === 0) return false;
|
||||||
|
if (userPermissions.includes(ADMIN_PERMISSION)) return true;
|
||||||
|
return permissions.some(p => userPermissions.some(up => up === p));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同时匹配其中权限
|
||||||
|
* @param role 权限字符数组
|
||||||
|
* @returns true | false
|
||||||
|
*/
|
||||||
|
export function matchPermissions(permissions: string[]): boolean {
|
||||||
|
if (!permissions || permissions.length === 0) return false;
|
||||||
|
const userPermissions = useUserStore().permissions;
|
||||||
|
if (!userPermissions || userPermissions.length === 0) return false;
|
||||||
|
if (userPermissions.includes(ADMIN_PERMISSION)) return true;
|
||||||
|
return permissions.every(p => userPermissions.some(up => up === p));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只需含有其中角色
|
||||||
|
* @param role 角色字符数组
|
||||||
|
* @returns true | false
|
||||||
|
*/
|
||||||
|
export function hasRoles(roles: string[]): boolean {
|
||||||
|
if (!roles || roles.length === 0) return false;
|
||||||
|
const userRoles = useUserStore().roles;
|
||||||
|
if (!userRoles || userRoles.length === 0) return false;
|
||||||
|
if (userRoles.includes(ADMIN_ROLE_KEY)) return true;
|
||||||
|
return roles.some(r => userRoles.some(ur => ur === r));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同时匹配其中角色
|
||||||
|
* @param role 角色字符数组
|
||||||
|
* @returns true | false
|
||||||
|
*/
|
||||||
|
export function matchRoles(roles: string[]): boolean {
|
||||||
|
if (!roles || roles.length === 0) return false;
|
||||||
|
const userRoles = useUserStore().roles;
|
||||||
|
if (!userRoles || userRoles.length === 0) return false;
|
||||||
|
if (userRoles.includes(ADMIN_ROLE_KEY)) return true;
|
||||||
|
return roles.every(r => userRoles.some(ur => ur === r));
|
||||||
|
}
|
||||||
171
practical_training/store/modules/user.ts
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import defaultAvatar from '@/assets/images/default_avatar.png';
|
||||||
|
import useLayoutStore from './layout';
|
||||||
|
import { login, logout, getInfo } from '@/api/login';
|
||||||
|
import { setToken, removeToken } from '@/plugins/auth-token';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { TOKEN_RESPONSE_FIELD } from '@/constants/token-constants';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { parseUrlPath } from '@/plugins/file-static-url';
|
||||||
|
|
||||||
|
/**用户信息类型 */
|
||||||
|
type UserInfo = {
|
||||||
|
/**用户ID */
|
||||||
|
userId: string;
|
||||||
|
/**登录账号 */
|
||||||
|
userName: string;
|
||||||
|
/**用户角色 字符串数组 */
|
||||||
|
roles: string[];
|
||||||
|
/**用户权限 字符串数组 */
|
||||||
|
permissions: string[];
|
||||||
|
/**用户头像 */
|
||||||
|
avatar: string;
|
||||||
|
/**用户昵称 */
|
||||||
|
nickName: string;
|
||||||
|
/**用户手机号 */
|
||||||
|
phonenumber: string;
|
||||||
|
/**用户邮箱 */
|
||||||
|
email: string;
|
||||||
|
/**用户性别 */
|
||||||
|
sex: string | undefined;
|
||||||
|
/**其他信息 */
|
||||||
|
profile: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const useUserStore = defineStore('user', {
|
||||||
|
state: (): UserInfo => ({
|
||||||
|
userId: '',
|
||||||
|
userName: '',
|
||||||
|
roles: [],
|
||||||
|
permissions: [],
|
||||||
|
avatar: '',
|
||||||
|
nickName: '',
|
||||||
|
phonenumber: '',
|
||||||
|
email: '',
|
||||||
|
sex: undefined,
|
||||||
|
profile: {},
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
/**
|
||||||
|
* 获取正确头像地址
|
||||||
|
* @param state 内部属性不用传入
|
||||||
|
* @returns 头像地址url
|
||||||
|
*/
|
||||||
|
getAvatar(state) {
|
||||||
|
if (!state.avatar) {
|
||||||
|
return defaultAvatar;
|
||||||
|
}
|
||||||
|
return parseUrlPath(state.avatar);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取基础信息属性
|
||||||
|
* @param state 内部属性不用传入
|
||||||
|
* @returns 基础信息
|
||||||
|
*/
|
||||||
|
getBaseInfo(state) {
|
||||||
|
return {
|
||||||
|
nickName: state.nickName,
|
||||||
|
phonenumber: state.phonenumber,
|
||||||
|
email: state.email,
|
||||||
|
sex: state.sex,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
/**
|
||||||
|
* 更新基础信息属性
|
||||||
|
* @param data 变更信息
|
||||||
|
*/
|
||||||
|
setBaseInfo(data: Record<string, any>) {
|
||||||
|
this.nickName = data.nickName;
|
||||||
|
this.phonenumber = data.phonenumber;
|
||||||
|
this.email = data.email;
|
||||||
|
this.sex = data.sex;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 更新头像
|
||||||
|
* @param avatar 上传后的地址
|
||||||
|
*/
|
||||||
|
setAvatar(avatar: string) {
|
||||||
|
this.avatar = avatar;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取正确头像地址
|
||||||
|
* @param avatar
|
||||||
|
*/
|
||||||
|
fnAvatar(avatar: string) {
|
||||||
|
if (!avatar) {
|
||||||
|
return defaultAvatar;
|
||||||
|
}
|
||||||
|
return parseUrlPath(avatar);
|
||||||
|
},
|
||||||
|
// 登录
|
||||||
|
async fnLogin(loginBody: Record<string, string>) {
|
||||||
|
const res = await login(loginBody);
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||||
|
const token = res.data[TOKEN_RESPONSE_FIELD];
|
||||||
|
setToken(token);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
// 获取用户信息
|
||||||
|
async fnGetInfo() {
|
||||||
|
const res = await getInfo();
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||||
|
const { user, roles, permissions } = res.data;
|
||||||
|
this.userId = user.userId;
|
||||||
|
// 登录账号
|
||||||
|
this.userName = user.userName;
|
||||||
|
// 用户头像
|
||||||
|
this.avatar = user.avatar;
|
||||||
|
// 基础信息
|
||||||
|
this.nickName = user.nickName;
|
||||||
|
this.phonenumber = user.phonenumber;
|
||||||
|
this.email = user.email;
|
||||||
|
this.sex = user.sex;
|
||||||
|
|
||||||
|
// 验证返回的roles是否是一个非空数组
|
||||||
|
if (Array.isArray(roles) && roles.length > 0) {
|
||||||
|
this.roles = roles;
|
||||||
|
this.permissions = permissions;
|
||||||
|
} else {
|
||||||
|
this.roles = ['ROLE_DEFAULT'];
|
||||||
|
this.permissions = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 水印文字信息=用户昵称 手机号
|
||||||
|
let waterMarkContent = this.userName;
|
||||||
|
if (this.phonenumber) {
|
||||||
|
waterMarkContent = `${this.userName} ${this.phonenumber}`;
|
||||||
|
}
|
||||||
|
// useLayoutStore().changeWaterMark(waterMarkContent);
|
||||||
|
useLayoutStore().changeWaterMark('');
|
||||||
|
// 学生布局用不一样的
|
||||||
|
if (this.roles.includes('student')) {
|
||||||
|
useLayoutStore().changeConf('layout', 'side');
|
||||||
|
useLayoutStore().changeConf('menuTheme', 'dark');
|
||||||
|
useLayoutStore().changeConf('tabRender', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 网络错误时退出登录状态
|
||||||
|
if (res.code === 0) {
|
||||||
|
removeToken();
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
// 退出系统
|
||||||
|
async fnLogOut() {
|
||||||
|
try {
|
||||||
|
await logout();
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
this.roles = [];
|
||||||
|
this.permissions = [];
|
||||||
|
removeToken();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default useUserStore;
|
||||||
714
practical_training/views/configManage/configParamApply/index.vue
Normal file
@@ -0,0 +1,714 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, onMounted, toRaw, computed } from 'vue';
|
||||||
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
|
import { ProModal } from 'antdv-pro-modal';
|
||||||
|
import { Form, message, Modal } from 'ant-design-vue/es';
|
||||||
|
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||||
|
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||||
|
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||||
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
import useNeInfoStore from '@/store/modules/neinfo';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import {
|
||||||
|
getPtNeConfigApplyList,
|
||||||
|
stuPtNeConfigApply,
|
||||||
|
updatePtNeConfigApply,
|
||||||
|
} from '@/api/pt/neConfigApply';
|
||||||
|
import { hasRoles } from '@/plugins/auth-user';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { getDict } = useDictStore();
|
||||||
|
const neInfoStore = useNeInfoStore();
|
||||||
|
|
||||||
|
/**字典数据 */
|
||||||
|
let dict: {
|
||||||
|
/**配置申请应用状态 */
|
||||||
|
ptConfigApplyStatus: DictType[];
|
||||||
|
} = reactive({
|
||||||
|
ptConfigApplyStatus: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**查询参数 */
|
||||||
|
let queryParams = reactive({
|
||||||
|
/**网元类型 */
|
||||||
|
neType: '',
|
||||||
|
/**申请人 */
|
||||||
|
createBy: '',
|
||||||
|
/**状态 */
|
||||||
|
status: undefined,
|
||||||
|
/**当前页数 */
|
||||||
|
pageNum: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**查询参数重置 */
|
||||||
|
function fnQueryReset() {
|
||||||
|
queryParams = Object.assign(queryParams, {
|
||||||
|
neType: '',
|
||||||
|
createBy: '',
|
||||||
|
status: undefined,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
tablePagination.current = 1;
|
||||||
|
tablePagination.pageSize = 20;
|
||||||
|
fnGetList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格状态类型 */
|
||||||
|
type TabeStateType = {
|
||||||
|
/**加载等待 */
|
||||||
|
loading: boolean;
|
||||||
|
/**紧凑型 */
|
||||||
|
size: SizeType;
|
||||||
|
/**搜索栏 */
|
||||||
|
seached: boolean;
|
||||||
|
/**记录数据 */
|
||||||
|
data: object[];
|
||||||
|
/**勾选记录 */
|
||||||
|
selectedRowKeys: (string | number)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**表格状态 */
|
||||||
|
let tableState: TabeStateType = reactive({
|
||||||
|
loading: false,
|
||||||
|
size: 'middle',
|
||||||
|
seached: true,
|
||||||
|
data: [],
|
||||||
|
selectedRowKeys: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格字段列 */
|
||||||
|
let tableColumns: ColumnsType = [
|
||||||
|
{
|
||||||
|
title: t('common.rowId'),
|
||||||
|
dataIndex: 'id',
|
||||||
|
align: 'center',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.ne.common.neType'),
|
||||||
|
dataIndex: 'neType',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '申请人',
|
||||||
|
dataIndex: 'createBy',
|
||||||
|
align: 'left',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '申请时间',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
if (+opt.value <= 0) return '';
|
||||||
|
return parseDateToStr(+opt.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '处理人',
|
||||||
|
dataIndex: 'updateBy',
|
||||||
|
align: 'left',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '处理时间',
|
||||||
|
dataIndex: 'updateTime',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
if (+opt.value <= 0) return '';
|
||||||
|
return parseDateToStr(+opt.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('common.operate'),
|
||||||
|
key: 'id',
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**表格分页器参数 */
|
||||||
|
let tablePagination = reactive({
|
||||||
|
/**当前页数 */
|
||||||
|
current: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
/**默认的每页条数 */
|
||||||
|
defaultPageSize: 20,
|
||||||
|
/**指定每页可以显示多少条 */
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
/**只有一页时是否隐藏分页器 */
|
||||||
|
hideOnSinglePage: false,
|
||||||
|
/**是否可以快速跳转至某页 */
|
||||||
|
showQuickJumper: true,
|
||||||
|
/**是否可以改变 pageSize */
|
||||||
|
showSizeChanger: true,
|
||||||
|
/**数据总数 */
|
||||||
|
total: 0,
|
||||||
|
showTotal: (total: number) =>
|
||||||
|
t('common.tablePaginationTotal', { total: total }),
|
||||||
|
onChange: (page: number, pageSize: number) => {
|
||||||
|
tablePagination.current = page;
|
||||||
|
tablePagination.pageSize = pageSize;
|
||||||
|
queryParams.pageNum = page;
|
||||||
|
queryParams.pageSize = pageSize;
|
||||||
|
fnGetList();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格紧凑型变更操作 */
|
||||||
|
function fnTableSize({ key }: MenuInfo) {
|
||||||
|
tableState.size = key as SizeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格多选 */
|
||||||
|
function fnTableSelectedRowKeys(keys: (string | number)[], infos: any) {
|
||||||
|
const arr = [];
|
||||||
|
for (const item of infos) {
|
||||||
|
if (item.status === '0') {
|
||||||
|
arr.push(item.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableState.selectedRowKeys = arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**查询列表, pageNum初始页数 */
|
||||||
|
function fnGetList(pageNum?: number) {
|
||||||
|
if (tableState.loading) return;
|
||||||
|
tableState.loading = true;
|
||||||
|
if (pageNum) {
|
||||||
|
queryParams.pageNum = pageNum;
|
||||||
|
}
|
||||||
|
getPtNeConfigApplyList(toRaw(queryParams)).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
// 取消勾选
|
||||||
|
if (tableState.selectedRowKeys.length > 0) {
|
||||||
|
tableState.selectedRowKeys = [];
|
||||||
|
}
|
||||||
|
tablePagination.total = res.total;
|
||||||
|
tableState.data = res.rows;
|
||||||
|
if (
|
||||||
|
tablePagination.total <=
|
||||||
|
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||||
|
queryParams.pageNum !== 1
|
||||||
|
) {
|
||||||
|
tableState.loading = false;
|
||||||
|
fnGetList(queryParams.pageNum - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableState.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**对话框对象信息状态类型 */
|
||||||
|
type ModalStateType = {
|
||||||
|
/**详情框是否显示 */
|
||||||
|
openByView: boolean;
|
||||||
|
/**新增框或修改框是否显示 */
|
||||||
|
openByEdit: boolean;
|
||||||
|
/**标题 */
|
||||||
|
title: string;
|
||||||
|
/**表单数据 */
|
||||||
|
from: Record<string, any>;
|
||||||
|
/**确定按钮 loading */
|
||||||
|
confirmLoading: boolean;
|
||||||
|
/**cron生成框是否显示 */
|
||||||
|
openByCron: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**对话框对象信息状态 */
|
||||||
|
let modalState: ModalStateType = reactive({
|
||||||
|
openByView: false,
|
||||||
|
openByEdit: false,
|
||||||
|
title: '任务',
|
||||||
|
from: {
|
||||||
|
id: undefined,
|
||||||
|
createBy: '',
|
||||||
|
createTime: 0,
|
||||||
|
updateBy: '',
|
||||||
|
updateTime: 0,
|
||||||
|
neType: 'MME',
|
||||||
|
status: '0',
|
||||||
|
backInfo: '',
|
||||||
|
},
|
||||||
|
confirmLoading: false,
|
||||||
|
openByCron: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**对话框内表单属性和校验规则 */
|
||||||
|
const modalStateFrom = Form.useForm(
|
||||||
|
modalState.from,
|
||||||
|
reactive({
|
||||||
|
status: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('common.selectPlease'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出显示为 查看
|
||||||
|
* @param jobId 任务id
|
||||||
|
*/
|
||||||
|
function fnModalVisibleByVive(row: Record<string, any>) {
|
||||||
|
modalState.from = Object.assign(modalState.from, row);
|
||||||
|
modalState.title = '查看';
|
||||||
|
modalState.openByView = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出显示为 编辑
|
||||||
|
* @param jobId 任务id
|
||||||
|
*/
|
||||||
|
function fnModalVisibleByEdit(row: Record<string, any>) {
|
||||||
|
Object.assign(modalState.from, row);
|
||||||
|
modalState.from.status = '3';
|
||||||
|
modalState.title = '编辑状态';
|
||||||
|
modalState.openByEdit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出确认执行函数
|
||||||
|
* 进行表达规则校验
|
||||||
|
*/
|
||||||
|
function fnModalOk() {
|
||||||
|
modalStateFrom
|
||||||
|
.validate()
|
||||||
|
.then(() => {
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
const from = toRaw(modalState.from);
|
||||||
|
updatePtNeConfigApply({
|
||||||
|
applyId: from.id,
|
||||||
|
neType: from.neType,
|
||||||
|
status: from.status,
|
||||||
|
backInfo: from.backInfo,
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
modalState.openByEdit = false;
|
||||||
|
modalStateFrom.resetFields();
|
||||||
|
fnGetList();
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出关闭执行函数
|
||||||
|
* 进行表达规则校验
|
||||||
|
*/
|
||||||
|
function fnModalCancel() {
|
||||||
|
modalState.openByEdit = false;
|
||||||
|
modalState.openByView = false;
|
||||||
|
modalState.from = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**批量退回 */
|
||||||
|
function fnRecordBack(row?: Record<string, any>) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: row
|
||||||
|
? '确认要撤回配置应用申请吗?'
|
||||||
|
: '确认要批量退回学生的配置应用申请吗?',
|
||||||
|
onOk() {
|
||||||
|
let result: any;
|
||||||
|
if (row) {
|
||||||
|
result = stuPtNeConfigApply({ neType: row.neType, status: '1' });
|
||||||
|
} else {
|
||||||
|
result = updatePtNeConfigApply({
|
||||||
|
status: '3',
|
||||||
|
backId: tableState.selectedRowKeys.join(','),
|
||||||
|
backInfo: '请重新检查配置',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
result.then((res: any) => {
|
||||||
|
fnGetList();
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**应用状态 */
|
||||||
|
const applyStatus = computed(() => {
|
||||||
|
if (hasRoles(['student'])) {
|
||||||
|
return dict.ptConfigApplyStatus.filter(s => ['0', '1'].includes(s.value));
|
||||||
|
}
|
||||||
|
let data = dict.ptConfigApplyStatus;
|
||||||
|
if (modalState.openByEdit && modalState.from.id) {
|
||||||
|
data = dict.ptConfigApplyStatus.filter(s => ['2', '3'].includes(s.value));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始字典数据
|
||||||
|
Promise.allSettled([getDict('pt_config_apply_status')]).then(resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
dict.ptConfigApplyStatus = resArr[0].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 获取网元列表
|
||||||
|
neInfoStore.fnNelist().finally(() => {
|
||||||
|
// 获取列表数据
|
||||||
|
fnGetList();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageContainer>
|
||||||
|
<a-card
|
||||||
|
v-show="tableState.seached"
|
||||||
|
:bordered="false"
|
||||||
|
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||||
|
>
|
||||||
|
<!-- 表格搜索栏 -->
|
||||||
|
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.configManage.license.neType')"
|
||||||
|
name="neType "
|
||||||
|
>
|
||||||
|
<a-auto-complete
|
||||||
|
v-model:value="queryParams.neType"
|
||||||
|
:options="neInfoStore.getNeSelectOtions"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('views.configManage.license.neTypePlease')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item label="状态" name="status">
|
||||||
|
<a-select
|
||||||
|
v-model:value="queryParams.status"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.selectPlease')"
|
||||||
|
:options="applyStatus"
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item>
|
||||||
|
<a-space :size="8">
|
||||||
|
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||||
|
<template #icon><SearchOutlined /></template>
|
||||||
|
{{ t('common.search') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="default" @click.prevent="fnQueryReset">
|
||||||
|
<template #icon><ClearOutlined /></template>
|
||||||
|
{{ t('common.reset') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||||
|
<!-- 插槽-卡片左侧侧 -->
|
||||||
|
<template #title>
|
||||||
|
<div class="button-container">
|
||||||
|
<a-button
|
||||||
|
type="default"
|
||||||
|
danger
|
||||||
|
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||||
|
@click.prevent="fnRecordBack()"
|
||||||
|
v-roles:has="['admin', 'teacher']"
|
||||||
|
>
|
||||||
|
<template #icon><DeleteOutlined /></template>
|
||||||
|
批量退回
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 插槽-卡片右侧 -->
|
||||||
|
<template #extra>
|
||||||
|
<div class="button-container">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.searchBarText') }}</template>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="tableState.seached"
|
||||||
|
:checked-children="t('common.switch.show')"
|
||||||
|
:un-checked-children="t('common.switch.hide')"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.reloadText') }}</template>
|
||||||
|
<a-button type="text" @click.prevent="fnGetList()">
|
||||||
|
<template #icon><ReloadOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip placement="topRight">
|
||||||
|
<template #title>{{ t('common.sizeText') }}</template>
|
||||||
|
<a-dropdown placement="bottomRight" trigger="click">
|
||||||
|
<a-button type="text">
|
||||||
|
<template #icon><ColumnHeightOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu
|
||||||
|
:selected-keys="[tableState.size as string]"
|
||||||
|
@click="fnTableSize"
|
||||||
|
>
|
||||||
|
<a-menu-item key="default">
|
||||||
|
{{ t('common.size.default') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="middle">
|
||||||
|
{{ t('common.size.middle') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="small">
|
||||||
|
{{ t('common.size.small') }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 表格列表 -->
|
||||||
|
<a-table
|
||||||
|
class="table"
|
||||||
|
row-key="id"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:loading="tableState.loading"
|
||||||
|
:data-source="tableState.data"
|
||||||
|
:size="tableState.size"
|
||||||
|
:scroll="{ x: tableColumns.length * 120 }"
|
||||||
|
:pagination="tablePagination"
|
||||||
|
:row-selection="{
|
||||||
|
type: 'checkbox',
|
||||||
|
selectedRowKeys: tableState.selectedRowKeys,
|
||||||
|
onChange: fnTableSelectedRowKeys,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'status'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.ptConfigApplyStatus"
|
||||||
|
:value="record.status"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'id'">
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.viewText') }}</template>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
@click.prevent="fnModalVisibleByVive(record)"
|
||||||
|
>
|
||||||
|
<template #icon><ProfileOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip v-if="record.status === '0' && hasRoles(['student'])">
|
||||||
|
<template #title>撤回</template>
|
||||||
|
<a-button type="link" @click.prevent="fnRecordBack(record)">
|
||||||
|
<template #icon><RollbackOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip
|
||||||
|
v-if="record.status === '0' && hasRoles(['admin', 'teacher'])"
|
||||||
|
>
|
||||||
|
<template #title>{{ t('common.editText') }}</template>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
@click.prevent="fnModalVisibleByEdit(record)"
|
||||||
|
>
|
||||||
|
<template #icon><FormOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<!-- 详情框 -->
|
||||||
|
<ProModal
|
||||||
|
:drag="true"
|
||||||
|
:width="800"
|
||||||
|
:open="modalState.openByView"
|
||||||
|
:title="modalState.title"
|
||||||
|
@cancel="fnModalCancel"
|
||||||
|
>
|
||||||
|
<a-form layout="horizontal" :label-col="{ span: 6 }" :label-wrap="true">
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item :label="t('views.ne.common.neType')" name="neType">
|
||||||
|
{{ modalState.from.neType }}
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item label="状态" name="status">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.ptConfigApplyStatus"
|
||||||
|
:value="modalState.from.status"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item label="申请人" name="createBy">
|
||||||
|
{{ modalState.from.createBy }}
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item label="申请时间" name="createTime">
|
||||||
|
{{ parseDateToStr(+modalState.from.createTime) }}
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-row v-if="modalState.from.status !== '0'">
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item label="处理人" name="updateBy">
|
||||||
|
{{ modalState.from.updateBy }}
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item label="处理时间" name="updateTime">
|
||||||
|
{{ parseDateToStr(+modalState.from.updateTime) }}
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
v-if="modalState.from.status === '3'"
|
||||||
|
label="退回说明"
|
||||||
|
name="backInfo"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
{{ modalState.from.backInfo }}
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
<template #footer>
|
||||||
|
<a-button key="cancel" @click="fnModalCancel">
|
||||||
|
{{ t('common.close') }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</ProModal>
|
||||||
|
|
||||||
|
<!-- 新增框或修改框 -->
|
||||||
|
<ProModal
|
||||||
|
:drag="true"
|
||||||
|
:width="800"
|
||||||
|
:open="modalState.openByEdit"
|
||||||
|
:title="modalState.title"
|
||||||
|
:confirm-loading="modalState.confirmLoading"
|
||||||
|
:destroyOnClose="true"
|
||||||
|
@ok="fnModalOk"
|
||||||
|
@cancel="fnModalCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
name="modalStateFrom"
|
||||||
|
layout="horizontal"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item :label="t('views.ne.common.neType')" name="neType">
|
||||||
|
{{ modalState.from.neType }}
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item label="申请人" name="createBy">
|
||||||
|
{{ modalState.from.createBy }}
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item label="申请时间" name="createTime">
|
||||||
|
{{ parseDateToStr(+modalState.from.createTime) }}
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
label="状态"
|
||||||
|
name="status"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
v-bind="modalStateFrom.validateInfos.status"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="modalState.from.status"
|
||||||
|
:placeholder="t('common.selectPlease')"
|
||||||
|
:options="applyStatus"
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
v-if="modalState.from.status === '3'"
|
||||||
|
label="退回说明"
|
||||||
|
name="backInfo"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="modalState.from.backInfo"
|
||||||
|
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||||
|
:maxlength="400"
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</ProModal>
|
||||||
|
</PageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.table :deep(.ant-pagination) {
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, onMounted, toRaw, watch } from 'vue';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import CodemirrorEditeDiff from '@/components/CodemirrorEditeDiff/index.vue';
|
||||||
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import {
|
||||||
|
getPtNeConfigDataLogList,
|
||||||
|
restorePtNeConfigDataLog,
|
||||||
|
} from '@/api/pt/neConfigDataLog';
|
||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const emit = defineEmits(['ok', 'cancel', 'update:open']);
|
||||||
|
const props = defineProps({
|
||||||
|
open: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/**网元类型 */
|
||||||
|
neType: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
/**参数名 */
|
||||||
|
paramName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
/**学生用户账号 */
|
||||||
|
student: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { getDict } = useDictStore();
|
||||||
|
|
||||||
|
/**字典数据 */
|
||||||
|
let dict: {
|
||||||
|
/**业务类型 */
|
||||||
|
sysBusinessType: DictType[];
|
||||||
|
} = reactive({
|
||||||
|
sysBusinessType: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**对话框对象信息状态类型 */
|
||||||
|
type StateType = {
|
||||||
|
/**新增框或修改框是否显示 */
|
||||||
|
openByList: boolean;
|
||||||
|
/**差异比较框是否显示 */
|
||||||
|
openByDiff: boolean;
|
||||||
|
/**标题 */
|
||||||
|
title: string;
|
||||||
|
/**加载状态 */
|
||||||
|
loading: boolean;
|
||||||
|
/**数据 */
|
||||||
|
data: Record<string, any>[];
|
||||||
|
/**差异数据 */
|
||||||
|
dataDiff: Record<string, any>;
|
||||||
|
/**确定按钮 loading */
|
||||||
|
confirmLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**对话框对象信息状态 */
|
||||||
|
let state: StateType = reactive({
|
||||||
|
openByList: false,
|
||||||
|
openByDiff: false,
|
||||||
|
title: '操作参数名称-学生账号',
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
dataDiff: {},
|
||||||
|
confirmLoading: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
function onClose() {
|
||||||
|
state.loading = false;
|
||||||
|
state.openByList = false;
|
||||||
|
state.openByDiff = false;
|
||||||
|
state.data = [];
|
||||||
|
state.dataDiff = {};
|
||||||
|
emit('cancel');
|
||||||
|
emit('update:open', false);
|
||||||
|
queryParams = {
|
||||||
|
neType: '',
|
||||||
|
paramName: '',
|
||||||
|
student: '',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**查询参数 */
|
||||||
|
let queryParams = reactive({
|
||||||
|
/**网元类型 */
|
||||||
|
neType: '',
|
||||||
|
/**可用属性值 */
|
||||||
|
paramName: '',
|
||||||
|
/**学生账号 */
|
||||||
|
student: '',
|
||||||
|
/**当前页数 */
|
||||||
|
pageNum: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**查询列表, pageNum初始页数 */
|
||||||
|
function fnGetList(pageNum?: number) {
|
||||||
|
if (state.loading) return;
|
||||||
|
state.loading = true;
|
||||||
|
if (pageNum) {
|
||||||
|
queryParams.pageNum = pageNum;
|
||||||
|
if (pageNum === 1) state.data = [];
|
||||||
|
}
|
||||||
|
getPtNeConfigDataLogList(toRaw(queryParams)).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||||
|
// tablePagination.total = res.total;
|
||||||
|
state.data = state.data.concat(res.rows);
|
||||||
|
// 去首个做标题
|
||||||
|
if (queryParams.pageNum === 1 && state.data.length > 0) {
|
||||||
|
const item = state.data[0];
|
||||||
|
state.title = `${item.paramDisplay} - ${item.createBy}`;
|
||||||
|
}
|
||||||
|
if (state.data.length <= res.total && res.rows.length > 0) {
|
||||||
|
queryParams.pageNum++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**差异比较框打开 */
|
||||||
|
function fnMergeCellOpen(row: Record<string, any>) {
|
||||||
|
state.dataDiff = row;
|
||||||
|
state.dataDiff.paramJsonOld = JSON.stringify(
|
||||||
|
JSON.parse(state.dataDiff.paramJsonOld),
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
state.dataDiff.paramJsonNew = JSON.stringify(
|
||||||
|
JSON.parse(state.dataDiff.paramJsonNew),
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
state.openByDiff = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**差异比较框关闭 */
|
||||||
|
function fnMergeCellClose() {
|
||||||
|
state.openByDiff = false;
|
||||||
|
state.dataDiff = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**差异比较还原 */
|
||||||
|
function fnMergeCellRestore(value: 'old' | 'new') {
|
||||||
|
if (state.confirmLoading) return;
|
||||||
|
const id = state.dataDiff.id;
|
||||||
|
restorePtNeConfigDataLog({ id, value })
|
||||||
|
.then((res: any) => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
fnMergeCellClose();
|
||||||
|
fnGetList(1);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
state.confirmLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**监听是否显示,初始数据 */
|
||||||
|
watch(
|
||||||
|
() => props.open,
|
||||||
|
val => {
|
||||||
|
if (val) {
|
||||||
|
if (props.neType && props.paramName) {
|
||||||
|
state.title = '';
|
||||||
|
state.openByList = true;
|
||||||
|
// 根据条件查询数据
|
||||||
|
queryParams.neType = props.neType;
|
||||||
|
queryParams.paramName = props.paramName;
|
||||||
|
if (props.student) {
|
||||||
|
queryParams.student = props.student;
|
||||||
|
}
|
||||||
|
fnGetList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始字典数据
|
||||||
|
Promise.allSettled([getDict('sys_oper_type')]).then(resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
dict.sysBusinessType = resArr[0].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<a-drawer
|
||||||
|
:width="500"
|
||||||
|
:title="state.title"
|
||||||
|
placement="right"
|
||||||
|
:open="state.openByList"
|
||||||
|
@close="onClose"
|
||||||
|
>
|
||||||
|
<a-list
|
||||||
|
class="demo-loadmore-list"
|
||||||
|
item-layout="horizontal"
|
||||||
|
:data-source="state.data"
|
||||||
|
>
|
||||||
|
<template #loadMore>
|
||||||
|
<div
|
||||||
|
:style="{
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: '12px',
|
||||||
|
height: '32px',
|
||||||
|
lineHeight: '32px',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<a-button @click="fnGetList()" :loading="state.loading">
|
||||||
|
{{ t('views.configManage.configParamForm.ptDiffLoad') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #renderItem="{ item }">
|
||||||
|
<a-list-item>
|
||||||
|
<template #actions>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>
|
||||||
|
{{ t('views.configManage.configParamForm.ptDiffMerge') }}
|
||||||
|
</template>
|
||||||
|
<a-button type="primary" @click.prevent="fnMergeCellOpen(item)">
|
||||||
|
<template #icon><MergeCellsOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
<a-list-item-meta>
|
||||||
|
<template #title>
|
||||||
|
<DictTag
|
||||||
|
:options="dict.sysBusinessType"
|
||||||
|
:value="item.operaType"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #description>
|
||||||
|
{{ parseDateToStr(item.createTime) }}
|
||||||
|
</template>
|
||||||
|
</a-list-item-meta>
|
||||||
|
</a-list-item>
|
||||||
|
</template>
|
||||||
|
</a-list>
|
||||||
|
</a-drawer>
|
||||||
|
|
||||||
|
<a-modal
|
||||||
|
:width="800"
|
||||||
|
:destroyOnClose="true"
|
||||||
|
:mask-closable="false"
|
||||||
|
v-model:open="state.openByDiff"
|
||||||
|
:footer="null"
|
||||||
|
:body-style="{ padding: 0, maxHeight: '650px', 'overflow-y': 'auto' }"
|
||||||
|
@ok="fnMergeCellClose()"
|
||||||
|
@cancel="fnMergeCellClose()"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<DictTag
|
||||||
|
:options="dict.sysBusinessType"
|
||||||
|
:value="state.dataDiff.operaType"
|
||||||
|
/>
|
||||||
|
{{ parseDateToStr(state.dataDiff.createTime) }}
|
||||||
|
</template>
|
||||||
|
<div class="diffBack">
|
||||||
|
<div>
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
:loading="state.confirmLoading"
|
||||||
|
@click.prevent="fnMergeCellRestore('old')"
|
||||||
|
>
|
||||||
|
<template #icon><MergeCellsOutlined /></template>
|
||||||
|
{{ t('views.configManage.configParamForm.ptDiffRest') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
:loading="state.confirmLoading"
|
||||||
|
@click.prevent="fnMergeCellRestore('new')"
|
||||||
|
>
|
||||||
|
<template #icon><MergeCellsOutlined /></template>
|
||||||
|
{{ t('views.configManage.configParamForm.ptDiffRest') }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CodemirrorEditeDiff
|
||||||
|
:old-area="state.dataDiff.paramJsonOld"
|
||||||
|
:new-area="state.dataDiff.paramJsonNew"
|
||||||
|
></CodemirrorEditeDiff>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.diffBack {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
& > div:first-child {
|
||||||
|
flex: 1;
|
||||||
|
background: #fa9;
|
||||||
|
}
|
||||||
|
& > div:last-child {
|
||||||
|
flex: 1;
|
||||||
|
background: #8f8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,407 @@
|
|||||||
|
import {
|
||||||
|
addPtNeConfigData,
|
||||||
|
delPtNeConfigData,
|
||||||
|
editPtNeConfigData,
|
||||||
|
} from '@/api/pt/neConfig';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { Modal, message } from 'ant-design-vue/es';
|
||||||
|
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||||
|
import { reactive, watch } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数配置array类型
|
||||||
|
* @param param 父级传入 { t, treeState, fnActiveConfigNode, ruleVerification, modalState, fnModalCancel}
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function useConfigArray({
|
||||||
|
t,
|
||||||
|
treeState,
|
||||||
|
fnActiveConfigNode,
|
||||||
|
ruleVerification,
|
||||||
|
modalState,
|
||||||
|
fnModalCancel,
|
||||||
|
}: any) {
|
||||||
|
/**多列列表状态类型 */
|
||||||
|
type ArrayStateType = {
|
||||||
|
/**紧凑型 */
|
||||||
|
size: SizeType;
|
||||||
|
/**多列嵌套记录字段 */
|
||||||
|
columns: Record<string, any>[];
|
||||||
|
/**表格字段列排序 */
|
||||||
|
columnsDnd: Record<string, any>[];
|
||||||
|
/**多列记录数据 */
|
||||||
|
columnsData: Record<string, any>[];
|
||||||
|
/**多列嵌套展开key */
|
||||||
|
arrayChildExpandKeys: any[];
|
||||||
|
|
||||||
|
/**多列记录数据 */
|
||||||
|
data: Record<string, any>[];
|
||||||
|
/**多列记录规则 */
|
||||||
|
dataRule: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**多列列表状态 */
|
||||||
|
let arrayState: ArrayStateType = reactive({
|
||||||
|
size: 'small',
|
||||||
|
columns: [],
|
||||||
|
columnsDnd: [],
|
||||||
|
columnsData: [],
|
||||||
|
arrayChildExpandKeys: [],
|
||||||
|
data: [],
|
||||||
|
dataRule: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**多列表编辑 */
|
||||||
|
function arrayEdit(rowIndex: Record<string, any>) {
|
||||||
|
const item = arrayState.data.find((s: any) => s.key === rowIndex.value);
|
||||||
|
if (!item) return;
|
||||||
|
const from = arrayInitEdit(item, arrayState.dataRule);
|
||||||
|
// 处理信息
|
||||||
|
const row: Record<string, any> = {};
|
||||||
|
for (const v of from.record) {
|
||||||
|
if (Array.isArray(v.array)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
row[v.name] = Object.assign({}, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊SMF-upfid选择
|
||||||
|
if (treeState.neType === 'SMF' && Reflect.has(row, 'upfId')) {
|
||||||
|
const v = row.upfId.value;
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
if (v === '') {
|
||||||
|
row.upfId.value = [];
|
||||||
|
} else if (v.includes(';')) {
|
||||||
|
row.upfId.value = v.split(';');
|
||||||
|
} else if (v.includes(',')) {
|
||||||
|
row.upfId.value = v.split(',');
|
||||||
|
} else {
|
||||||
|
row.upfId.value = [v];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modalState.from = row;
|
||||||
|
modalState.type = 'arrayEdit';
|
||||||
|
modalState.title = `${treeState.selectNode.paramDisplay} ${from.title}`;
|
||||||
|
modalState.key = from.key;
|
||||||
|
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
|
||||||
|
modalState.open = true;
|
||||||
|
|
||||||
|
// 关闭嵌套
|
||||||
|
arrayState.arrayChildExpandKeys = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**多列表编辑关闭 */
|
||||||
|
function arrayEditClose() {
|
||||||
|
arrayState.arrayChildExpandKeys = [];
|
||||||
|
fnModalCancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**多列表编辑确认 */
|
||||||
|
function arrayEditOk(from: Record<string, any>) {
|
||||||
|
const loc = `${from['index']['value']}`;
|
||||||
|
|
||||||
|
// 特殊SMF-upfid选择
|
||||||
|
if (treeState.neType === 'SMF' && Reflect.has(from, 'upfId')) {
|
||||||
|
const v = from.upfId.value;
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
from.upfId.value = v.join(';');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历提取属性和值
|
||||||
|
let data: Record<string, any> = {};
|
||||||
|
for (const key in from) {
|
||||||
|
// 子嵌套的不插入
|
||||||
|
if (from[key]['array']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 检查规则
|
||||||
|
const [ok, msg] = ruleVerification(from[key]);
|
||||||
|
if (!ok) {
|
||||||
|
message.warning({
|
||||||
|
content: `${msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data[key] = from[key]['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
editPtNeConfigData({
|
||||||
|
neType: treeState.neType,
|
||||||
|
paramName: treeState.selectNode.paramName,
|
||||||
|
paramData: data,
|
||||||
|
loc: loc,
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('views.ne.neConfig.updateItem', {
|
||||||
|
num: modalState.title,
|
||||||
|
}),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
fnActiveConfigNode('#');
|
||||||
|
} else {
|
||||||
|
message.warning({
|
||||||
|
content: t('views.ne.neConfig.updateItemErr'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
arrayEditClose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**多列表删除单行 */
|
||||||
|
function arrayDelete(rowIndex: Record<string, any>) {
|
||||||
|
const loc = `${rowIndex.value}`;
|
||||||
|
const title = `${treeState.selectNode.paramDisplay} Index-${loc}`;
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.ne.neConfig.delItemTip', {
|
||||||
|
num: title,
|
||||||
|
}),
|
||||||
|
onOk() {
|
||||||
|
delPtNeConfigData({
|
||||||
|
neType: treeState.neType,
|
||||||
|
paramName: treeState.selectNode.paramName,
|
||||||
|
loc: loc,
|
||||||
|
}).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('views.ne.neConfig.delItemOk', {
|
||||||
|
num: title,
|
||||||
|
}),
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
arrayEditClose();
|
||||||
|
fnActiveConfigNode('#');
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**多列表新增单行 */
|
||||||
|
function arrayAdd() {
|
||||||
|
const from = arrayInitAdd(arrayState.data, arrayState.dataRule);
|
||||||
|
|
||||||
|
// 处理信息
|
||||||
|
const row: Record<string, any> = {};
|
||||||
|
for (const v of from.record) {
|
||||||
|
if (Array.isArray(v.array)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
row[v.name] = Object.assign({}, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊SMF-upfid选择
|
||||||
|
if (treeState.neType === 'SMF' && Reflect.has(row, 'upfId')) {
|
||||||
|
const v = row.upfId.value;
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
if (v === '') {
|
||||||
|
row.upfId.value = [];
|
||||||
|
} else if (v.includes(';')) {
|
||||||
|
row.upfId.value = v.split(';');
|
||||||
|
} else if (v.includes(',')) {
|
||||||
|
row.upfId.value = v.split(',');
|
||||||
|
} else {
|
||||||
|
row.upfId.value = [v];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modalState.from = row;
|
||||||
|
modalState.type = 'arrayAdd';
|
||||||
|
modalState.title = `${treeState.selectNode.paramDisplay} ${from.title}`;
|
||||||
|
modalState.key = from.key;
|
||||||
|
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
|
||||||
|
modalState.open = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**多列表新增单行确认 */
|
||||||
|
function arrayAddOk(from: Record<string, any>) {
|
||||||
|
// 特殊SMF-upfid选择
|
||||||
|
if (treeState.neType === 'SMF' && Reflect.has(from, 'upfId')) {
|
||||||
|
const v = from.upfId.value;
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
from.upfId.value = v.join(';');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历提取属性和值
|
||||||
|
let data: Record<string, any> = {};
|
||||||
|
for (const key in from) {
|
||||||
|
// 子嵌套的不插入
|
||||||
|
if (from[key]['array']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 检查规则
|
||||||
|
const [ok, msg] = ruleVerification(from[key]);
|
||||||
|
if (!ok) {
|
||||||
|
message.warning({
|
||||||
|
content: `${msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data[key] = from[key]['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
addPtNeConfigData({
|
||||||
|
neType: treeState.neType,
|
||||||
|
paramName: treeState.selectNode.paramName,
|
||||||
|
paramData: data,
|
||||||
|
loc: `${from['index']['value']}`,
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('views.ne.neConfig.addItemOk', {
|
||||||
|
num: modalState.title,
|
||||||
|
}),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
fnActiveConfigNode('#');
|
||||||
|
} else {
|
||||||
|
message.warning({
|
||||||
|
content: t('views.ne.neConfig.addItemErr'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
arrayEditClose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**多列表编辑行数据初始化 */
|
||||||
|
function arrayInitEdit(data: Record<string, any>, dataRule: any) {
|
||||||
|
const dataFrom = data.record;
|
||||||
|
const ruleFrom = Object.assign({}, JSON.parse(JSON.stringify(dataRule)));
|
||||||
|
for (const row of ruleFrom.record) {
|
||||||
|
// 子嵌套的不初始
|
||||||
|
if (row.array) {
|
||||||
|
row.value = [];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 查找项的值
|
||||||
|
const item = dataFrom.find((s: any) => s.name === row.name);
|
||||||
|
if (!item) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 可选的
|
||||||
|
row.optional = 'true';
|
||||||
|
// 根据规则类型转值
|
||||||
|
if (['enum', 'int'].includes(row.type)) {
|
||||||
|
row.value = Number(item.value);
|
||||||
|
} else if ('bool' === row.type) {
|
||||||
|
row.value = Boolean(item.value);
|
||||||
|
} else {
|
||||||
|
row.value = item.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ruleFrom.key = data.key;
|
||||||
|
ruleFrom.title = data.title;
|
||||||
|
return ruleFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**多列表新增行数据初始化 */
|
||||||
|
function arrayInitAdd(data: any[], dataRule: any) {
|
||||||
|
// 有数据时取得最后的index
|
||||||
|
let dataLastIndex = 0;
|
||||||
|
if (data.length !== 0) {
|
||||||
|
const lastFrom = Object.assign(
|
||||||
|
{},
|
||||||
|
JSON.parse(JSON.stringify(data.at(-1)))
|
||||||
|
);
|
||||||
|
if (lastFrom.record.length > 0) {
|
||||||
|
dataLastIndex = parseInt(lastFrom.key);
|
||||||
|
dataLastIndex += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ruleFrom = Object.assign({}, JSON.parse(JSON.stringify(dataRule)));
|
||||||
|
for (const row of ruleFrom.record) {
|
||||||
|
// 子嵌套的不初始
|
||||||
|
if (row.array) {
|
||||||
|
row.value = [];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 可选的
|
||||||
|
row.optional = 'true';
|
||||||
|
// index值
|
||||||
|
if (row.name === 'index') {
|
||||||
|
let newIndex =
|
||||||
|
dataLastIndex !== 0 ? dataLastIndex : parseInt(row.value);
|
||||||
|
if (isNaN(newIndex)) {
|
||||||
|
newIndex = 0;
|
||||||
|
}
|
||||||
|
row.value = newIndex;
|
||||||
|
ruleFrom.key = newIndex;
|
||||||
|
ruleFrom.title = `Index-${newIndex}`;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 根据规则类型转值
|
||||||
|
if (['enum', 'int'].includes(row.type)) {
|
||||||
|
row.value = Number(row.value);
|
||||||
|
}
|
||||||
|
if ('bool' === row.type) {
|
||||||
|
row.value = Boolean(row.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 特殊SMF-upfid选择
|
||||||
|
if (treeState.neType === 'SMF' && row.name === 'upfId') {
|
||||||
|
const v = row.value;
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
if (v === '') {
|
||||||
|
row.value = [];
|
||||||
|
} else if (v.includes(';')) {
|
||||||
|
row.value = v.split(';');
|
||||||
|
} else if (v.includes(',')) {
|
||||||
|
row.value = v.split(',');
|
||||||
|
} else {
|
||||||
|
row.value = [v];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ruleFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听表格字段列排序变化关闭展开
|
||||||
|
watch(
|
||||||
|
() => arrayState.columnsDnd,
|
||||||
|
() => {
|
||||||
|
arrayEditClose();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
arrayState,
|
||||||
|
arrayEdit,
|
||||||
|
arrayEditClose,
|
||||||
|
arrayEditOk,
|
||||||
|
arrayDelete,
|
||||||
|
arrayAdd,
|
||||||
|
arrayAddOk,
|
||||||
|
arrayInitEdit,
|
||||||
|
arrayInitAdd,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,348 @@
|
|||||||
|
import {
|
||||||
|
addPtNeConfigData,
|
||||||
|
delPtNeConfigData,
|
||||||
|
editPtNeConfigData,
|
||||||
|
} from '@/api/pt/neConfig';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { Modal, message } from 'ant-design-vue/es';
|
||||||
|
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||||
|
import { nextTick, reactive } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数配置array类型的嵌套array
|
||||||
|
* @param param 父级传入 { t, treeState, fnActiveConfigNode, ruleVerification, modalState, arrayState, arrayInitEdit, arrayInitAdd, arrayEditClose}
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function useConfigArrayChild({
|
||||||
|
t,
|
||||||
|
treeState,
|
||||||
|
fnActiveConfigNode,
|
||||||
|
ruleVerification,
|
||||||
|
modalState,
|
||||||
|
arrayState,
|
||||||
|
arrayInitEdit,
|
||||||
|
arrayInitAdd,
|
||||||
|
arrayEditClose,
|
||||||
|
}: any) {
|
||||||
|
/**多列嵌套列表状态类型 */
|
||||||
|
type ArrayChildStateType = {
|
||||||
|
/**标题 */
|
||||||
|
title: string;
|
||||||
|
/**层级index */
|
||||||
|
loc: string;
|
||||||
|
/**紧凑型 */
|
||||||
|
size: SizeType;
|
||||||
|
/**多列嵌套记录字段 */
|
||||||
|
columns: Record<string, any>[];
|
||||||
|
/**表格字段列排序 */
|
||||||
|
columnsDnd: Record<string, any>[];
|
||||||
|
/**多列记录数据 */
|
||||||
|
columnsData: Record<string, any>[];
|
||||||
|
|
||||||
|
/**多列嵌套记录数据 */
|
||||||
|
data: Record<string, any>[];
|
||||||
|
/**多列嵌套记录规则 */
|
||||||
|
dataRule: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**多列嵌套表格状态 */
|
||||||
|
let arrayChildState: ArrayChildStateType = reactive({
|
||||||
|
title: '',
|
||||||
|
loc: '',
|
||||||
|
size: 'small',
|
||||||
|
columns: [],
|
||||||
|
columnsDnd: [],
|
||||||
|
columnsData: [],
|
||||||
|
data: [],
|
||||||
|
dataRule: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**多列表展开嵌套行 */
|
||||||
|
function arrayChildExpand(
|
||||||
|
indexRow: Record<string, any>,
|
||||||
|
row: Record<string, any>
|
||||||
|
) {
|
||||||
|
const loc = indexRow.value;
|
||||||
|
if (arrayChildState.loc === `${loc}/${row.name}`) {
|
||||||
|
arrayChildState.loc = '';
|
||||||
|
arrayState.arrayChildExpandKeys = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
arrayChildState.loc = '';
|
||||||
|
arrayState.arrayChildExpandKeys = [];
|
||||||
|
const from = Object.assign({}, JSON.parse(JSON.stringify(row)));
|
||||||
|
// 无数据时
|
||||||
|
if (!Array.isArray(from.value)) {
|
||||||
|
from.value = [];
|
||||||
|
}
|
||||||
|
const dataArr = Object.freeze(from.value);
|
||||||
|
const ruleArr = Object.freeze(from.array);
|
||||||
|
|
||||||
|
// 列表项数据
|
||||||
|
const dataArray: Record<string, any>[] = [];
|
||||||
|
for (const item of dataArr) {
|
||||||
|
const index = item['index'];
|
||||||
|
let record: Record<string, any>[] = [];
|
||||||
|
for (const key of Object.keys(item)) {
|
||||||
|
// 规则为准
|
||||||
|
for (const rule of ruleArr) {
|
||||||
|
if (rule['name'] === key) {
|
||||||
|
const ruleItem = Object.assign({ optional: 'true' }, rule, {
|
||||||
|
value: item[key],
|
||||||
|
});
|
||||||
|
record.push(ruleItem);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// dataArray.push(record);
|
||||||
|
dataArray.push({ title: `Index-${index}`, key: index, record });
|
||||||
|
}
|
||||||
|
arrayChildState.data = dataArray;
|
||||||
|
|
||||||
|
// 无数据时,用于新增
|
||||||
|
arrayChildState.dataRule = {
|
||||||
|
title: `Index-0`,
|
||||||
|
key: 0,
|
||||||
|
record: ruleArr,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 列表数据
|
||||||
|
const columnsData: Record<string, any>[] = [];
|
||||||
|
for (const v of arrayChildState.data) {
|
||||||
|
const row: Record<string, any> = {};
|
||||||
|
for (const item of v.record) {
|
||||||
|
row[item.name] = item;
|
||||||
|
}
|
||||||
|
columnsData.push(row);
|
||||||
|
}
|
||||||
|
arrayChildState.columnsData = columnsData;
|
||||||
|
|
||||||
|
// 列表字段
|
||||||
|
const columns: Record<string, any>[] = [];
|
||||||
|
for (const rule of arrayChildState.dataRule.record) {
|
||||||
|
columns.push({
|
||||||
|
title: rule.display,
|
||||||
|
dataIndex: rule.name,
|
||||||
|
align: 'left',
|
||||||
|
resizable: true,
|
||||||
|
width: 50,
|
||||||
|
minWidth: 50,
|
||||||
|
maxWidth: 250,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
columns.push({
|
||||||
|
title: t('common.operate'),
|
||||||
|
dataIndex: 'index',
|
||||||
|
key: 'index',
|
||||||
|
align: 'center',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 100,
|
||||||
|
});
|
||||||
|
arrayChildState.columns = columns;
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
// 设置展开key
|
||||||
|
arrayState.arrayChildExpandKeys = [indexRow];
|
||||||
|
// 层级标识
|
||||||
|
arrayChildState.loc = `${loc}/${from['name']}`;
|
||||||
|
// 设置展开列表标题
|
||||||
|
arrayChildState.title = `${from['display']}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**多列表嵌套行编辑 */
|
||||||
|
function arrayChildEdit(rowIndex: Record<string, any>) {
|
||||||
|
const item = arrayChildState.data.find(
|
||||||
|
(s: any) => s.key === rowIndex.value
|
||||||
|
);
|
||||||
|
if (!item) return;
|
||||||
|
const from = arrayInitEdit(item, arrayChildState.dataRule);
|
||||||
|
// 处理信息
|
||||||
|
const row: Record<string, any> = {};
|
||||||
|
for (const v of from.record) {
|
||||||
|
if (Array.isArray(v.array)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
row[v.name] = Object.assign({}, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
modalState.from = row;
|
||||||
|
modalState.type = 'arrayChildEdit';
|
||||||
|
modalState.title = `${arrayChildState.title} ${from.title}`;
|
||||||
|
modalState.key = from.key;
|
||||||
|
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
|
||||||
|
modalState.open = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**多列表嵌套行编辑确认 */
|
||||||
|
function arrayChildEditOk(from: Record<string, any>) {
|
||||||
|
const loc = `${arrayChildState.loc}/${from['index']['value']}`;
|
||||||
|
|
||||||
|
let data: Record<string, any> = {};
|
||||||
|
for (const key in from) {
|
||||||
|
// 子嵌套的不插入
|
||||||
|
if (from[key]['array']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 检查规则
|
||||||
|
const [ok, msg] = ruleVerification(from[key]);
|
||||||
|
if (!ok) {
|
||||||
|
message.warning({
|
||||||
|
content: `${msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data[key] = from[key]['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
editPtNeConfigData({
|
||||||
|
neType: treeState.neType,
|
||||||
|
paramName: treeState.selectNode.paramName,
|
||||||
|
paramData: data,
|
||||||
|
loc,
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('views.ne.neConfig.updateItem', {
|
||||||
|
num: modalState.title,
|
||||||
|
}),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
fnActiveConfigNode('#');
|
||||||
|
} else {
|
||||||
|
message.warning({
|
||||||
|
content: t('views.ne.neConfig.updateItemErr'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
arrayEditClose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**多列表嵌套行删除单行 */
|
||||||
|
function arrayChildDelete(rowIndex: Record<string, any>) {
|
||||||
|
const index = rowIndex.value;
|
||||||
|
const loc = `${arrayChildState.loc}/${index}`;
|
||||||
|
const title = `${arrayChildState.title} Index-${index}`;
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.ne.neConfig.delItemTip', {
|
||||||
|
num: title,
|
||||||
|
}),
|
||||||
|
onOk() {
|
||||||
|
delPtNeConfigData({
|
||||||
|
neType: treeState.neType,
|
||||||
|
paramName: treeState.selectNode.paramName,
|
||||||
|
loc,
|
||||||
|
}).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('views.ne.neConfig.delItemOk', {
|
||||||
|
num: title,
|
||||||
|
}),
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
arrayEditClose();
|
||||||
|
fnActiveConfigNode('#');
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**多列表嵌套行新增单行 */
|
||||||
|
function arrayChildAdd() {
|
||||||
|
const from = arrayInitAdd(arrayChildState.data, arrayChildState.dataRule);
|
||||||
|
// 处理信息
|
||||||
|
const row: Record<string, any> = {};
|
||||||
|
for (const v of from.record) {
|
||||||
|
if (Array.isArray(v.array)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
row[v.name] = Object.assign({}, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
modalState.from = row;
|
||||||
|
modalState.type = 'arrayChildAdd';
|
||||||
|
modalState.title = `${arrayChildState.title} ${from.title}`;
|
||||||
|
modalState.key = from.key;
|
||||||
|
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
|
||||||
|
modalState.open = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**多列表新增单行确认 */
|
||||||
|
function arrayChildAddOk(from: Record<string, any>) {
|
||||||
|
const loc = `${arrayChildState.loc}/${from['index']['value']}`;
|
||||||
|
|
||||||
|
let data: Record<string, any> = {};
|
||||||
|
for (const key in from) {
|
||||||
|
// 子嵌套的不插入
|
||||||
|
if (from[key]['array']) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 检查规则
|
||||||
|
const [ok, msg] = ruleVerification(from[key]);
|
||||||
|
if (!ok) {
|
||||||
|
message.warning({
|
||||||
|
content: `${msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data[key] = from[key]['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
addPtNeConfigData({
|
||||||
|
neType: treeState.neType,
|
||||||
|
paramName: treeState.selectNode.paramName,
|
||||||
|
paramData: data,
|
||||||
|
loc,
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('views.ne.neConfig.addItemOk', {
|
||||||
|
num: modalState.title,
|
||||||
|
}),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
fnActiveConfigNode('#');
|
||||||
|
} else {
|
||||||
|
message.warning({
|
||||||
|
content: t('views.ne.neConfig.addItemErr'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
arrayEditClose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
arrayChildState,
|
||||||
|
arrayChildExpand,
|
||||||
|
arrayChildEdit,
|
||||||
|
arrayChildEditOk,
|
||||||
|
arrayChildDelete,
|
||||||
|
arrayChildAdd,
|
||||||
|
arrayChildAddOk,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
import { editPtNeConfigData } from '@/api/pt/neConfig';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||||
|
import { message } from 'ant-design-vue/es';
|
||||||
|
import { reactive, toRaw } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* list类型参数处理
|
||||||
|
* @param param 父级传入 {t, treeState, ruleVerification}
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function useConfigList({ t, treeState, ruleVerification }: any) {
|
||||||
|
/**单列表状态类型 */
|
||||||
|
type ListStateType = {
|
||||||
|
/**紧凑型 */
|
||||||
|
size: SizeType;
|
||||||
|
/**单列记录字段 */
|
||||||
|
columns: Record<string, any>[];
|
||||||
|
/**单列记录数据 */
|
||||||
|
data: Record<string, any>[];
|
||||||
|
/**编辑行记录 */
|
||||||
|
editRecord: Record<string, any>;
|
||||||
|
/**确认提交等待 */
|
||||||
|
confirmLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**单列表状态 */
|
||||||
|
let listState: ListStateType = reactive({
|
||||||
|
size: 'small',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
title: 'Key',
|
||||||
|
dataIndex: 'display',
|
||||||
|
align: 'left',
|
||||||
|
width: '30%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Value',
|
||||||
|
dataIndex: 'value',
|
||||||
|
align: 'left',
|
||||||
|
width: '70%',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
data: [],
|
||||||
|
confirmLoading: false,
|
||||||
|
editRecord: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**单列表编辑 */
|
||||||
|
function listEdit(row: Record<string, any>) {
|
||||||
|
if (
|
||||||
|
listState.confirmLoading ||
|
||||||
|
['read-only', 'read', 'ro'].includes(row.access)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
listState.editRecord = Object.assign({}, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**单列表编辑关闭 */
|
||||||
|
function listEditClose() {
|
||||||
|
listState.confirmLoading = false;
|
||||||
|
listState.editRecord = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**单列表编辑确认 */
|
||||||
|
function listEditOk() {
|
||||||
|
if (listState.confirmLoading) return;
|
||||||
|
const from = toRaw(listState.editRecord);
|
||||||
|
// 检查规则
|
||||||
|
const [ok, msg] = ruleVerification(from);
|
||||||
|
if (!ok) {
|
||||||
|
message.warning({
|
||||||
|
content: `${msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送
|
||||||
|
listState.confirmLoading = true;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
editPtNeConfigData({
|
||||||
|
neType: treeState.neType,
|
||||||
|
paramName: treeState.selectNode.paramName,
|
||||||
|
paramData: {
|
||||||
|
[from['name']]: from['value'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('views.ne.neConfig.updateValue', {
|
||||||
|
num: from['display'],
|
||||||
|
}),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
// 改变表格数据
|
||||||
|
const item = listState.data.find(
|
||||||
|
(item: Record<string, any>) => from['name'] === item['name']
|
||||||
|
);
|
||||||
|
if (item) {
|
||||||
|
Object.assign(item, listState.editRecord);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.warning({
|
||||||
|
content: t('views.ne.neConfig.updateValueErr'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
listState.confirmLoading = false;
|
||||||
|
listState.editRecord = {};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格分页器参数 */
|
||||||
|
let tablePagination = reactive({
|
||||||
|
/**当前页数 */
|
||||||
|
current: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 10,
|
||||||
|
/**默认的每页条数 */
|
||||||
|
defaultPageSize: 10,
|
||||||
|
/**指定每页可以显示多少条 */
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
/**只有一页时是否隐藏分页器 */
|
||||||
|
hideOnSinglePage: true,
|
||||||
|
/**是否可以快速跳转至某页 */
|
||||||
|
showQuickJumper: true,
|
||||||
|
/**是否可以改变 pageSize */
|
||||||
|
showSizeChanger: true,
|
||||||
|
/**数据总数 */
|
||||||
|
total: 0,
|
||||||
|
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||||
|
onChange: (page: number, pageSize: number) => {
|
||||||
|
tablePagination.current = page;
|
||||||
|
tablePagination.pageSize = pageSize;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { tablePagination, listState, listEdit, listEditClose, listEditOk };
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import { getNeConfigData } from '@/api/ne/neConfig';
|
||||||
|
import { regExpIPv4, regExpIPv6, validURL } from '@/utils/regular-utils';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数公共函数
|
||||||
|
* @param param 父级传入 {t}
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function useOptions({ t }: any) {
|
||||||
|
/**规则校验 */
|
||||||
|
function ruleVerification(row: Record<string, any>): (string | boolean)[] {
|
||||||
|
let result = [true, ''];
|
||||||
|
const type = row.type;
|
||||||
|
const value = row.value;
|
||||||
|
const filter = row.filter;
|
||||||
|
const display = row.display;
|
||||||
|
|
||||||
|
// 子嵌套的不检查
|
||||||
|
if (row.array) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选的同时没有值不检查
|
||||||
|
if (row.optional === 'true' && !value) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case 'int':
|
||||||
|
// filter: "0~128"
|
||||||
|
|
||||||
|
if (filter && filter.indexOf('~') !== -1) {
|
||||||
|
const filterArr = filter.split('~');
|
||||||
|
const minInt = parseInt(filterArr[0]);
|
||||||
|
const maxInt = parseInt(filterArr[1]);
|
||||||
|
const valueInt = parseInt(value);
|
||||||
|
if (valueInt < minInt || valueInt > maxInt) {
|
||||||
|
return [
|
||||||
|
false,
|
||||||
|
t('views.ne.neConfig.requireInt', {
|
||||||
|
display,
|
||||||
|
filter,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ipv4':
|
||||||
|
if (!regExpIPv4.test(value)) {
|
||||||
|
return [
|
||||||
|
false,
|
||||||
|
t('views.ne.neConfig.requireIpv4', { display }),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ipv6':
|
||||||
|
if (!regExpIPv6.test(value)) {
|
||||||
|
return [
|
||||||
|
false,
|
||||||
|
t('views.ne.neConfig.requireIpv6', { display }),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'enum':
|
||||||
|
if (filter && filter.indexOf('{') === 1) {
|
||||||
|
let filterJson: Record<string, any> = {};
|
||||||
|
try {
|
||||||
|
filterJson = JSON.parse(filter); //string---json
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.keys(filterJson).includes(`${value}`)) {
|
||||||
|
return [
|
||||||
|
false,
|
||||||
|
t('views.ne.neConfig.requireEnum', { display }),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'bool':
|
||||||
|
// filter: '{"0":"false", "1":"true"}'
|
||||||
|
|
||||||
|
if (filter && filter.indexOf('{') === 1) {
|
||||||
|
let filterJson: Record<string, any> = {};
|
||||||
|
try {
|
||||||
|
filterJson = JSON.parse(filter); //string---json
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.values(filterJson).includes(`${value}`)) {
|
||||||
|
return [
|
||||||
|
false,
|
||||||
|
t('views.ne.neConfig.requireBool', { display }),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
// filter: "0~128"
|
||||||
|
|
||||||
|
// 字符串长度判断
|
||||||
|
if (filter && filter.indexOf('~') !== -1) {
|
||||||
|
try {
|
||||||
|
const filterArr = filter.split('~');
|
||||||
|
let rule = new RegExp(
|
||||||
|
'^\\S{' + filterArr[0] + ',' + filterArr[1] + '}$'
|
||||||
|
);
|
||||||
|
if (!rule.test(value)) {
|
||||||
|
return [
|
||||||
|
false,
|
||||||
|
t('views.ne.neConfig.requireString', {
|
||||||
|
display,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 字符串http判断
|
||||||
|
if (value.startsWith('http')) {
|
||||||
|
try {
|
||||||
|
if (!validURL(value)) {
|
||||||
|
return [
|
||||||
|
false,
|
||||||
|
t('views.ne.neConfig.requireString', {
|
||||||
|
display,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'regex':
|
||||||
|
// filter: "^[0-9]{3}$"
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
try {
|
||||||
|
let regex = new RegExp(filter);
|
||||||
|
if (!regex.test(value)) {
|
||||||
|
return [
|
||||||
|
false,
|
||||||
|
t('views.ne.neConfig.requireString', {
|
||||||
|
display,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return [
|
||||||
|
false,
|
||||||
|
t('views.ne.neConfig.requireUn', { display }),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**upfId可选择 */
|
||||||
|
const smfByUPFIdOptions = ref<{ value: string; label: string }[]>([]);
|
||||||
|
/**加载smf配置的upfId */
|
||||||
|
function smfByUPFIdLoadData(neId: string) {
|
||||||
|
getNeConfigData({
|
||||||
|
neType: 'SMF',
|
||||||
|
neId: neId,
|
||||||
|
paramName: 'upfConfig',
|
||||||
|
}).then(res => {
|
||||||
|
smfByUPFIdOptions.value = [];
|
||||||
|
for (const s of res.data) {
|
||||||
|
smfByUPFIdOptions.value.push({
|
||||||
|
value: s.id,
|
||||||
|
label: s.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ruleVerification,
|
||||||
|
smfByUPFIdLoadData,
|
||||||
|
smfByUPFIdOptions,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
import { ptSaveAsDefault, ptResetAsDefault } from '@/api/pt/neConfig';
|
||||||
|
import {
|
||||||
|
getPtClassStudents,
|
||||||
|
stuPtNeConfigApply,
|
||||||
|
updatePtNeConfigApply,
|
||||||
|
} from '@/api/pt/neConfigApply';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { Modal } from 'ant-design-vue/es';
|
||||||
|
import { message } from 'ant-design-vue/lib';
|
||||||
|
import { computed, reactive } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实训教学函数
|
||||||
|
* @param param 父级传入 {t,fnActiveConfigNode}
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function usePtOptions({ t, fnActiveConfigNode }: any) {
|
||||||
|
const ptConfigState = reactive({
|
||||||
|
saveLoading: false,
|
||||||
|
restLoading: false,
|
||||||
|
applyLoading: false,
|
||||||
|
});
|
||||||
|
/**(管理员)保存网元下所有配置为示例配置 */
|
||||||
|
function ptConfigSave(neType: string) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.configManage.configParamForm.ptLoadTip'),
|
||||||
|
onOk() {
|
||||||
|
ptConfigState.saveLoading = true;
|
||||||
|
ptSaveAsDefault(neType, '001')
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
ptConfigState.saveLoading = false;
|
||||||
|
fnActiveConfigNode('#');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**重置网元下所有配置 */
|
||||||
|
function ptConfigReset(neType: string) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.configManage.configParamForm.ptResetTip'),
|
||||||
|
onOk() {
|
||||||
|
ptConfigState.restLoading = true;
|
||||||
|
ptResetAsDefault(neType)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
ptConfigState.restLoading = false;
|
||||||
|
fnActiveConfigNode('#');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**配置下方应用(学生)申请撤回和(管理/教师)应用退回 */
|
||||||
|
function ptConfigApply(
|
||||||
|
neType: string,
|
||||||
|
status: '0' | '1' | '2' | '3',
|
||||||
|
student?: string
|
||||||
|
) {
|
||||||
|
let result: any;
|
||||||
|
if (status === '2' || status === '3') {
|
||||||
|
let from: {
|
||||||
|
neType: string;
|
||||||
|
status: string;
|
||||||
|
student?: string;
|
||||||
|
backInfo?: string;
|
||||||
|
} = {
|
||||||
|
neType,
|
||||||
|
status: '2',
|
||||||
|
};
|
||||||
|
if (student) {
|
||||||
|
if (status === '2') {
|
||||||
|
from = { neType, status, student };
|
||||||
|
}
|
||||||
|
if (status === '3') {
|
||||||
|
from = { neType, status, student, backInfo: '请重新检查配置' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = updatePtNeConfigApply(from);
|
||||||
|
}
|
||||||
|
if (status === '0' || status === '1') {
|
||||||
|
result = stuPtNeConfigApply({ neType, status });
|
||||||
|
}
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.configManage.configParamForm.ptApplyStuTip', {
|
||||||
|
ne: neType,
|
||||||
|
}),
|
||||||
|
onOk() {
|
||||||
|
ptConfigState.applyLoading = true;
|
||||||
|
result
|
||||||
|
.then((res: any) => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
// 教师修改学生时改变状态
|
||||||
|
if (student) {
|
||||||
|
const item = classState.studentOptionsDef.find(
|
||||||
|
s => s.value === classState.student
|
||||||
|
);
|
||||||
|
if (item) {
|
||||||
|
item.applyStatus = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
ptConfigState.applyLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const classState = reactive<{
|
||||||
|
/**学生账号 */
|
||||||
|
student: string | undefined;
|
||||||
|
/**学生可选择列表 */
|
||||||
|
studentOptions: {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
applyId: string;
|
||||||
|
applyStatus: string;
|
||||||
|
}[];
|
||||||
|
studentOptionsDef: {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
applyId: string;
|
||||||
|
applyStatus: string;
|
||||||
|
}[];
|
||||||
|
}>({
|
||||||
|
student: undefined,
|
||||||
|
studentOptions: [],
|
||||||
|
studentOptionsDef: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**学生选择搜索 */
|
||||||
|
function studentChange(v: any) {
|
||||||
|
if (!v) {
|
||||||
|
Object.assign(classState.studentOptions, classState.studentOptionsDef);
|
||||||
|
}
|
||||||
|
fnActiveConfigNode('#');
|
||||||
|
}
|
||||||
|
|
||||||
|
let timeout: any;
|
||||||
|
|
||||||
|
/**学生选择搜索 */
|
||||||
|
function studentSearch(neType: string, val: string) {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = null;
|
||||||
|
}
|
||||||
|
if (!val) {
|
||||||
|
Object.assign(classState.studentOptions, classState.studentOptionsDef);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timeout = setTimeout(() => classStudents(neType, val), 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**班级学生列表 */
|
||||||
|
function classStudents(neType: string, val?: string) {
|
||||||
|
getPtClassStudents({ neType, userName: val }).then(res => {
|
||||||
|
classState.studentOptions = [];
|
||||||
|
if (!Array.isArray(res.data) || res.data.length <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const v of res.data) {
|
||||||
|
classState.studentOptions.push({
|
||||||
|
value: v.userName,
|
||||||
|
label: v.userName,
|
||||||
|
applyId: v.applyId,
|
||||||
|
applyStatus: v.applyStatus,
|
||||||
|
});
|
||||||
|
// 设为最新状态
|
||||||
|
const item = classState.studentOptionsDef.find(
|
||||||
|
s => s.value === v.userName
|
||||||
|
);
|
||||||
|
if (item) {
|
||||||
|
item.applyStatus = v.applyStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!val) {
|
||||||
|
Object.assign(classState.studentOptionsDef, classState.studentOptions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 学生状态
|
||||||
|
const studentStatus = computed(() => {
|
||||||
|
const item = classState.studentOptionsDef.find(
|
||||||
|
s => s.value === classState.student
|
||||||
|
);
|
||||||
|
if (item) return item.applyStatus;
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
ptConfigState,
|
||||||
|
ptConfigSave,
|
||||||
|
ptConfigReset,
|
||||||
|
ptConfigApply,
|
||||||
|
classState,
|
||||||
|
classStudents,
|
||||||
|
studentStatus,
|
||||||
|
studentSearch,
|
||||||
|
studentChange,
|
||||||
|
};
|
||||||
|
}
|
||||||
1295
practical_training/views/configManage/configParamTreeTable/index.vue
Normal file
1275
practical_training/views/configManage/neManage/index.vue
Normal file
442
practical_training/views/configManage/neOverview/index.vue
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
|
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||||
|
import { message } from 'ant-design-vue/es';
|
||||||
|
import { reactive, ref, onMounted, onBeforeUnmount, markRaw } from 'vue';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import { TooltipComponent } from 'echarts/components';
|
||||||
|
import { GaugeChart } from 'echarts/charts';
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
|
import * as echarts from 'echarts/core';
|
||||||
|
import { TitleComponent, LegendComponent } from 'echarts/components';
|
||||||
|
import { PieChart } from 'echarts/charts';
|
||||||
|
import { LabelLayout } from 'echarts/features';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||||
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
|
const { getDict } = useDictStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const route = useRoute();
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
echarts.use([
|
||||||
|
TooltipComponent,
|
||||||
|
GaugeChart,
|
||||||
|
TitleComponent,
|
||||||
|
LegendComponent,
|
||||||
|
PieChart,
|
||||||
|
CanvasRenderer,
|
||||||
|
LabelLayout,
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**图DOM节点实例对象 */
|
||||||
|
const statusBar = ref<HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
|
/**图实例对象 */
|
||||||
|
const statusBarChart = ref<any>(null);
|
||||||
|
|
||||||
|
/**网元状态字典数据 */
|
||||||
|
let indexColor = ref<DictType[]>([
|
||||||
|
{ label: 'Normal', value: 'normal', tagType: '', tagClass: '#91cc75' },
|
||||||
|
{
|
||||||
|
label: 'Abnormal',
|
||||||
|
value: 'abnormal',
|
||||||
|
tagType: '',
|
||||||
|
tagClass: '#ee6666',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**表格字段列 */
|
||||||
|
//customRender(){} ----单元格处理
|
||||||
|
let tableColumns: ColumnsType = [
|
||||||
|
{
|
||||||
|
title: t('views.index.object'),
|
||||||
|
dataIndex: 'neName',
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.index.realNeStatus'),
|
||||||
|
dataIndex: 'serverState',
|
||||||
|
align: 'left',
|
||||||
|
key: 'status',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.index.reloadTime'),
|
||||||
|
dataIndex: 'serverState',
|
||||||
|
align: 'left',
|
||||||
|
customRender(opt) {
|
||||||
|
if (opt.value?.refreshTime) return parseDateToStr(opt.value?.refreshTime);
|
||||||
|
return '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.index.version'),
|
||||||
|
dataIndex: 'serverState',
|
||||||
|
align: 'left',
|
||||||
|
customRender(opt) {
|
||||||
|
return opt.value?.version || '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.index.serialNum'),
|
||||||
|
dataIndex: 'serverState',
|
||||||
|
align: 'left',
|
||||||
|
customRender(opt) {
|
||||||
|
return opt.value?.sn || '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.index.expiryDate'),
|
||||||
|
dataIndex: 'serverState',
|
||||||
|
align: 'left',
|
||||||
|
customRender(opt) {
|
||||||
|
return opt.value?.expire || '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.index.ipAddress'),
|
||||||
|
dataIndex: 'serverState',
|
||||||
|
align: 'left',
|
||||||
|
customRender(opt) {
|
||||||
|
return opt.value?.neIP || '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
/**表格状态类型 */
|
||||||
|
type TabeStateType = {
|
||||||
|
/**加载等待 */
|
||||||
|
loading: boolean;
|
||||||
|
/**记录数据 */
|
||||||
|
data: object[];
|
||||||
|
/**勾选记录 */
|
||||||
|
selectedRowKeys: (string | number)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**表格状态 */
|
||||||
|
let tableState: TabeStateType = reactive({
|
||||||
|
loading: false,
|
||||||
|
data: [],
|
||||||
|
selectedRowKeys: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格状态 */
|
||||||
|
let nfInfo: any = reactive({
|
||||||
|
obj: 'OMC',
|
||||||
|
version: appStore.version,
|
||||||
|
status: t('views.index.normal'),
|
||||||
|
outTimeDate: '',
|
||||||
|
serialNum: appStore.serialNum,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格状态类型 */
|
||||||
|
type nfStateType = {
|
||||||
|
/**主机名 */
|
||||||
|
hostName: string;
|
||||||
|
/**操作系统信息 */
|
||||||
|
osInfo: string;
|
||||||
|
/**IP地址 */
|
||||||
|
ipAddress: string;
|
||||||
|
/**版本 */
|
||||||
|
version: string;
|
||||||
|
/**CPU利用率 */
|
||||||
|
cpuUse: string;
|
||||||
|
/**内存使用 */
|
||||||
|
memoryUse: string;
|
||||||
|
/**用户容量 */
|
||||||
|
capability: number;
|
||||||
|
/**序列号 */
|
||||||
|
serialNum: string;
|
||||||
|
/**许可证到期日期 */
|
||||||
|
/* selectedRowKeys: (string | number)[];*/
|
||||||
|
expiryDate: string;
|
||||||
|
};
|
||||||
|
/**网元详细信息 */
|
||||||
|
let pronInfo: nfStateType = reactive({
|
||||||
|
hostName: '5gc',
|
||||||
|
osInfo: 'Linux 5gc 4.15.0-112-generic 2020 x86_64 GNU/Linux',
|
||||||
|
ipAddress: '-',
|
||||||
|
version: '-',
|
||||||
|
cpuUse: '-',
|
||||||
|
memoryUse: '-',
|
||||||
|
capability: 0,
|
||||||
|
serialNum: '-',
|
||||||
|
expiryDate: '-',
|
||||||
|
});
|
||||||
|
|
||||||
|
/**查询网元状态列表 */
|
||||||
|
function fnGetList(one: boolean) {
|
||||||
|
if (tableState.loading) return;
|
||||||
|
one && (tableState.loading = true);
|
||||||
|
listAllNeInfo({ bandStatus: true }).then(res => {
|
||||||
|
tableState.data = res.data;
|
||||||
|
tableState.loading = false;
|
||||||
|
var rightNum = 0;
|
||||||
|
var errorNum = 0;
|
||||||
|
res.data.forEach((item: any) => {
|
||||||
|
if (item.serverState.online) {
|
||||||
|
rightNum++;
|
||||||
|
} else {
|
||||||
|
errorNum++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const optionData: any = {
|
||||||
|
title: {
|
||||||
|
text: '',
|
||||||
|
subtext: '',
|
||||||
|
left: 'center',
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
left: 'left',
|
||||||
|
},
|
||||||
|
color: indexColor.value.map(item => item.tagClass),
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: t('views.index.realNeStatus'),
|
||||||
|
type: 'pie',
|
||||||
|
radius: '70%',
|
||||||
|
center: ['50%', '50%'],
|
||||||
|
data: [
|
||||||
|
{ value: rightNum, name: t('views.index.normal') },
|
||||||
|
{ value: errorNum, name: t('views.index.abnormal') },
|
||||||
|
],
|
||||||
|
emphasis: {
|
||||||
|
itemStyle: {
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
label: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
fnDesign(statusBar.value, optionData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fnDesign(container: HTMLElement | undefined, option: any) {
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
if (!statusBarChart.value) {
|
||||||
|
statusBarChart.value = markRaw(echarts.init(container, 'light'));
|
||||||
|
}
|
||||||
|
option && statusBarChart.value.setOption(option);
|
||||||
|
|
||||||
|
// 创建 ResizeObserver 实例
|
||||||
|
var observer = new ResizeObserver(entries => {
|
||||||
|
if (statusBarChart.value) {
|
||||||
|
statusBarChart.value.resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 监听元素大小变化
|
||||||
|
observer.observe(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**抽屉 网元详细信息 */
|
||||||
|
const open = ref(false);
|
||||||
|
const closeDrawer = () => {
|
||||||
|
open.value = false;
|
||||||
|
};
|
||||||
|
/**抽屉 网元详细信息 */
|
||||||
|
|
||||||
|
/**监听表格行事件*/
|
||||||
|
function rowClick(record: any, index: any) {
|
||||||
|
return {
|
||||||
|
onClick: (event: any) => {
|
||||||
|
let pronData = JSON.parse(JSON.stringify(record.serverState));
|
||||||
|
if (!pronData.online) {
|
||||||
|
message.error(t('views.index.neStatus'), 2);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
const totalMemInKB = pronData.mem?.totalMem;
|
||||||
|
const nfUsedMemInKB = pronData.mem?.nfUsedMem;
|
||||||
|
const sysMemUsageInKB = pronData.mem?.sysMemUsage;
|
||||||
|
|
||||||
|
// 将KB转换为MB
|
||||||
|
const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100;
|
||||||
|
const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
|
||||||
|
const sysMemUsageInMB =
|
||||||
|
Math.round((sysMemUsageInKB / 1024) * 100) / 100;
|
||||||
|
|
||||||
|
//渲染详细信息
|
||||||
|
pronInfo = {
|
||||||
|
hostName: pronData.hostname,
|
||||||
|
osInfo: pronData.os,
|
||||||
|
ipAddress: pronData.neIP,
|
||||||
|
version: pronData.version,
|
||||||
|
cpuUse:
|
||||||
|
pronData.neName +
|
||||||
|
':' +
|
||||||
|
pronData.cpu?.nfCpuUsage / 100 +
|
||||||
|
'%; ' +
|
||||||
|
'SYS:' +
|
||||||
|
pronData.cpu?.sysCpuUsage / 100 +
|
||||||
|
'%',
|
||||||
|
memoryUse:
|
||||||
|
'Total:' +
|
||||||
|
totalMemInMB +
|
||||||
|
'MB; ' +
|
||||||
|
pronData.name +
|
||||||
|
':' +
|
||||||
|
nfUsedMemInMB +
|
||||||
|
'MB; SYS:' +
|
||||||
|
sysMemUsageInMB +
|
||||||
|
'MB',
|
||||||
|
capability: pronData.capability,
|
||||||
|
serialNum: pronData.sn,
|
||||||
|
expiryDate: pronData.expire,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
open.value = true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let timer: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 国际化翻译转换
|
||||||
|
*/
|
||||||
|
function fnLocale() {
|
||||||
|
let title = route.meta.title as string;
|
||||||
|
if (title.indexOf('router.') !== -1) {
|
||||||
|
title = t(title);
|
||||||
|
}
|
||||||
|
appStore.setTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getDict('index_status')
|
||||||
|
.then(res => {
|
||||||
|
if (res.length > 0) {
|
||||||
|
indexColor.value = res;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
fnLocale();
|
||||||
|
fnGetList(true);
|
||||||
|
timer = setInterval(() => fnGetList(false), 10000); // 每隔10秒执行一次
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 在组件卸载之前清除定时器
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearInterval(timer);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageContainer :breadcrumb="{}">
|
||||||
|
<div>
|
||||||
|
<a-drawer :open="open" @close="closeDrawer" :width="700">
|
||||||
|
<a-descriptions bordered :column="1" :label-style="{ width: '160px' }">
|
||||||
|
<a-descriptions-item :label="t('views.index.hostName')">{{
|
||||||
|
pronInfo.hostName
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
<a-descriptions-item :label="t('views.index.osInfo')">{{
|
||||||
|
pronInfo.osInfo
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
<a-descriptions-item :label="t('views.index.ipAddress')">{{
|
||||||
|
pronInfo.ipAddress
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
<a-descriptions-item :label="t('views.index.version')">{{
|
||||||
|
pronInfo.version
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
<a-descriptions-item :label="t('views.index.capability')">{{
|
||||||
|
pronInfo.capability
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
<a-descriptions-item :label="t('views.index.cpuUse')">{{
|
||||||
|
pronInfo.cpuUse
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
<a-descriptions-item :label="t('views.index.memoryUse')">{{
|
||||||
|
pronInfo.memoryUse
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
<a-descriptions-item :label="t('views.index.serialNum')">{{
|
||||||
|
pronInfo.serialNum
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
<a-descriptions-item :label="t('views.index.expiryDate')">{{
|
||||||
|
pronInfo.expiryDate
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-drawer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="14" :md="16" :xs="24">
|
||||||
|
<!-- 表格列表 -->
|
||||||
|
<a-table
|
||||||
|
class="table"
|
||||||
|
row-key="id"
|
||||||
|
size="small"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:loading="tableState.loading"
|
||||||
|
:data-source="tableState.data"
|
||||||
|
:pagination="false"
|
||||||
|
:scroll="{ x: true }"
|
||||||
|
:customRow="rowClick"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'status'">
|
||||||
|
<div v-if="record.serverState.online">
|
||||||
|
<a-tag color="blue">{{ t('views.index.normal') }}</a-tag>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<a-tag color="pink">{{ t('views.index.abnormal') }}</a-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="10" :md="8" :xs="24">
|
||||||
|
<a-card
|
||||||
|
:title="t('views.index.runStatus')"
|
||||||
|
style="margin-bottom: 16px"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<div style="width: 100%; min-height: 200px" ref="statusBar"></div>
|
||||||
|
</a-card>
|
||||||
|
<a-card
|
||||||
|
:title="t('views.index.mark')"
|
||||||
|
style="margin-top: 16px"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<a-descriptions
|
||||||
|
bordered
|
||||||
|
:column="1"
|
||||||
|
:label-style="{ width: '160px' }"
|
||||||
|
>
|
||||||
|
<a-descriptions-item :label="t('views.index.object')">{{
|
||||||
|
nfInfo.obj
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
<template v-if="nfInfo.obj === 'OMC'">
|
||||||
|
<a-descriptions-item :label="t('views.index.versionNum')">{{
|
||||||
|
nfInfo.version
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
<a-descriptions-item :label="t('views.index.systemStatus')">{{
|
||||||
|
nfInfo.status
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-descriptions-item :label="t('views.index.serialNum')">{{
|
||||||
|
nfInfo.serialNum
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
<a-descriptions-item :label="t('views.index.expiryDate')">{{
|
||||||
|
nfInfo.outTimeDate
|
||||||
|
}}</a-descriptions-item>
|
||||||
|
</template>
|
||||||
|
</a-descriptions>
|
||||||
|
</a-card>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</PageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
||||||
728
practical_training/views/dashboard/amfUE/index.vue
Normal file
@@ -0,0 +1,728 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, onMounted, toRaw, ref, onBeforeUnmount } from 'vue';
|
||||||
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
|
import { message, Modal } from 'ant-design-vue/es';
|
||||||
|
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||||
|
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||||
|
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import {
|
||||||
|
RESULT_CODE_ERROR,
|
||||||
|
RESULT_CODE_SUCCESS,
|
||||||
|
} from '@/constants/result-constants';
|
||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
import { listAMFDataUE, delAMFDataUE, exportAMFDataUE } from '@/api/neData/amf';
|
||||||
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
|
import saveAs from 'file-saver';
|
||||||
|
import PQueue from 'p-queue';
|
||||||
|
import { hasRoles } from '@/plugins/auth-user';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { getDict } = useDictStore();
|
||||||
|
const ws = new WS();
|
||||||
|
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||||
|
|
||||||
|
/**字典数据 */
|
||||||
|
let dict: {
|
||||||
|
/**UE 事件认证代码类型 */
|
||||||
|
ueAauthCode: DictType[];
|
||||||
|
/**UE 事件类型 */
|
||||||
|
ueEventType: DictType[];
|
||||||
|
/**UE 事件CM状态 */
|
||||||
|
ueEventCmState: DictType[];
|
||||||
|
} = reactive({
|
||||||
|
ueAauthCode: [],
|
||||||
|
ueEventType: [],
|
||||||
|
ueEventCmState: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**开始结束时间 */
|
||||||
|
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||||
|
|
||||||
|
/**查询参数 */
|
||||||
|
let queryParams = reactive({
|
||||||
|
/**网元类型 */
|
||||||
|
neType: 'AMF',
|
||||||
|
neId: '001',
|
||||||
|
eventType: '',
|
||||||
|
imsi: '',
|
||||||
|
sortField: 'timestamp',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
/**开始时间 */
|
||||||
|
startTime: '',
|
||||||
|
/**结束时间 */
|
||||||
|
endTime: '',
|
||||||
|
/**当前页数 */
|
||||||
|
pageNum: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**查询参数重置 */
|
||||||
|
function fnQueryReset() {
|
||||||
|
eventTypes.value = [];
|
||||||
|
queryParams = Object.assign(queryParams, {
|
||||||
|
eventType: '',
|
||||||
|
imsi: '',
|
||||||
|
startTime: '',
|
||||||
|
endTime: '',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
queryRangePicker.value = ['', ''];
|
||||||
|
tablePagination.current = 1;
|
||||||
|
tablePagination.pageSize = 20;
|
||||||
|
fnGetList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**记录类型 */
|
||||||
|
const eventTypes = ref<string[]>([]);
|
||||||
|
|
||||||
|
/**查询记录类型变更 */
|
||||||
|
function fnQueryEventTypeChange(value: any) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
queryParams.eventType = value.join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格状态类型 */
|
||||||
|
type TabeStateType = {
|
||||||
|
/**加载等待 */
|
||||||
|
loading: boolean;
|
||||||
|
/**紧凑型 */
|
||||||
|
size: SizeType;
|
||||||
|
/**搜索栏 */
|
||||||
|
seached: boolean;
|
||||||
|
/**记录数据 */
|
||||||
|
data: object[];
|
||||||
|
/**勾选记录 */
|
||||||
|
selectedRowKeys: (string | number)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**表格状态 */
|
||||||
|
let tableState: TabeStateType = reactive({
|
||||||
|
loading: false,
|
||||||
|
size: 'middle',
|
||||||
|
seached: true,
|
||||||
|
data: [],
|
||||||
|
selectedRowKeys: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格字段列 */
|
||||||
|
let tableColumns: ColumnsType = [
|
||||||
|
{
|
||||||
|
title: t('common.rowId'),
|
||||||
|
dataIndex: 'id',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'IMSI',
|
||||||
|
dataIndex: 'eventJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
const eventJSON = opt.value;
|
||||||
|
return eventJSON.imsi;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.ue.eventType'),
|
||||||
|
dataIndex: 'eventType',
|
||||||
|
key: 'eventType',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.ue.result'),
|
||||||
|
dataIndex: 'eventJSON',
|
||||||
|
key: 'result',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.ue.time'),
|
||||||
|
dataIndex: 'eventJSON',
|
||||||
|
key: 'time',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('common.operate'),
|
||||||
|
key: 'id',
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**表格分页器参数 */
|
||||||
|
let tablePagination = reactive({
|
||||||
|
/**当前页数 */
|
||||||
|
current: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
/**默认的每页条数 */
|
||||||
|
defaultPageSize: 20,
|
||||||
|
/**指定每页可以显示多少条 */
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
/**只有一页时是否隐藏分页器 */
|
||||||
|
hideOnSinglePage: false,
|
||||||
|
/**是否可以快速跳转至某页 */
|
||||||
|
showQuickJumper: true,
|
||||||
|
/**是否可以改变 pageSize */
|
||||||
|
showSizeChanger: true,
|
||||||
|
/**数据总数 */
|
||||||
|
total: 0,
|
||||||
|
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||||
|
onChange: (page: number, pageSize: number) => {
|
||||||
|
tablePagination.current = page;
|
||||||
|
tablePagination.pageSize = pageSize;
|
||||||
|
queryParams.pageNum = page;
|
||||||
|
queryParams.pageSize = pageSize;
|
||||||
|
fnGetList();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格紧凑型变更操作 */
|
||||||
|
function fnTableSize({ key }: MenuInfo) {
|
||||||
|
tableState.size = key as SizeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格多选 */
|
||||||
|
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||||
|
tableState.selectedRowKeys = keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**对话框对象信息状态类型 */
|
||||||
|
type ModalStateType = {
|
||||||
|
/**确定按钮 loading */
|
||||||
|
confirmLoading: boolean;
|
||||||
|
/**最大ID值 */
|
||||||
|
maxId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**对话框对象信息状态 */
|
||||||
|
let modalState: ModalStateType = reactive({
|
||||||
|
confirmLoading: false,
|
||||||
|
maxId: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录删除
|
||||||
|
* @param id 编号
|
||||||
|
*/
|
||||||
|
function fnRecordDelete(id: string) {
|
||||||
|
if (!id || modalState.confirmLoading) return;
|
||||||
|
let msg = id;
|
||||||
|
if (id === '0') {
|
||||||
|
msg = `${id}... ${tableState.selectedRowKeys.length}`;
|
||||||
|
id = tableState.selectedRowKeys.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.dashboard.ue.delTip', { msg }),
|
||||||
|
onOk() {
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
delAMFDataUE(id)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
fnGetList(1);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**查询列表, pageNum初始页数 */
|
||||||
|
function fnGetList(pageNum?: number) {
|
||||||
|
if (tableState.loading) return;
|
||||||
|
tableState.loading = true;
|
||||||
|
if (pageNum) {
|
||||||
|
queryParams.pageNum = pageNum;
|
||||||
|
}
|
||||||
|
if (!queryRangePicker.value) {
|
||||||
|
queryRangePicker.value = ['', ''];
|
||||||
|
}
|
||||||
|
queryParams.startTime = queryRangePicker.value[0];
|
||||||
|
queryParams.endTime = queryRangePicker.value[1];
|
||||||
|
listAMFDataUE(toRaw(queryParams)).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||||
|
// 取消勾选
|
||||||
|
if (tableState.selectedRowKeys.length > 0) {
|
||||||
|
tableState.selectedRowKeys = [];
|
||||||
|
}
|
||||||
|
tablePagination.total = res.total;
|
||||||
|
// 遍历处理cdr字符串数据
|
||||||
|
tableState.data = res.rows.map(item => {
|
||||||
|
let eventJSON = item.eventJSON;
|
||||||
|
if (!eventJSON) {
|
||||||
|
Reflect.set(item, 'eventJSON', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
eventJSON = JSON.parse(eventJSON);
|
||||||
|
Reflect.set(item, 'eventJSON', eventJSON);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
Reflect.set(item, 'eventJSON', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 取最大值ID用作实时累加
|
||||||
|
if (res.total > 0) {
|
||||||
|
modalState.maxId = Number(res.rows[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableState.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**列表导出 */
|
||||||
|
function fnExportList() {
|
||||||
|
if (modalState.confirmLoading) return;
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.dashboard.ue.exportTip'),
|
||||||
|
onOk() {
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
const querys = toRaw(queryParams);
|
||||||
|
querys.pageSize = 10000;
|
||||||
|
exportAMFDataUE(querys)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
saveAs(res.data, `amf_ue_event_export_${Date.now()}.xlsx`);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**实时数据开关 */
|
||||||
|
const realTimeData = ref<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实时数据
|
||||||
|
*/
|
||||||
|
function fnRealTime() {
|
||||||
|
realTimeData.value = !realTimeData.value;
|
||||||
|
if (realTimeData.value) {
|
||||||
|
// 建立链接
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: '/ws',
|
||||||
|
params: {
|
||||||
|
/**订阅通道组
|
||||||
|
*
|
||||||
|
* AMF_UE会话事件(GroupID:1010)
|
||||||
|
*/
|
||||||
|
subGroupID: '1010',
|
||||||
|
},
|
||||||
|
onmessage: wsMessage,
|
||||||
|
onerror: wsError,
|
||||||
|
};
|
||||||
|
ws.connect(options);
|
||||||
|
} else {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsError(ev: any) {
|
||||||
|
// 接收数据后回调
|
||||||
|
console.error(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsMessage(res: Record<string, any>) {
|
||||||
|
const { code, requestId, data } = res;
|
||||||
|
if (code === RESULT_CODE_ERROR) {
|
||||||
|
console.warn(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅组信息
|
||||||
|
if (!data?.groupId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ueEvent AMF_UE会话事件
|
||||||
|
if (data.groupId === '1010') {
|
||||||
|
const ueEvent = data.data;
|
||||||
|
queue.add(async () => {
|
||||||
|
modalState.maxId += 1;
|
||||||
|
tableState.data.unshift({
|
||||||
|
id: modalState.maxId,
|
||||||
|
neType: ueEvent.neType,
|
||||||
|
neName: ueEvent.neName, // 空
|
||||||
|
rmUID: ueEvent.rmUID, // 空
|
||||||
|
timestamp: ueEvent.timestamp,
|
||||||
|
eventType: ueEvent.eventType,
|
||||||
|
eventJSON: ueEvent.eventJSON,
|
||||||
|
});
|
||||||
|
tablePagination.total += 1;
|
||||||
|
if (tableState.data.length > 100) {
|
||||||
|
tableState.data.pop();
|
||||||
|
}
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始字典数据
|
||||||
|
Promise.allSettled([
|
||||||
|
getDict('ue_auth_code'),
|
||||||
|
getDict('ue_event_type'),
|
||||||
|
getDict('ue_event_cm_state'),
|
||||||
|
])
|
||||||
|
.then(resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
dict.ueAauthCode = resArr[0].value;
|
||||||
|
}
|
||||||
|
if (resArr[1].status === 'fulfilled') {
|
||||||
|
dict.ueEventType = resArr[1].value;
|
||||||
|
}
|
||||||
|
if (resArr[2].status === 'fulfilled') {
|
||||||
|
dict.ueEventCmState = resArr[2].value;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// 获取列表数据
|
||||||
|
fnGetList();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (ws.state() !== -1) {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageContainer>
|
||||||
|
<a-card
|
||||||
|
v-show="tableState.seached"
|
||||||
|
:bordered="false"
|
||||||
|
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||||
|
>
|
||||||
|
<!-- 表格搜索栏 -->
|
||||||
|
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.ue.eventType')"
|
||||||
|
name="eventType "
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="eventTypes"
|
||||||
|
mode="multiple"
|
||||||
|
:options="dict.ueEventType"
|
||||||
|
:placeholder="t('common.selectPlease')"
|
||||||
|
@change="fnQueryEventTypeChange"
|
||||||
|
></a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="4" :md="12" :xs="24">
|
||||||
|
<a-form-item label="IMSI" name="imsi ">
|
||||||
|
<a-input
|
||||||
|
v-model:value="queryParams.imsi"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="8" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.cdr.time')"
|
||||||
|
name="queryRangePicker"
|
||||||
|
>
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="queryRangePicker"
|
||||||
|
allow-clear
|
||||||
|
bordered
|
||||||
|
:show-time="{ format: 'HH:mm:ss' }"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="x"
|
||||||
|
style="width: 100%"
|
||||||
|
></a-range-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item>
|
||||||
|
<a-space :size="8">
|
||||||
|
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||||
|
<template #icon><SearchOutlined /></template>
|
||||||
|
{{ t('common.search') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="default" @click.prevent="fnQueryReset">
|
||||||
|
<template #icon><ClearOutlined /></template>
|
||||||
|
{{ t('common.reset') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||||
|
<!-- 插槽-卡片左侧侧 -->
|
||||||
|
<template #title>
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-popconfirm
|
||||||
|
placement="bottomLeft"
|
||||||
|
:title="
|
||||||
|
!realTimeData
|
||||||
|
? t('views.dashboard.ue.realTimeDataStart')
|
||||||
|
: t('views.dashboard.ue.realTimeDataStop')
|
||||||
|
"
|
||||||
|
ok-text="Yes"
|
||||||
|
cancel-text="No"
|
||||||
|
@confirm="fnRealTime()"
|
||||||
|
>
|
||||||
|
<a-button type="primary" :danger="realTimeData">
|
||||||
|
<template #icon><FundOutlined /> </template>
|
||||||
|
{{
|
||||||
|
!realTimeData
|
||||||
|
? t('views.dashboard.ue.realTimeDataStart')
|
||||||
|
: t('views.dashboard.ue.realTimeDataStop')
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
type="default"
|
||||||
|
danger
|
||||||
|
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||||
|
:loading="modalState.confirmLoading"
|
||||||
|
@click.prevent="fnRecordDelete('0')"
|
||||||
|
v-if="!hasRoles(['student'])"
|
||||||
|
>
|
||||||
|
<template #icon><DeleteOutlined /></template>
|
||||||
|
{{ t('common.deleteText') }}
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<a-button type="dashed" @click.prevent="fnExportList()">
|
||||||
|
<template #icon><ExportOutlined /></template>
|
||||||
|
{{ t('common.export') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 插槽-卡片右侧 -->
|
||||||
|
<template #extra>
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.searchBarText') }}</template>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="tableState.seached"
|
||||||
|
:checked-children="t('common.switch.show')"
|
||||||
|
:un-checked-children="t('common.switch.hide')"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.reloadText') }}</template>
|
||||||
|
<a-button type="text" @click.prevent="fnGetList()">
|
||||||
|
<template #icon><ReloadOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.sizeText') }}</template>
|
||||||
|
<a-dropdown trigger="click" placement="bottomRight">
|
||||||
|
<a-button type="text">
|
||||||
|
<template #icon><ColumnHeightOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu
|
||||||
|
:selected-keys="[tableState.size as string]"
|
||||||
|
@click="fnTableSize"
|
||||||
|
>
|
||||||
|
<a-menu-item key="default">
|
||||||
|
{{ t('common.size.default') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="middle">
|
||||||
|
{{ t('common.size.middle') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="small">
|
||||||
|
{{ t('common.size.small') }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 表格列表 -->
|
||||||
|
<a-table
|
||||||
|
class="table"
|
||||||
|
row-key="id"
|
||||||
|
:columns="hasRoles(['student']) ? tableColumns.filter((s:any)=>s.key !== 'id'): tableColumns"
|
||||||
|
:loading="tableState.loading"
|
||||||
|
:data-source="tableState.data"
|
||||||
|
:size="tableState.size"
|
||||||
|
:pagination="tablePagination"
|
||||||
|
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
|
||||||
|
:row-selection="{
|
||||||
|
type: 'checkbox',
|
||||||
|
columnWidth: '48px',
|
||||||
|
selectedRowKeys: tableState.selectedRowKeys,
|
||||||
|
onChange: fnTableSelectedRowKeys,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'eventType'">
|
||||||
|
<DictTag :options="dict.ueEventType" :value="record.eventType" />
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'result'">
|
||||||
|
<span v-if="record.eventType === 'auth-result'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.ueAauthCode"
|
||||||
|
:value="record.eventJSON.authCode"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span v-if="record.eventType === 'detach'">
|
||||||
|
<span>{{ t('views.dashboard.ue.resultOk') }}</span>
|
||||||
|
</span>
|
||||||
|
<span v-if="record.eventType === 'cm-state'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.ueEventCmState"
|
||||||
|
:value="record.eventJSON.status"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'time'">
|
||||||
|
<span
|
||||||
|
v-if="record.eventType === 'auth-result'"
|
||||||
|
:title="record.eventJSON.authTime"
|
||||||
|
>
|
||||||
|
{{ record.eventJSON.authTime }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="record.eventType === 'detach'"
|
||||||
|
:title="record.eventJSON.detachTime"
|
||||||
|
>
|
||||||
|
{{ record.eventJSON.detachTime }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="record.eventType === 'cm-state'"
|
||||||
|
:title="record.eventJSON.changeTime"
|
||||||
|
>
|
||||||
|
{{ record.eventJSON.changeTime }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'id'">
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.deleteText') }}</template>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
@click.prevent="fnRecordDelete(record.id)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #expandedRowRender="{ record }">
|
||||||
|
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.dashboard.ue.ueInfo') }}
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.common.neName') }}: </span>
|
||||||
|
<span>{{ record.neName }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.common.rmUid') }}: </span>
|
||||||
|
<span>{{ record.rmUID }}</span>
|
||||||
|
</div>
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.dashboard.ue.rowInfo') }}
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.ue.time') }}: </span>
|
||||||
|
<span
|
||||||
|
v-if="record.eventType === 'auth-result'"
|
||||||
|
:title="record.eventJSON.authTime"
|
||||||
|
>
|
||||||
|
{{ record.eventJSON.authTime }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="record.eventType === 'detach'"
|
||||||
|
:title="record.eventJSON.detachTime"
|
||||||
|
>
|
||||||
|
{{ record.eventJSON.detachTime }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="record.eventType === 'cm-state'"
|
||||||
|
:title="record.eventJSON.changeTime"
|
||||||
|
>
|
||||||
|
{{ record.eventJSON.changeTime }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.ue.eventType') }}: </span>
|
||||||
|
<DictTag :options="dict.ueEventType" :value="record.eventType" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.ue.result') }}: </span>
|
||||||
|
<span v-if="record.eventType === 'auth-result'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.ueAauthCode"
|
||||||
|
:value="record.eventJSON.authCode"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span v-if="record.eventType === 'detach'">
|
||||||
|
{{ t('views.dashboard.ue.resultOk') }}
|
||||||
|
</span>
|
||||||
|
<span v-if="record.eventType === 'cm-state'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.ueEventCmState"
|
||||||
|
:value="record.eventJSON.status"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-card>
|
||||||
|
</PageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.table :deep(.ant-pagination) {
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
839
practical_training/views/dashboard/imsCDR/index.vue
Normal file
@@ -0,0 +1,839 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, onMounted, toRaw, ref, onBeforeUnmount } from 'vue';
|
||||||
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
|
import { message, Modal } from 'ant-design-vue/es';
|
||||||
|
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||||
|
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||||
|
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import {
|
||||||
|
RESULT_CODE_ERROR,
|
||||||
|
RESULT_CODE_SUCCESS,
|
||||||
|
} from '@/constants/result-constants';
|
||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
import useNeInfoStore from '@/store/modules/neinfo';
|
||||||
|
import {
|
||||||
|
delIMSDataCDR,
|
||||||
|
exportIMSDataCDR,
|
||||||
|
listIMSDataCDR,
|
||||||
|
} from '@/api/neData/ims';
|
||||||
|
import { parseDateToStr, parseDuration } from '@/utils/date-utils';
|
||||||
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
|
import saveAs from 'file-saver';
|
||||||
|
import PQueue from 'p-queue';
|
||||||
|
import { hasRoles } from '@/plugins/auth-user';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { getDict } = useDictStore();
|
||||||
|
const ws = new WS();
|
||||||
|
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||||
|
|
||||||
|
/**字典数据 */
|
||||||
|
let dict: {
|
||||||
|
/**CDR SIP响应代码类别类型 */
|
||||||
|
cdrSipCode: DictType[];
|
||||||
|
/**CDR 呼叫类型 */
|
||||||
|
cdrCallType: DictType[];
|
||||||
|
} = reactive({
|
||||||
|
cdrSipCode: [],
|
||||||
|
cdrCallType: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**网元可选 */
|
||||||
|
let neOtions = ref<Record<string, any>[]>([]);
|
||||||
|
|
||||||
|
/**开始结束时间 */
|
||||||
|
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||||
|
|
||||||
|
/**查询参数 */
|
||||||
|
let queryParams = reactive({
|
||||||
|
/**网元类型 */
|
||||||
|
neType: 'IMS',
|
||||||
|
neId: '001',
|
||||||
|
recordType: '',
|
||||||
|
callerParty: '',
|
||||||
|
calledParty: '',
|
||||||
|
sortField: 'timestamp',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
/**开始时间 */
|
||||||
|
startTime: '',
|
||||||
|
/**结束时间 */
|
||||||
|
endTime: '',
|
||||||
|
/**当前页数 */
|
||||||
|
pageNum: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**查询参数重置 */
|
||||||
|
function fnQueryReset() {
|
||||||
|
recordTypes.value = [];
|
||||||
|
queryParams = Object.assign(queryParams, {
|
||||||
|
recordType: '',
|
||||||
|
callerParty: '',
|
||||||
|
calledParty: '',
|
||||||
|
startTime: '',
|
||||||
|
endTime: '',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
queryRangePicker.value = ['', ''];
|
||||||
|
tablePagination.current = 1;
|
||||||
|
tablePagination.pageSize = 20;
|
||||||
|
fnGetList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**记录类型 */
|
||||||
|
const recordTypes = ref<string[]>([]);
|
||||||
|
|
||||||
|
/**查询记录类型变更 */
|
||||||
|
function fnQueryRecordTypeChange(value: any) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
queryParams.recordType = value.join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格状态类型 */
|
||||||
|
type TabeStateType = {
|
||||||
|
/**加载等待 */
|
||||||
|
loading: boolean;
|
||||||
|
/**紧凑型 */
|
||||||
|
size: SizeType;
|
||||||
|
/**搜索栏 */
|
||||||
|
seached: boolean;
|
||||||
|
/**记录数据 */
|
||||||
|
data: object[];
|
||||||
|
/**勾选记录 */
|
||||||
|
selectedRowKeys: (string | number)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**表格状态 */
|
||||||
|
let tableState: TabeStateType = reactive({
|
||||||
|
loading: false,
|
||||||
|
size: 'middle',
|
||||||
|
seached: true,
|
||||||
|
data: [],
|
||||||
|
selectedRowKeys: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格字段列 */
|
||||||
|
let tableColumns: ColumnsType = [
|
||||||
|
{
|
||||||
|
title: t('common.rowId'),
|
||||||
|
dataIndex: 'id',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.recordType'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.recordType;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.type'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
key: 'callType',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.caller'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
key: 'callerParty',
|
||||||
|
align: 'left',
|
||||||
|
width: 120,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.callerParty;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.called'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
key: 'calledParty',
|
||||||
|
align: 'left',
|
||||||
|
width: 120,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.calledParty;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.result'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
key: 'cause',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.duration'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
key: 'callDuration',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.callType === 'sms'
|
||||||
|
? '-'
|
||||||
|
: parseDuration(cdrJSON.callDuration);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.seizureTime'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 200,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
if (typeof cdrJSON.seizureTime === 'number') {
|
||||||
|
return parseDateToStr(+cdrJSON.seizureTime * 1000);
|
||||||
|
}
|
||||||
|
return cdrJSON.seizureTime;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.releaseTime'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 200,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
if (typeof cdrJSON.releaseTime === 'number') {
|
||||||
|
return parseDateToStr(+cdrJSON.releaseTime * 1000);
|
||||||
|
}
|
||||||
|
return cdrJSON.releaseTime;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('common.operate'),
|
||||||
|
key: 'id',
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**表格分页器参数 */
|
||||||
|
let tablePagination = reactive({
|
||||||
|
/**当前页数 */
|
||||||
|
current: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
/**默认的每页条数 */
|
||||||
|
defaultPageSize: 20,
|
||||||
|
/**指定每页可以显示多少条 */
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
/**只有一页时是否隐藏分页器 */
|
||||||
|
hideOnSinglePage: false,
|
||||||
|
/**是否可以快速跳转至某页 */
|
||||||
|
showQuickJumper: true,
|
||||||
|
/**是否可以改变 pageSize */
|
||||||
|
showSizeChanger: true,
|
||||||
|
/**数据总数 */
|
||||||
|
total: 0,
|
||||||
|
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||||
|
onChange: (page: number, pageSize: number) => {
|
||||||
|
tablePagination.current = page;
|
||||||
|
tablePagination.pageSize = pageSize;
|
||||||
|
queryParams.pageNum = page;
|
||||||
|
queryParams.pageSize = pageSize;
|
||||||
|
fnGetList();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格紧凑型变更操作 */
|
||||||
|
function fnTableSize({ key }: MenuInfo) {
|
||||||
|
tableState.size = key as SizeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格多选 */
|
||||||
|
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||||
|
tableState.selectedRowKeys = keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**对话框对象信息状态类型 */
|
||||||
|
type ModalStateType = {
|
||||||
|
/**确定按钮 loading */
|
||||||
|
confirmLoading: boolean;
|
||||||
|
/**最大ID值 */
|
||||||
|
maxId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**对话框对象信息状态 */
|
||||||
|
let modalState: ModalStateType = reactive({
|
||||||
|
confirmLoading: false,
|
||||||
|
maxId: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录删除
|
||||||
|
* @param id 编号
|
||||||
|
*/
|
||||||
|
function fnRecordDelete(id: string) {
|
||||||
|
if (!id || modalState.confirmLoading) return;
|
||||||
|
let msg = id;
|
||||||
|
if (id === '0') {
|
||||||
|
msg = `${id}... ${tableState.selectedRowKeys.length}`;
|
||||||
|
id = tableState.selectedRowKeys.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.dashboard.cdr.delTip', { msg }),
|
||||||
|
onOk() {
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
delIMSDataCDR(id)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
fnGetList(1);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**查询列表, pageNum初始页数 */
|
||||||
|
function fnGetList(pageNum?: number) {
|
||||||
|
if (tableState.loading) return;
|
||||||
|
tableState.loading = true;
|
||||||
|
if (pageNum) {
|
||||||
|
queryParams.pageNum = pageNum;
|
||||||
|
}
|
||||||
|
if (!queryRangePicker.value) {
|
||||||
|
queryRangePicker.value = ['', ''];
|
||||||
|
}
|
||||||
|
queryParams.startTime = queryRangePicker.value[0];
|
||||||
|
queryParams.endTime = queryRangePicker.value[1];
|
||||||
|
listIMSDataCDR(toRaw(queryParams)).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||||
|
// 取消勾选
|
||||||
|
if (tableState.selectedRowKeys.length > 0) {
|
||||||
|
tableState.selectedRowKeys = [];
|
||||||
|
}
|
||||||
|
tablePagination.total = res.total;
|
||||||
|
// 遍历处理cdr字符串数据
|
||||||
|
tableState.data = res.rows.map(item => {
|
||||||
|
let cdrJSON = item.cdrJSON;
|
||||||
|
if (!cdrJSON) {
|
||||||
|
Reflect.set(item, 'cdrJSON', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cdrJSON = JSON.parse(cdrJSON);
|
||||||
|
Reflect.set(item, 'cdrJSON', cdrJSON);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
Reflect.set(item, 'cdrJSON', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 取最大值ID用作实时累加
|
||||||
|
if (res.total > 0) {
|
||||||
|
modalState.maxId = Number(res.rows[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableState.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**列表导出 */
|
||||||
|
function fnExportList() {
|
||||||
|
if (modalState.confirmLoading) return;
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.dashboard.cdr.exportTip'),
|
||||||
|
onOk() {
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
const querys = toRaw(queryParams);
|
||||||
|
querys.pageSize = 10000;
|
||||||
|
exportIMSDataCDR(querys)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
saveAs(res.data, `ims_cdr_event_export_${Date.now()}.xlsx`);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**实时数据开关 */
|
||||||
|
const realTimeData = ref<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实时数据
|
||||||
|
*/
|
||||||
|
function fnRealTime() {
|
||||||
|
realTimeData.value = !realTimeData.value;
|
||||||
|
if (realTimeData.value) {
|
||||||
|
tableState.seached = false;
|
||||||
|
// 建立链接
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: '/ws',
|
||||||
|
params: {
|
||||||
|
/**订阅通道组
|
||||||
|
*
|
||||||
|
* IMS_CDR会话事件(GroupID:1005)
|
||||||
|
*/
|
||||||
|
subGroupID: `1005_${queryParams.neId}`,
|
||||||
|
},
|
||||||
|
onmessage: wsMessage,
|
||||||
|
onerror: wsError,
|
||||||
|
};
|
||||||
|
ws.connect(options);
|
||||||
|
} else {
|
||||||
|
ws.close();
|
||||||
|
tableState.seached = true;
|
||||||
|
fnGetList(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsError(ev: any) {
|
||||||
|
// 接收数据后回调
|
||||||
|
console.error(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsMessage(res: Record<string, any>) {
|
||||||
|
const { code, requestId, data } = res;
|
||||||
|
if (code === RESULT_CODE_ERROR) {
|
||||||
|
console.warn(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅组信息
|
||||||
|
if (!data?.groupId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// cdrEvent CDR会话事件
|
||||||
|
if (data.groupId === `1005_${queryParams.neId}`) {
|
||||||
|
const cdrEvent = data.data;
|
||||||
|
queue.add(async () => {
|
||||||
|
modalState.maxId += 1;
|
||||||
|
tableState.data.unshift({
|
||||||
|
id: modalState.maxId,
|
||||||
|
neType: cdrEvent.neType,
|
||||||
|
neName: cdrEvent.neName,
|
||||||
|
rmUID: cdrEvent.rmUID,
|
||||||
|
timestamp: cdrEvent.timestamp,
|
||||||
|
cdrJSON: cdrEvent.CDR,
|
||||||
|
});
|
||||||
|
tablePagination.total += 1;
|
||||||
|
if (tableState.data.length > 100) {
|
||||||
|
tableState.data.pop();
|
||||||
|
}
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始字典数据
|
||||||
|
Promise.allSettled([getDict('cdr_sip_code'), getDict('cdr_call_type')]).then(
|
||||||
|
resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
dict.cdrSipCode = resArr[0].value;
|
||||||
|
}
|
||||||
|
if (resArr[1].status === 'fulfilled') {
|
||||||
|
dict.cdrCallType = resArr[1].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// 获取网元网元列表
|
||||||
|
useNeInfoStore()
|
||||||
|
.fnNelist()
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||||
|
if (res.data.length > 0) {
|
||||||
|
let arr: Record<string, any>[] = [];
|
||||||
|
res.data.forEach(i => {
|
||||||
|
if (i.neType === 'IMS') {
|
||||||
|
arr.push({ value: i.neId, label: i.neName });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
neOtions.value = arr;
|
||||||
|
if (arr.length > 0) {
|
||||||
|
queryParams.neId = arr[0].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.warning({
|
||||||
|
content: t('common.noData'),
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// 获取列表数据
|
||||||
|
fnGetList();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (ws.state() !== -1) {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageContainer>
|
||||||
|
<a-card
|
||||||
|
v-show="tableState.seached"
|
||||||
|
:bordered="false"
|
||||||
|
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||||
|
>
|
||||||
|
<!-- 表格搜索栏 -->
|
||||||
|
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item label="IMS" name="neId ">
|
||||||
|
<a-select
|
||||||
|
v-model:value="queryParams.neId"
|
||||||
|
:options="neOtions"
|
||||||
|
:placeholder="t('common.selectPlease')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.cdr.called')"
|
||||||
|
name="calledParty"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="queryParams.calledParty"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.cdr.caller')"
|
||||||
|
name="callerParty "
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="queryParams.callerParty"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="4" :md="12" :xs="24">
|
||||||
|
<a-form-item>
|
||||||
|
<a-space :size="8">
|
||||||
|
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||||
|
<template #icon><SearchOutlined /></template>
|
||||||
|
{{ t('common.search') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="default" @click.prevent="fnQueryReset">
|
||||||
|
<template #icon><ClearOutlined /></template>
|
||||||
|
{{ t('common.reset') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="8" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.cdr.recordType')"
|
||||||
|
name="recordType"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="recordTypes"
|
||||||
|
mode="multiple"
|
||||||
|
:options="['MOC', 'MTC'].map(v => ({ value: v }))"
|
||||||
|
:placeholder="t('common.selectPlease')"
|
||||||
|
@change="fnQueryRecordTypeChange"
|
||||||
|
></a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="8" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.cdr.time')"
|
||||||
|
name="queryRangePicker"
|
||||||
|
>
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="queryRangePicker"
|
||||||
|
allow-clear
|
||||||
|
bordered
|
||||||
|
:show-time="{ format: 'HH:mm:ss' }"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="x"
|
||||||
|
style="width: 100%"
|
||||||
|
></a-range-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||||
|
<!-- 插槽-卡片左侧侧 -->
|
||||||
|
<template #title>
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-popconfirm
|
||||||
|
placement="bottomLeft"
|
||||||
|
:title="
|
||||||
|
!realTimeData
|
||||||
|
? t('views.dashboard.cdr.realTimeDataStart')
|
||||||
|
: t('views.dashboard.cdr.realTimeDataStop')
|
||||||
|
"
|
||||||
|
ok-text="Yes"
|
||||||
|
cancel-text="No"
|
||||||
|
@confirm="fnRealTime()"
|
||||||
|
>
|
||||||
|
<a-button type="primary" :danger="realTimeData">
|
||||||
|
<template #icon><FundOutlined /> </template>
|
||||||
|
{{
|
||||||
|
!realTimeData
|
||||||
|
? t('views.dashboard.cdr.realTimeDataStart')
|
||||||
|
: t('views.dashboard.cdr.realTimeDataStop')
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
type="default"
|
||||||
|
danger
|
||||||
|
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||||
|
:loading="modalState.confirmLoading"
|
||||||
|
@click.prevent="fnRecordDelete('0')"
|
||||||
|
v-if="!hasRoles(['student'])"
|
||||||
|
>
|
||||||
|
<template #icon><DeleteOutlined /></template>
|
||||||
|
{{ t('common.deleteText') }}
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<a-button type="dashed" @click.prevent="fnExportList()">
|
||||||
|
<template #icon><ExportOutlined /></template>
|
||||||
|
{{ t('common.export') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 插槽-卡片右侧 -->
|
||||||
|
<template #extra>
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.searchBarText') }}</template>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="tableState.seached"
|
||||||
|
:checked-children="t('common.switch.show')"
|
||||||
|
:un-checked-children="t('common.switch.hide')"
|
||||||
|
size="small"
|
||||||
|
:disabled="realTimeData"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.reloadText') }}</template>
|
||||||
|
<a-button type="text" @click.prevent="fnGetList()">
|
||||||
|
<template #icon><ReloadOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.sizeText') }}</template>
|
||||||
|
<a-dropdown trigger="click" placement="bottomRight">
|
||||||
|
<a-button type="text">
|
||||||
|
<template #icon><ColumnHeightOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu
|
||||||
|
:selected-keys="[tableState.size as string]"
|
||||||
|
@click="fnTableSize"
|
||||||
|
>
|
||||||
|
<a-menu-item key="default">
|
||||||
|
{{ t('common.size.default') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="middle">
|
||||||
|
{{ t('common.size.middle') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="small">
|
||||||
|
{{ t('common.size.small') }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 表格列表 -->
|
||||||
|
<a-table
|
||||||
|
class="table"
|
||||||
|
row-key="id"
|
||||||
|
:columns="hasRoles(['student']) ? tableColumns.filter((s:any)=>s.key !== 'id'): tableColumns"
|
||||||
|
:loading="tableState.loading"
|
||||||
|
:data-source="tableState.data"
|
||||||
|
:size="tableState.size"
|
||||||
|
:pagination="tablePagination"
|
||||||
|
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
|
||||||
|
:row-selection="{
|
||||||
|
type: 'checkbox',
|
||||||
|
columnWidth: '48px',
|
||||||
|
selectedRowKeys: tableState.selectedRowKeys,
|
||||||
|
onChange: fnTableSelectedRowKeys,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'callType'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.cdrCallType"
|
||||||
|
:value="record.cdrJSON.callType"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'cause'">
|
||||||
|
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.cdrSipCode"
|
||||||
|
:value="record.cdrJSON.cause"
|
||||||
|
value-default="0"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ t('views.dashboard.cdr.resultOk') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'id'">
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.deleteText') }}</template>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
@click.prevent="fnRecordDelete(record.id)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #expandedRowRender="{ record }">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="5" :md="12" :xs="24">
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.dashboard.cdr.cdrInfo') }}
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.common.neName') }}: </span>
|
||||||
|
<span>{{ record.neName }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.common.rmUid') }}: </span>
|
||||||
|
<span>{{ record.rmUID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.time') }}: </span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
typeof record.cdrJSON.releaseTime === 'number'
|
||||||
|
? parseDateToStr(+record.cdrJSON.releaseTime * 1000)
|
||||||
|
: record.cdrJSON.releaseTime
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.dashboard.cdr.rowInfo') }}
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.type') }}: </span>
|
||||||
|
<DictTag
|
||||||
|
:options="dict.cdrCallType"
|
||||||
|
:value="record.cdrJSON.callType"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.duration') }}: </span>
|
||||||
|
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||||
|
{{ parseDuration(record.cdrJSON.callDuration) }}
|
||||||
|
</span>
|
||||||
|
<span v-else> - </span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.caller') }}: </span>
|
||||||
|
<span>{{ record.cdrJSON.callerParty }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.called') }}: </span>
|
||||||
|
<span>{{ record.cdrJSON.calledParty }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.result') }}: </span>
|
||||||
|
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.cdrSipCode"
|
||||||
|
:value="record.cdrJSON.cause"
|
||||||
|
value-default="0"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ t('views.dashboard.cdr.resultOk') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.seizureTime') }}: </span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
typeof record.cdrJSON.seizureTime === 'number'
|
||||||
|
? parseDateToStr(+record.cdrJSON.seizureTime * 1000)
|
||||||
|
: record.cdrJSON.seizureTime
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.releaseTime') }}: </span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
typeof record.cdrJSON.releaseTime === 'number'
|
||||||
|
? parseDateToStr(+record.cdrJSON.releaseTime * 1000)
|
||||||
|
: record.cdrJSON.releaseTime
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-card>
|
||||||
|
</PageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.table :deep(.ant-pagination) {
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
742
practical_training/views/dashboard/mmeUE/index.vue
Normal file
@@ -0,0 +1,742 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, onMounted, toRaw, ref, onBeforeUnmount } from 'vue';
|
||||||
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
|
import { message, Modal } from 'ant-design-vue/es';
|
||||||
|
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||||
|
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||||
|
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import {
|
||||||
|
RESULT_CODE_ERROR,
|
||||||
|
RESULT_CODE_SUCCESS,
|
||||||
|
} from '@/constants/result-constants';
|
||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
import useNeInfoStore from '@/store/modules/neinfo';
|
||||||
|
import { listMMEDataUE, delMMEDataUE, exportMMEDataUE } from '@/api/neData/mme';
|
||||||
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
|
import saveAs from 'file-saver';
|
||||||
|
import PQueue from 'p-queue';
|
||||||
|
import { hasRoles } from '@/plugins/auth-user';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { getDict } = useDictStore();
|
||||||
|
const ws = new WS();
|
||||||
|
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||||
|
|
||||||
|
/**网元可选 */
|
||||||
|
let neOtions = ref<Record<string, any>[]>([]);
|
||||||
|
|
||||||
|
/**字典数据 */
|
||||||
|
let dict: {
|
||||||
|
/**UE 事件认证代码类型 */
|
||||||
|
ueAauthCode: DictType[];
|
||||||
|
/**UE 事件类型 */
|
||||||
|
ueEventType: DictType[];
|
||||||
|
/**UE 事件CM状态 */
|
||||||
|
ueEventCmState: DictType[];
|
||||||
|
} = reactive({
|
||||||
|
ueAauthCode: [],
|
||||||
|
ueEventType: [],
|
||||||
|
ueEventCmState: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**开始结束时间 */
|
||||||
|
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||||
|
|
||||||
|
/**查询参数 */
|
||||||
|
let queryParams = reactive({
|
||||||
|
/**网元类型 */
|
||||||
|
neType: 'MME',
|
||||||
|
neId: '001',
|
||||||
|
eventType: '',
|
||||||
|
imsi: '',
|
||||||
|
sortField: 'timestamp',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
/**开始时间 */
|
||||||
|
startTime: '',
|
||||||
|
/**结束时间 */
|
||||||
|
endTime: '',
|
||||||
|
/**当前页数 */
|
||||||
|
pageNum: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**查询参数重置 */
|
||||||
|
function fnQueryReset() {
|
||||||
|
eventTypes.value = [];
|
||||||
|
queryParams = Object.assign(queryParams, {
|
||||||
|
eventType: '',
|
||||||
|
imsi: '',
|
||||||
|
startTime: '',
|
||||||
|
endTime: '',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
queryRangePicker.value = ['', ''];
|
||||||
|
tablePagination.current = 1;
|
||||||
|
tablePagination.pageSize = 20;
|
||||||
|
fnGetList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**记录类型 */
|
||||||
|
const eventTypes = ref<string[]>([]);
|
||||||
|
|
||||||
|
/**查询记录类型变更 */
|
||||||
|
function fnQueryEventTypeChange(value: any) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
queryParams.eventType = value.join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格状态类型 */
|
||||||
|
type TabeStateType = {
|
||||||
|
/**加载等待 */
|
||||||
|
loading: boolean;
|
||||||
|
/**紧凑型 */
|
||||||
|
size: SizeType;
|
||||||
|
/**搜索栏 */
|
||||||
|
seached: boolean;
|
||||||
|
/**记录数据 */
|
||||||
|
data: object[];
|
||||||
|
/**勾选记录 */
|
||||||
|
selectedRowKeys: (string | number)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**表格状态 */
|
||||||
|
let tableState: TabeStateType = reactive({
|
||||||
|
loading: false,
|
||||||
|
size: 'middle',
|
||||||
|
seached: true,
|
||||||
|
data: [],
|
||||||
|
selectedRowKeys: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格字段列 */
|
||||||
|
let tableColumns: ColumnsType = [
|
||||||
|
{
|
||||||
|
title: t('common.rowId'),
|
||||||
|
dataIndex: 'id',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'IMSI',
|
||||||
|
dataIndex: 'eventJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
const eventJSON = opt.value;
|
||||||
|
return eventJSON.imsi;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.ue.eventType'),
|
||||||
|
dataIndex: 'eventType',
|
||||||
|
key: 'eventType',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.ue.result'),
|
||||||
|
dataIndex: 'eventJSON',
|
||||||
|
key: 'result',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.ue.time'),
|
||||||
|
dataIndex: 'eventJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return parseDateToStr(+cdrJSON.timestamp * 1000);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('common.operate'),
|
||||||
|
key: 'id',
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**表格分页器参数 */
|
||||||
|
let tablePagination = reactive({
|
||||||
|
/**当前页数 */
|
||||||
|
current: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
/**默认的每页条数 */
|
||||||
|
defaultPageSize: 20,
|
||||||
|
/**指定每页可以显示多少条 */
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
/**只有一页时是否隐藏分页器 */
|
||||||
|
hideOnSinglePage: false,
|
||||||
|
/**是否可以快速跳转至某页 */
|
||||||
|
showQuickJumper: true,
|
||||||
|
/**是否可以改变 pageSize */
|
||||||
|
showSizeChanger: true,
|
||||||
|
/**数据总数 */
|
||||||
|
total: 0,
|
||||||
|
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||||
|
onChange: (page: number, pageSize: number) => {
|
||||||
|
tablePagination.current = page;
|
||||||
|
tablePagination.pageSize = pageSize;
|
||||||
|
queryParams.pageNum = page;
|
||||||
|
queryParams.pageSize = pageSize;
|
||||||
|
fnGetList();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格紧凑型变更操作 */
|
||||||
|
function fnTableSize({ key }: MenuInfo) {
|
||||||
|
tableState.size = key as SizeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格多选 */
|
||||||
|
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||||
|
tableState.selectedRowKeys = keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**对话框对象信息状态类型 */
|
||||||
|
type ModalStateType = {
|
||||||
|
/**确定按钮 loading */
|
||||||
|
confirmLoading: boolean;
|
||||||
|
/**最大ID值 */
|
||||||
|
maxId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**对话框对象信息状态 */
|
||||||
|
let modalState: ModalStateType = reactive({
|
||||||
|
confirmLoading: false,
|
||||||
|
maxId: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录删除
|
||||||
|
* @param id 编号
|
||||||
|
*/
|
||||||
|
function fnRecordDelete(id: string) {
|
||||||
|
if (!id || modalState.confirmLoading) return;
|
||||||
|
let msg = id;
|
||||||
|
if (id === '0') {
|
||||||
|
msg = `${id}... ${tableState.selectedRowKeys.length}`;
|
||||||
|
id = tableState.selectedRowKeys.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.dashboard.ue.delTip', { msg }),
|
||||||
|
onOk() {
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
delMMEDataUE(id)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
fnGetList(1);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**查询列表, pageNum初始页数 */
|
||||||
|
function fnGetList(pageNum?: number) {
|
||||||
|
if (tableState.loading) return;
|
||||||
|
tableState.loading = true;
|
||||||
|
if (pageNum) {
|
||||||
|
queryParams.pageNum = pageNum;
|
||||||
|
}
|
||||||
|
if (!queryRangePicker.value) {
|
||||||
|
queryRangePicker.value = ['', ''];
|
||||||
|
}
|
||||||
|
queryParams.startTime = queryRangePicker.value[0];
|
||||||
|
queryParams.endTime = queryRangePicker.value[1];
|
||||||
|
listMMEDataUE(toRaw(queryParams)).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||||
|
// 取消勾选
|
||||||
|
if (tableState.selectedRowKeys.length > 0) {
|
||||||
|
tableState.selectedRowKeys = [];
|
||||||
|
}
|
||||||
|
tablePagination.total = res.total;
|
||||||
|
// 遍历处理cdr字符串数据
|
||||||
|
tableState.data = res.rows.map(item => {
|
||||||
|
let eventJSON = item.eventJSON;
|
||||||
|
if (!eventJSON) {
|
||||||
|
Reflect.set(item, 'eventJSON', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
eventJSON = JSON.parse(eventJSON);
|
||||||
|
Reflect.set(item, 'eventJSON', eventJSON);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
Reflect.set(item, 'eventJSON', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 取最大值ID用作实时累加
|
||||||
|
if (res.total > 0) {
|
||||||
|
modalState.maxId = Number(res.rows[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableState.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**列表导出 */
|
||||||
|
function fnExportList() {
|
||||||
|
if (modalState.confirmLoading) return;
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.dashboard.ue.exportTip'),
|
||||||
|
onOk() {
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
const querys = toRaw(queryParams);
|
||||||
|
querys.pageSize = 10000;
|
||||||
|
exportMMEDataUE(querys)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
saveAs(res.data, `mme_ue_event_export_${Date.now()}.xlsx`);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**实时数据开关 */
|
||||||
|
const realTimeData = ref<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实时数据
|
||||||
|
*/
|
||||||
|
function fnRealTime() {
|
||||||
|
realTimeData.value = !realTimeData.value;
|
||||||
|
if (realTimeData.value) {
|
||||||
|
tableState.seached = false;
|
||||||
|
// 建立链接
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: '/ws',
|
||||||
|
params: {
|
||||||
|
/**订阅通道组
|
||||||
|
*
|
||||||
|
* MME_UE会话事件(GroupID:1011)
|
||||||
|
*/
|
||||||
|
subGroupID: `1011_${queryParams.neId}`,
|
||||||
|
},
|
||||||
|
onmessage: wsMessage,
|
||||||
|
onerror: wsError,
|
||||||
|
};
|
||||||
|
ws.connect(options);
|
||||||
|
} else {
|
||||||
|
ws.close();
|
||||||
|
tableState.seached = true;
|
||||||
|
fnGetList(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsError(ev: any) {
|
||||||
|
// 接收数据后回调
|
||||||
|
console.error(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsMessage(res: Record<string, any>) {
|
||||||
|
const { code, requestId, data } = res;
|
||||||
|
if (code === RESULT_CODE_ERROR) {
|
||||||
|
console.warn(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅组信息
|
||||||
|
if (!data?.groupId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ueEvent MME_UE会话事件
|
||||||
|
if (data.groupId === `1011_${queryParams.neId}`) {
|
||||||
|
const ueEvent = data.data;
|
||||||
|
queue.add(async () => {
|
||||||
|
modalState.maxId += 1;
|
||||||
|
tableState.data.unshift({
|
||||||
|
id: modalState.maxId,
|
||||||
|
neType: ueEvent.neType,
|
||||||
|
neName: ueEvent.neName, // 空
|
||||||
|
rmUID: ueEvent.rmUID, // 空
|
||||||
|
timestamp: ueEvent.timestamp,
|
||||||
|
eventType: ueEvent.eventType,
|
||||||
|
eventJSON: ueEvent.eventJSON,
|
||||||
|
});
|
||||||
|
tablePagination.total += 1;
|
||||||
|
if (tableState.data.length > 100) {
|
||||||
|
tableState.data.pop();
|
||||||
|
}
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始字典数据
|
||||||
|
Promise.allSettled([
|
||||||
|
getDict('ue_auth_code'),
|
||||||
|
getDict('ue_event_type'),
|
||||||
|
getDict('ue_event_cm_state'),
|
||||||
|
]).then(resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
dict.ueAauthCode = resArr[0].value;
|
||||||
|
}
|
||||||
|
if (resArr[1].status === 'fulfilled') {
|
||||||
|
const ueEventType: any[] = JSON.parse(JSON.stringify(resArr[1]));
|
||||||
|
dict.ueEventType = ueEventType.map(item => {
|
||||||
|
if (item.value === 'cm-state') {
|
||||||
|
item.label = item.label.replace('CM', 'ECM');
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (resArr[2].status === 'fulfilled') {
|
||||||
|
dict.ueEventCmState = resArr[2].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取网元网元列表
|
||||||
|
useNeInfoStore()
|
||||||
|
.fnNelist()
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||||
|
if (res.data.length > 0) {
|
||||||
|
let arr: Record<string, any>[] = [];
|
||||||
|
res.data.forEach(i => {
|
||||||
|
if (i.neType === 'MME') {
|
||||||
|
arr.push({ value: i.neId, label: i.neName });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
neOtions.value = arr;
|
||||||
|
if (arr.length > 0) {
|
||||||
|
queryParams.neId = arr[0].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.warning({
|
||||||
|
content: t('common.noData'),
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// 获取列表数据
|
||||||
|
fnGetList();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (ws.state() !== -1) {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageContainer>
|
||||||
|
<a-card
|
||||||
|
v-show="tableState.seached"
|
||||||
|
:bordered="false"
|
||||||
|
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||||
|
>
|
||||||
|
<!-- 表格搜索栏 -->
|
||||||
|
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item label="MME" name="neId ">
|
||||||
|
<a-select
|
||||||
|
v-model:value="queryParams.neId"
|
||||||
|
:options="neOtions"
|
||||||
|
:placeholder="t('common.selectPlease')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.ue.eventType')"
|
||||||
|
name="eventType "
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="eventTypes"
|
||||||
|
mode="multiple"
|
||||||
|
:options="dict.ueEventType"
|
||||||
|
:placeholder="t('common.selectPlease')"
|
||||||
|
@change="fnQueryEventTypeChange"
|
||||||
|
></a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="4" :md="12" :xs="24">
|
||||||
|
<a-form-item label="IMSI" name="imsi ">
|
||||||
|
<a-input
|
||||||
|
v-model:value="queryParams.imsi"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="8" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.cdr.time')"
|
||||||
|
name="queryRangePicker"
|
||||||
|
>
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="queryRangePicker"
|
||||||
|
allow-clear
|
||||||
|
bordered
|
||||||
|
:show-time="{ format: 'HH:mm:ss' }"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="x"
|
||||||
|
style="width: 100%"
|
||||||
|
></a-range-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item>
|
||||||
|
<a-space :size="8">
|
||||||
|
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||||
|
<template #icon><SearchOutlined /></template>
|
||||||
|
{{ t('common.search') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="default" @click.prevent="fnQueryReset">
|
||||||
|
<template #icon><ClearOutlined /></template>
|
||||||
|
{{ t('common.reset') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||||
|
<!-- 插槽-卡片左侧侧 -->
|
||||||
|
<template #title>
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-popconfirm
|
||||||
|
placement="bottomLeft"
|
||||||
|
:title="
|
||||||
|
!realTimeData
|
||||||
|
? t('views.dashboard.ue.realTimeDataStart')
|
||||||
|
: t('views.dashboard.ue.realTimeDataStop')
|
||||||
|
"
|
||||||
|
ok-text="Yes"
|
||||||
|
cancel-text="No"
|
||||||
|
@confirm="fnRealTime()"
|
||||||
|
>
|
||||||
|
<a-button type="primary" :danger="realTimeData">
|
||||||
|
<template #icon><FundOutlined /> </template>
|
||||||
|
{{
|
||||||
|
!realTimeData
|
||||||
|
? t('views.dashboard.ue.realTimeDataStart')
|
||||||
|
: t('views.dashboard.ue.realTimeDataStop')
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
type="default"
|
||||||
|
danger
|
||||||
|
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||||
|
:loading="modalState.confirmLoading"
|
||||||
|
@click.prevent="fnRecordDelete('0')"
|
||||||
|
v-if="!hasRoles(['student'])"
|
||||||
|
>
|
||||||
|
<template #icon><DeleteOutlined /></template>
|
||||||
|
{{ t('common.deleteText') }}
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<a-button type="dashed" @click.prevent="fnExportList()">
|
||||||
|
<template #icon><ExportOutlined /></template>
|
||||||
|
{{ t('common.export') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 插槽-卡片右侧 -->
|
||||||
|
<template #extra>
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.searchBarText') }}</template>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="tableState.seached"
|
||||||
|
:checked-children="t('common.switch.show')"
|
||||||
|
:un-checked-children="t('common.switch.hide')"
|
||||||
|
size="small"
|
||||||
|
:disabled="realTimeData"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.reloadText') }}</template>
|
||||||
|
<a-button type="text" @click.prevent="fnGetList()">
|
||||||
|
<template #icon><ReloadOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.sizeText') }}</template>
|
||||||
|
<a-dropdown trigger="click" placement="bottomRight">
|
||||||
|
<a-button type="text">
|
||||||
|
<template #icon><ColumnHeightOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu
|
||||||
|
:selected-keys="[tableState.size as string]"
|
||||||
|
@click="fnTableSize"
|
||||||
|
>
|
||||||
|
<a-menu-item key="default">
|
||||||
|
{{ t('common.size.default') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="middle">
|
||||||
|
{{ t('common.size.middle') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="small">
|
||||||
|
{{ t('common.size.small') }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 表格列表 -->
|
||||||
|
<a-table
|
||||||
|
class="table"
|
||||||
|
row-key="id"
|
||||||
|
:columns="hasRoles(['student']) ? tableColumns.filter((s:any)=>s.key !== 'id'): tableColumns"
|
||||||
|
:loading="tableState.loading"
|
||||||
|
:data-source="tableState.data"
|
||||||
|
:size="tableState.size"
|
||||||
|
:pagination="tablePagination"
|
||||||
|
:scroll="{ x: tableColumns.length * 120, y: 'calc(100vh - 480px)' }"
|
||||||
|
:row-selection="{
|
||||||
|
type: 'checkbox',
|
||||||
|
columnWidth: '48px',
|
||||||
|
selectedRowKeys: tableState.selectedRowKeys,
|
||||||
|
onChange: fnTableSelectedRowKeys,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'eventType'">
|
||||||
|
<DictTag :options="dict.ueEventType" :value="record.eventType" />
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'result'">
|
||||||
|
<span v-if="record.eventType === 'auth-result'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.ueAauthCode"
|
||||||
|
:value="record.eventJSON.result"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span v-if="record.eventType === 'detach'">
|
||||||
|
<span>{{ t('views.dashboard.ue.resultOk') }}</span>
|
||||||
|
</span>
|
||||||
|
<span v-if="record.eventType === 'cm-state'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.ueEventCmState"
|
||||||
|
:value="record.eventJSON.result"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'id'">
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.deleteText') }}</template>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
@click.prevent="fnRecordDelete(record.id)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #expandedRowRender="{ record }">
|
||||||
|
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.dashboard.ue.ueInfo') }}
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.common.neName') }}: </span>
|
||||||
|
<span>{{ record.neName }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.common.rmUid') }}: </span>
|
||||||
|
<span>{{ record.rmUID }}</span>
|
||||||
|
</div>
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.dashboard.ue.rowInfo') }}
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.ue.time') }}: </span>
|
||||||
|
{{ parseDateToStr(record.eventJSON.timestamp * 1000) }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.ue.eventType') }}: </span>
|
||||||
|
<DictTag :options="dict.ueEventType" :value="record.eventType" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.ue.result') }}: </span>
|
||||||
|
<span v-if="record.eventType === 'auth-result'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.ueAauthCode"
|
||||||
|
:value="record.eventJSON.result"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span v-if="record.eventType === 'detach'">
|
||||||
|
{{ t('views.dashboard.ue.resultOk') }}
|
||||||
|
</span>
|
||||||
|
<span v-if="record.eventType === 'cm-state'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.ueEventCmState"
|
||||||
|
:value="record.eventJSON.result"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-card>
|
||||||
|
</PageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.table :deep(.ant-pagination) {
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import * as echarts from 'echarts/core';
|
||||||
|
import {
|
||||||
|
TitleComponent,
|
||||||
|
TitleComponentOption,
|
||||||
|
TooltipComponent,
|
||||||
|
TooltipComponentOption,
|
||||||
|
GridComponent,
|
||||||
|
GridComponentOption,
|
||||||
|
LegendComponent,
|
||||||
|
LegendComponentOption,
|
||||||
|
} from 'echarts/components';
|
||||||
|
import {
|
||||||
|
PieChart,
|
||||||
|
PieSeriesOption,
|
||||||
|
BarChart,
|
||||||
|
BarSeriesOption,
|
||||||
|
} from 'echarts/charts';
|
||||||
|
import { LabelLayout } from 'echarts/features';
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
|
|
||||||
|
import { markRaw, onMounted, ref } from 'vue';
|
||||||
|
import { origGet, top3Sel } from '@/api/faultManage/actAlarm';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
echarts.use([
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
GridComponent,
|
||||||
|
LegendComponent,
|
||||||
|
PieChart,
|
||||||
|
BarChart,
|
||||||
|
CanvasRenderer,
|
||||||
|
LabelLayout,
|
||||||
|
]);
|
||||||
|
|
||||||
|
type EChartsOption = echarts.ComposeOption<
|
||||||
|
| TitleComponentOption
|
||||||
|
| TooltipComponentOption
|
||||||
|
| GridComponentOption
|
||||||
|
| LegendComponentOption
|
||||||
|
| PieSeriesOption
|
||||||
|
| BarSeriesOption
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**图DOM节点实例对象 */
|
||||||
|
const alarmTypeBar = ref<HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
|
/**图实例对象 */
|
||||||
|
const alarmTypeBarChart = ref<any>(null);
|
||||||
|
|
||||||
|
/**告警类型数据 */
|
||||||
|
const alarmTypeType = ref<any>([
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('views.index.Critical'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('views.index.Major'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('views.index.Minor'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
name: t('views.index.Warning'),
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// value: 0,
|
||||||
|
// name: t('views.index.Event'),
|
||||||
|
// },
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**告警类型Top数据 */
|
||||||
|
const alarmTypeTypeTop = ref<any>([
|
||||||
|
{ name: 'AMF', value: 0 },
|
||||||
|
{ name: 'UDM', value: 0 },
|
||||||
|
{ name: 'SMF', value: 0 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
//
|
||||||
|
function initPicture() {
|
||||||
|
Promise.allSettled([origGet(), top3Sel()])
|
||||||
|
.then(resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
const res0 = resArr[0].value;
|
||||||
|
if (res0.code === RESULT_CODE_SUCCESS && Array.isArray(res0.data)) {
|
||||||
|
for (const item of res0.data) {
|
||||||
|
let index = 0;
|
||||||
|
switch (item.name) {
|
||||||
|
case 'Critical':
|
||||||
|
index = 0;
|
||||||
|
break;
|
||||||
|
case 'Major':
|
||||||
|
index = 1;
|
||||||
|
break;
|
||||||
|
case 'Minor':
|
||||||
|
index = 2;
|
||||||
|
break;
|
||||||
|
case 'Warning':
|
||||||
|
index = 3;
|
||||||
|
break;
|
||||||
|
// case 'Event':
|
||||||
|
// index = 4;
|
||||||
|
// break;
|
||||||
|
}
|
||||||
|
alarmTypeType.value[index].value = Number(item.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resArr[1].status === 'fulfilled') {
|
||||||
|
const res1 = resArr[1].value;
|
||||||
|
if (res1.code === RESULT_CODE_SUCCESS && Array.isArray(res1.data)) {
|
||||||
|
alarmTypeTypeTop.value = alarmTypeTypeTop.value
|
||||||
|
.concat(res1.data)
|
||||||
|
.sort((a: any, b: any) => {
|
||||||
|
return b.value - a.value;
|
||||||
|
})
|
||||||
|
.slice(0, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
const optionData: EChartsOption = {
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: t('views.dashboard.overview.alarmTypeBar.topTitle'),
|
||||||
|
textStyle: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: '14',
|
||||||
|
fontWeight: 400,
|
||||||
|
},
|
||||||
|
top: '50%',
|
||||||
|
left: '0%',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{b} : {c}',
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
right: '2%',
|
||||||
|
top: '12%',
|
||||||
|
data: alarmTypeType.value.map((item: any) => item.name), //label数组
|
||||||
|
textStyle: {
|
||||||
|
color: '#A7D6F4', // 设置图例文字颜色
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: [
|
||||||
|
{
|
||||||
|
top: '60%',
|
||||||
|
left: '15%',
|
||||||
|
right: '25%',
|
||||||
|
bottom: '10%',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
//饼图:
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: '35%',
|
||||||
|
color: ['#f5222d', '#fa8c16', '#fadb14', '#1677ff', '#13c2c2'],
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'inner',
|
||||||
|
formatter: (params: any) => {
|
||||||
|
if (!params.value) return '';
|
||||||
|
return `${params.value}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
labelLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
center: ['35%', '25%'],
|
||||||
|
data: alarmTypeType.value,
|
||||||
|
zlevel: 2, // 设置zlevel为1,使得柱状图在下层显示
|
||||||
|
itemStyle: {
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowOffsetX: 0,
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
//柱状
|
||||||
|
{
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 12, // 柱子宽度
|
||||||
|
barCategoryGap: '30%', // 控制同一系列的柱间距离
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'right', // 位置
|
||||||
|
color: '#A7D6F4', //淡蓝色
|
||||||
|
fontSize: 14,
|
||||||
|
distance: 14, // label与柱子距离
|
||||||
|
formatter: '{c}',
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: [0, 20, 20, 0], // 圆角(左上、右上、右下、左下)
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#f0f5ff' },
|
||||||
|
{ offset: 0.5, color: '#adc6ff' },
|
||||||
|
{ offset: 1, color: '#2f54eb' },
|
||||||
|
]), // 渐变
|
||||||
|
},
|
||||||
|
data: alarmTypeTypeTop.value,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 柱状图设置
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
//y轴
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
type: 'category',
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
inverse: true,
|
||||||
|
data: alarmTypeTypeTop.value.map((item: any) => item.name),
|
||||||
|
axisLabel: {
|
||||||
|
color: '#A7D6F4',
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
fnDesign(alarmTypeBar.value, optionData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fnDesign(container: HTMLElement | undefined, option: any) {
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
alarmTypeBarChart.value = markRaw(echarts.init(container, 'light'));
|
||||||
|
option && alarmTypeBarChart.value.setOption(option);
|
||||||
|
|
||||||
|
// 创建 ResizeObserver 实例
|
||||||
|
var observer = new ResizeObserver(entries => {
|
||||||
|
if (alarmTypeBarChart.value) {
|
||||||
|
alarmTypeBarChart.value.resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 监听元素大小变化
|
||||||
|
observer.observe(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initPicture();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="alarmTypeBar" class="chart-container"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.chart-container {
|
||||||
|
/* 设置图表容器大小和位置 */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref, nextTick, watch } from 'vue';
|
||||||
|
import * as echarts from 'echarts/core';
|
||||||
|
import { GridComponent, GridComponentOption } from 'echarts/components';
|
||||||
|
import {
|
||||||
|
BarChart,
|
||||||
|
BarSeriesOption,
|
||||||
|
PictorialBarChart,
|
||||||
|
PictorialBarSeriesOption,
|
||||||
|
} from 'echarts/charts';
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
|
import { graphNodeClickID, graphNodeState } from '../../hooks/useTopology';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
echarts.use([GridComponent, BarChart, PictorialBarChart, CanvasRenderer]);
|
||||||
|
|
||||||
|
type EChartsOption = echarts.ComposeOption<
|
||||||
|
GridComponentOption | BarSeriesOption | PictorialBarSeriesOption
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**图DOM节点实例对象 */
|
||||||
|
const neResourcesDom = ref<HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
|
/**图实例对象 */
|
||||||
|
const neResourcesChart = ref<any>(null);
|
||||||
|
|
||||||
|
// 类别
|
||||||
|
const category = ref<any>([
|
||||||
|
{
|
||||||
|
name: t('views.dashboard.overview.resources.sysDisk'),
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('views.dashboard.overview.resources.sysMem'),
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('views.dashboard.overview.resources.sysCpu'),
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('views.dashboard.overview.resources.neCpu'),
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 数据总数
|
||||||
|
const total = 100;
|
||||||
|
|
||||||
|
/**图数据 */
|
||||||
|
const optionData: EChartsOption = {
|
||||||
|
xAxis: {
|
||||||
|
max: total,
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: '1%', // 设置条形图的边距
|
||||||
|
bottom: '12%',
|
||||||
|
left: '25%',
|
||||||
|
right: '25%',
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
inverse: false,
|
||||||
|
data: category.value,
|
||||||
|
axisLine: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
// 内
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: 10,
|
||||||
|
legendHoverLink: false,
|
||||||
|
silent: true,
|
||||||
|
itemStyle: {
|
||||||
|
color: function (params) {
|
||||||
|
// 红色
|
||||||
|
if (params.value && +params.value >= 70) {
|
||||||
|
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#fff1f0' },
|
||||||
|
{ offset: 0.5, color: '#ffa39e' },
|
||||||
|
{ offset: 1, color: '#f5222d' },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
// 蓝色
|
||||||
|
if (params.value && +params.value >= 30) {
|
||||||
|
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#f0f5ff' },
|
||||||
|
{ offset: 0.5, color: '#adc6ff' },
|
||||||
|
{ offset: 1, color: '#2f54eb' },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
// 绿色
|
||||||
|
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||||
|
{ offset: 0, color: '#f6ffed' },
|
||||||
|
{ offset: 0.5, color: '#b7eb8f' },
|
||||||
|
{ offset: 1, color: '#52c41a' },
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
position: 'left',
|
||||||
|
formatter: '{b}: ',
|
||||||
|
fontSize: 15,
|
||||||
|
color: '#fff',
|
||||||
|
},
|
||||||
|
data: category.value,
|
||||||
|
z: 1,
|
||||||
|
animationEasing: 'elasticOut',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 分隔
|
||||||
|
type: 'pictorialBar',
|
||||||
|
itemStyle: {
|
||||||
|
color: '#0a3ca0',
|
||||||
|
},
|
||||||
|
symbolRepeat: 'fixed',
|
||||||
|
symbolMargin: 6,
|
||||||
|
symbol: 'rect',
|
||||||
|
symbolClip: true,
|
||||||
|
symbolSize: [1, 12],
|
||||||
|
symbolPosition: 'start',
|
||||||
|
symbolOffset: [0, -1],
|
||||||
|
symbolBoundingData: total,
|
||||||
|
data: category.value,
|
||||||
|
z: 2,
|
||||||
|
animationEasing: 'elasticOut',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// 外边框
|
||||||
|
type: 'pictorialBar',
|
||||||
|
symbol: 'rect',
|
||||||
|
symbolBoundingData: total,
|
||||||
|
itemStyle: {
|
||||||
|
color: 'transparent',
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
formatter: params => {
|
||||||
|
var text = `{a| ${params.value}%} `;
|
||||||
|
if (params.value && +params.value >= 70) {
|
||||||
|
text = `{c| ${params.value}%} `;
|
||||||
|
} else if (params.value && +params.value >= 30) {
|
||||||
|
text = `{b| ${params.value}%} `;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
rich: {
|
||||||
|
a: {
|
||||||
|
color: '#52c41a', // 绿
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
b: {
|
||||||
|
color: '#2f54eb', // 蓝
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
c: {
|
||||||
|
color: '#f5222d', // 红
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
f: {
|
||||||
|
color: '#ffffff', // 默认
|
||||||
|
fontSize: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
position: 'right',
|
||||||
|
distance: 0, // 向右偏移位置
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
|
data: category.value,
|
||||||
|
z: 0,
|
||||||
|
animationEasing: 'elasticOut',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '外框',
|
||||||
|
type: 'bar',
|
||||||
|
barGap: '-120%', // 设置外框粗细
|
||||||
|
data: [total, total, total],
|
||||||
|
barWidth: 14,
|
||||||
|
itemStyle: {
|
||||||
|
color: 'transparent', // 填充色
|
||||||
|
borderColor: '#0a3ca0', // 边框色
|
||||||
|
borderWidth: 1, // 边框宽度
|
||||||
|
borderRadius: 1, //圆角半径
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
// 标签显示位置
|
||||||
|
show: false,
|
||||||
|
position: 'top', // insideTop 或者横向的 insideLeft
|
||||||
|
},
|
||||||
|
z: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**图数据渲染 */
|
||||||
|
function handleRanderChart(
|
||||||
|
container: HTMLElement | undefined,
|
||||||
|
option: EChartsOption
|
||||||
|
) {
|
||||||
|
if (!container) return;
|
||||||
|
neResourcesChart.value = markRaw(echarts.init(container, 'light'));
|
||||||
|
option && neResourcesChart.value.setOption(option);
|
||||||
|
|
||||||
|
// 创建 ResizeObserver 实例
|
||||||
|
var observer = new ResizeObserver(entries => {
|
||||||
|
if (neResourcesChart.value) {
|
||||||
|
neResourcesChart.value.resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 监听元素大小变化
|
||||||
|
observer.observe(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fnChangeData(data: any[], itemID: string) {
|
||||||
|
let info = data.find((item: any) => item.id === itemID);
|
||||||
|
if (!info || !info.neState.online) return;
|
||||||
|
// if (!info.neState.online) {
|
||||||
|
// info = data.find((item: any) => item.id === itemID);
|
||||||
|
// graphNodeClickID.value = itemID;
|
||||||
|
// }
|
||||||
|
// console.log(info.id);
|
||||||
|
// console.log(info.neState.cpu.nfCpuUsage);
|
||||||
|
// console.log(info.neState.cpu.sysCpuUsage);
|
||||||
|
// console.log(info.neState.mem);
|
||||||
|
// console.log(info.neState.disk);
|
||||||
|
let sysCpuUsage = 0;
|
||||||
|
let nfCpuUsage = 0;
|
||||||
|
if (info.neState.cpu) {
|
||||||
|
nfCpuUsage = info.neState.cpu.nfCpuUsage;
|
||||||
|
const nfCpu = +(info.neState.cpu.nfCpuUsage / 100);
|
||||||
|
nfCpuUsage = +nfCpu.toFixed(2);
|
||||||
|
if (nfCpuUsage > 100) {
|
||||||
|
nfCpuUsage = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
sysCpuUsage = info.neState.cpu.sysCpuUsage;
|
||||||
|
let sysCpu = +(info.neState.cpu.sysCpuUsage / 100);
|
||||||
|
sysCpuUsage = +sysCpu.toFixed(2);
|
||||||
|
if (sysCpuUsage > 100) {
|
||||||
|
sysCpuUsage = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sysMemUsage = 0;
|
||||||
|
if (info.neState.mem) {
|
||||||
|
const men = info.neState.mem.sysMemUsage;
|
||||||
|
sysMemUsage = +(men / 100).toFixed(2);
|
||||||
|
if (sysMemUsage > 100) {
|
||||||
|
sysMemUsage = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sysDiskUsage = 0;
|
||||||
|
if (info.neState.disk && Array.isArray(info.neState.disk.partitionInfo)) {
|
||||||
|
let disks: any[] = info.neState.disk.partitionInfo;
|
||||||
|
disks = disks.sort((a, b) => +b.used - +a.used);
|
||||||
|
if (disks.length > 0) {
|
||||||
|
const { total, used } = disks[0];
|
||||||
|
sysDiskUsage = +((used / total) * 100).toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
category.value[0].value = sysDiskUsage;
|
||||||
|
category.value[1].value = sysMemUsage;
|
||||||
|
category.value[2].value = sysCpuUsage;
|
||||||
|
category.value[3].value = nfCpuUsage;
|
||||||
|
neResourcesChart.value.setOption({
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: category.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: category.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: category.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: category.value,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
graphNodeState,
|
||||||
|
v => {
|
||||||
|
fnChangeData(v, graphNodeClickID.value);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(graphNodeClickID, v => {
|
||||||
|
fnChangeData(graphNodeState.value, v);
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// setInterval(function () {
|
||||||
|
// var ndata = [
|
||||||
|
// {
|
||||||
|
// name: '系统内存',
|
||||||
|
// value: Math.round(Math.random() * 100),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: '系统CPU',
|
||||||
|
// value: Math.round(Math.random() * 100),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: '网元CPU',
|
||||||
|
// value: Math.round(Math.random() * 100),
|
||||||
|
// },
|
||||||
|
// ];
|
||||||
|
// neResourcesChart.value.setOption({
|
||||||
|
// series: [
|
||||||
|
// {
|
||||||
|
// data: ndata,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// data: ndata,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// data: ndata,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
// }, 2000);
|
||||||
|
nextTick(() => {
|
||||||
|
handleRanderChart(neResourcesDom.value, optionData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="neResourcesDom" class="chart"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||||
|
import { message } from 'ant-design-vue/es';
|
||||||
|
import { getGraphData } from '@/api/monitor/topology';
|
||||||
|
import { Graph, GraphData, Tooltip } from '@antv/g6';
|
||||||
|
import { parseBasePath } from '@/plugins/file-static-url';
|
||||||
|
import { edgeLineAnimateState } from '@/views/monitor/topologyBuild/hooks/registerEdge';
|
||||||
|
import { nodeImageAnimateState } from '@/views/monitor/topologyBuild/hooks/registerNode';
|
||||||
|
import {
|
||||||
|
graphG6,
|
||||||
|
graphState,
|
||||||
|
graphNodeClickID,
|
||||||
|
notNeNodes,
|
||||||
|
} from '../../hooks/useTopology';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
/**图DOM节点实例对象 */
|
||||||
|
const graphG6Dom = ref<HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
|
/**图节点展示 */
|
||||||
|
const graphNodeTooltip = new Tooltip({
|
||||||
|
offsetX: 20,
|
||||||
|
offsetY: 20,
|
||||||
|
getContent(evt) {
|
||||||
|
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
|
||||||
|
const { id, label, neState }: any = evt.item?.getModel();
|
||||||
|
if (notNeNodes.includes(id)) {
|
||||||
|
return `<div><span>${label || id}</span></div>`;
|
||||||
|
}
|
||||||
|
if (!neState) {
|
||||||
|
return `<div><span>${label || id}</span></div>`;
|
||||||
|
}
|
||||||
|
return `
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 200px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||||
|
${
|
||||||
|
neState.online
|
||||||
|
? t('views.monitor.topology.normalcy')
|
||||||
|
: t('views.monitor.topology.exceptions')
|
||||||
|
}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.refreshTime')}:</strong><span>
|
||||||
|
${neState.refreshTime ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
<div>========================</div>
|
||||||
|
<div><strong>ID:</strong><span>${neState.neId}</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.name')}:</strong><span>
|
||||||
|
${neState.neName ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>IP:</strong><span>${neState.neIP}</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
|
||||||
|
${neState.version ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.serialNum')}:</strong><span>
|
||||||
|
${neState.sn ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
|
||||||
|
${neState.expire ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
itemTypes: ['node'],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**图绑定事件 */
|
||||||
|
function fnGraphEvent(graph: Graph) {
|
||||||
|
// 节点点击
|
||||||
|
graph.on('node:click', evt => {
|
||||||
|
// 获得鼠标当前目标节点
|
||||||
|
const node = evt.item?.getModel();
|
||||||
|
if (node && node.id && !notNeNodes.includes(node.id)) {
|
||||||
|
graphNodeClickID.value = node.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**图数据渲染 */
|
||||||
|
function handleRanderGraph(
|
||||||
|
container: HTMLElement | undefined,
|
||||||
|
data: GraphData
|
||||||
|
) {
|
||||||
|
if (!container) return;
|
||||||
|
const { clientHeight, clientWidth } = container;
|
||||||
|
|
||||||
|
edgeLineAnimateState();
|
||||||
|
nodeImageAnimateState();
|
||||||
|
|
||||||
|
const graph = new Graph({
|
||||||
|
container: container,
|
||||||
|
width: clientWidth,
|
||||||
|
height: clientHeight - 36,
|
||||||
|
fitCenter: true,
|
||||||
|
fitView: true,
|
||||||
|
fitViewPadding: [20],
|
||||||
|
autoPaint: true,
|
||||||
|
modes: {
|
||||||
|
default: ['drag-canvas', 'zoom-canvas'],
|
||||||
|
},
|
||||||
|
groupByTypes: false,
|
||||||
|
nodeStateStyles: {
|
||||||
|
selected: {
|
||||||
|
fill: 'transparent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [graphNodeTooltip],
|
||||||
|
animate: true, // 是否使用动画过度,默认为 false
|
||||||
|
animateCfg: {
|
||||||
|
duration: 500, // Number,一次动画的时长
|
||||||
|
easing: 'linearEasing', // String,动画函数
|
||||||
|
},
|
||||||
|
});
|
||||||
|
graph.data(data);
|
||||||
|
graph.render();
|
||||||
|
|
||||||
|
fnGraphEvent(graph);
|
||||||
|
|
||||||
|
graphG6.value = graph;
|
||||||
|
|
||||||
|
// 创建 ResizeObserver 实例
|
||||||
|
var observer = new ResizeObserver(function (entries) {
|
||||||
|
// 当元素大小发生变化时触发回调函数
|
||||||
|
entries.forEach(function (entry) {
|
||||||
|
if (!graphG6.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
graphG6.value.changeSize(
|
||||||
|
entry.contentRect.width,
|
||||||
|
entry.contentRect.height - 30
|
||||||
|
);
|
||||||
|
graphG6.value.fitCenter();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// 监听元素大小变化
|
||||||
|
observer.observe(container);
|
||||||
|
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取图组数据渲染到画布
|
||||||
|
* @param reload 是否重载数据
|
||||||
|
*/
|
||||||
|
function fnGraphDataLoad(reload: boolean = false) {
|
||||||
|
Promise.all([
|
||||||
|
getGraphData(graphState.group),
|
||||||
|
listAllNeInfo({
|
||||||
|
bandStatus: false,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
.then(resArr => {
|
||||||
|
const graphRes = resArr[0];
|
||||||
|
const neRes = resArr[1];
|
||||||
|
if (
|
||||||
|
graphRes.code === RESULT_CODE_SUCCESS &&
|
||||||
|
Array.isArray(graphRes.data.nodes) &&
|
||||||
|
graphRes.data.nodes.length > 0 &&
|
||||||
|
neRes.code === RESULT_CODE_SUCCESS &&
|
||||||
|
Array.isArray(neRes.data) &&
|
||||||
|
neRes.data.length > 0
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
graphData: graphRes.data,
|
||||||
|
neList: neRes.data,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
message.warning({
|
||||||
|
content: t('views.monitor.topology.noData'),
|
||||||
|
duration: 5,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (!res) return;
|
||||||
|
const { combos, edges, nodes } = res.graphData;
|
||||||
|
|
||||||
|
// 节点过滤
|
||||||
|
const nf: Record<string, any>[] = nodes.filter(
|
||||||
|
(node: Record<string, any>) => {
|
||||||
|
Reflect.set(node, 'neState', { online: false });
|
||||||
|
// 图片路径处理
|
||||||
|
if (node.img) node.img = parseBasePath(node.img);
|
||||||
|
if (node.icon.show && node.icon?.img) {
|
||||||
|
node.icon.img = parseBasePath(node.icon.img);
|
||||||
|
}
|
||||||
|
// 遍历是否有网元数据
|
||||||
|
const nodeID: string = node.id;
|
||||||
|
const hasNe = res.neList.some(ne => {
|
||||||
|
Reflect.set(node, 'neInfo', ne.neType === nodeID ? ne : {});
|
||||||
|
return ne.neType === nodeID;
|
||||||
|
});
|
||||||
|
if (hasNe) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (notNeNodes.includes(nodeID)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 边过滤
|
||||||
|
const ef: Record<string, any>[] = edges.filter(
|
||||||
|
(edge: Record<string, any>) => {
|
||||||
|
const edgeSource: string = edge.source;
|
||||||
|
const edgeTarget: string = edge.target;
|
||||||
|
const hasNeS = nf.some(n => n.id === edgeSource);
|
||||||
|
const hasNeT = nf.some(n => n.id === edgeTarget);
|
||||||
|
// console.log(hasNeS, edgeSource, hasNeT, edgeTarget);
|
||||||
|
if (hasNeS && hasNeT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (hasNeS && notNeNodes.includes(edgeTarget)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (hasNeT && notNeNodes.includes(edgeSource)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 分组过滤
|
||||||
|
combos.forEach((combo: Record<string, any>) => {
|
||||||
|
const comboChildren: Record<string, any>[] = combo.children;
|
||||||
|
combo.children = comboChildren.filter(c => nf.some(n => n.id === c.id));
|
||||||
|
return combo;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图数据
|
||||||
|
graphState.data = { combos, edges: ef, nodes: nf };
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (graphState.data.length < 0) return;
|
||||||
|
// 重载数据
|
||||||
|
if (reload) {
|
||||||
|
graphG6.value.read(graphState.data);
|
||||||
|
} else {
|
||||||
|
handleRanderGraph(graphG6Dom.value, graphState.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fnGraphDataLoad(false);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="graphG6Dom" class="chart"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
import { listKPIData } from '@/api/perfManage/goldTarget';
|
||||||
|
import * as echarts from 'echarts/core';
|
||||||
|
import {
|
||||||
|
TooltipComponent,
|
||||||
|
TooltipComponentOption,
|
||||||
|
GridComponent,
|
||||||
|
GridComponentOption,
|
||||||
|
LegendComponent,
|
||||||
|
LegendComponentOption,
|
||||||
|
} from 'echarts/components';
|
||||||
|
import { LineChart, LineSeriesOption } from 'echarts/charts';
|
||||||
|
import { UniversalTransition } from 'echarts/features';
|
||||||
|
import { CanvasRenderer } from 'echarts/renderers';
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { upfFlowData, upfFlowParse } from '../../hooks/useUPFTotalFlow';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
echarts.use([
|
||||||
|
TooltipComponent,
|
||||||
|
GridComponent,
|
||||||
|
LegendComponent,
|
||||||
|
LineChart,
|
||||||
|
CanvasRenderer,
|
||||||
|
UniversalTransition,
|
||||||
|
]);
|
||||||
|
|
||||||
|
type EChartsOption = echarts.ComposeOption<
|
||||||
|
| TooltipComponentOption
|
||||||
|
| GridComponentOption
|
||||||
|
| LegendComponentOption
|
||||||
|
| LineSeriesOption
|
||||||
|
>;
|
||||||
|
|
||||||
|
/**图DOM节点实例对象 */
|
||||||
|
const upfFlow = ref<HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
|
/**图实例对象 */
|
||||||
|
const upfFlowChart = ref<any>(null);
|
||||||
|
|
||||||
|
function fnDesign(container: HTMLElement | undefined, option: EChartsOption) {
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!upfFlowChart.value) {
|
||||||
|
upfFlowChart.value = markRaw(echarts.init(container, 'light'));
|
||||||
|
}
|
||||||
|
|
||||||
|
option && upfFlowChart.value.setOption(option);
|
||||||
|
|
||||||
|
// 创建 ResizeObserver 实例
|
||||||
|
var observer = new ResizeObserver(entries => {
|
||||||
|
if (upfFlowChart.value) {
|
||||||
|
upfFlowChart.value.resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 监听元素大小变化
|
||||||
|
observer.observe(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
//渲染速率图
|
||||||
|
function handleRanderChart() {
|
||||||
|
const { lineXTime, lineYUp, lineYDown } = upfFlowData.value;
|
||||||
|
|
||||||
|
var yAxisSeries: any = [
|
||||||
|
{
|
||||||
|
name: t('views.dashboard.overview.upfFlow.up'),
|
||||||
|
type: 'line',
|
||||||
|
color: 'rgba(250, 219, 20)',
|
||||||
|
smooth: true,
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(250, 219, 20, .5)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(250, 219, 20, 0.5)',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
shadowBlur: 10,
|
||||||
|
},
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 5,
|
||||||
|
formatter: '{b}',
|
||||||
|
data: lineYUp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('views.dashboard.overview.upfFlow.down'),
|
||||||
|
type: 'line',
|
||||||
|
color: 'rgba(92, 123, 217)',
|
||||||
|
smooth: true,
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(92, 123, 217, .5)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(92, 123, 217, 0.5)',
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
shadowBlur: 10,
|
||||||
|
},
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 5,
|
||||||
|
formatter: '{b}',
|
||||||
|
data: lineYDown,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const optionData: EChartsOption = {
|
||||||
|
tooltip: {
|
||||||
|
show: true, //是否显示提示框组件
|
||||||
|
trigger: 'axis',
|
||||||
|
//formatter:'{a0}:{c0}<br>{a1}:{c1}'
|
||||||
|
formatter: function (param: any) {
|
||||||
|
var tip = '';
|
||||||
|
if (param !== null && param.length > 0) {
|
||||||
|
tip += param[0].name + '<br />';
|
||||||
|
for (var i = 0; i < param.length; i++) {
|
||||||
|
tip +=
|
||||||
|
param[i].marker +
|
||||||
|
param[i].seriesName +
|
||||||
|
': ' +
|
||||||
|
param[i].value +
|
||||||
|
'<br />';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tip;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
data: yAxisSeries.map((s: any) => s.name),
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: 'rgb(0,253,255,0.6)',
|
||||||
|
},
|
||||||
|
left: 'center', // 设置图例居中
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
top: '14%',
|
||||||
|
left: '4%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '12%',
|
||||||
|
containLabel: true,
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: lineXTime,
|
||||||
|
axisLabel: {
|
||||||
|
formatter: function (params: any) {
|
||||||
|
return params.split(' ')[1];
|
||||||
|
},
|
||||||
|
fontSize: 14,
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgb(0,253,255,0.6)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
name: '(Mbps)',
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 12, // 设置文字距离x轴的距离
|
||||||
|
padding: [0, -10, 0, 0], // 设置名称在x轴方向上的偏移
|
||||||
|
},
|
||||||
|
type: 'value',
|
||||||
|
// splitNumber: 4,
|
||||||
|
min: 0,
|
||||||
|
//max: 300,
|
||||||
|
axisLabel: {
|
||||||
|
formatter: '{value}',
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgb(23,255,243,0.3)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: 'rgb(0,253,255,0.6)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
series: yAxisSeries,
|
||||||
|
};
|
||||||
|
fnDesign(upfFlow.value, optionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**查询初始UPF数据 */
|
||||||
|
function fnGetInitData() {
|
||||||
|
// 查询5分钟前的
|
||||||
|
const nowDate = new Date().getTime();
|
||||||
|
|
||||||
|
listKPIData({
|
||||||
|
neType: 'UPF',
|
||||||
|
neId: '001',
|
||||||
|
startTime: nowDate - 5 * 60 * 1000,
|
||||||
|
endTime: nowDate,
|
||||||
|
interval: 5, // 5秒
|
||||||
|
sortField: 'timeGroup',
|
||||||
|
sortOrder: 'asc',
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||||
|
for (const item of res.data) {
|
||||||
|
upfFlowParse(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
handleRanderChart();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => upfFlowData.value,
|
||||||
|
v => {
|
||||||
|
if (upfFlowChart.value == null) return;
|
||||||
|
upfFlowChart.value.setOption({
|
||||||
|
xAxis: {
|
||||||
|
data: v.lineXTime,
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: v.lineYUp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: v.lineYDown,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fnGetInitData();
|
||||||
|
|
||||||
|
// setInterval(() => {
|
||||||
|
// upfFlowData.value.lineXTime.push(parseDateToStr(new Date()));
|
||||||
|
// const upN3 = parseSizeFromKbs(+145452, 5);
|
||||||
|
// upfFlowData.value.lineYUp.push(upN3[0]);
|
||||||
|
// const downN6 = parseSizeFromKbs(+232343, 5);
|
||||||
|
// upfFlowData.value.lineYDown.push(downN6[0]);
|
||||||
|
|
||||||
|
// upfFlowChart.value.setOption({
|
||||||
|
// xAxis: {
|
||||||
|
// data: upfFlowData.value.lineXTime,
|
||||||
|
// },
|
||||||
|
// series: [
|
||||||
|
// {
|
||||||
|
// data: upfFlowData.value.lineYUp,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// data: upfFlowData.value.lineYDown,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
// }, 5000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="upfFlow" class="chart-container"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.chart-container {
|
||||||
|
/* 设置图表容器大小和位置 */
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { parseDuration, parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import { eventData, eventId } from '../../hooks/useUserActivity';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
import { onMounted, reactive } from 'vue';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { getDict } = useDictStore();
|
||||||
|
|
||||||
|
/**字典数据 */
|
||||||
|
let dict: {
|
||||||
|
/**CDR SIP响应代码类别类型 */
|
||||||
|
cdrSipCode: DictType[];
|
||||||
|
/**CDR 呼叫类型 */
|
||||||
|
cdrCallType: DictType[];
|
||||||
|
/**UE 事件认证代码类型 */
|
||||||
|
ueAauthCode: DictType[];
|
||||||
|
/**UE 事件类型 */
|
||||||
|
ueEventType: DictType[];
|
||||||
|
/**UE 事件CM状态 */
|
||||||
|
ueEventCmState: DictType[];
|
||||||
|
} = reactive({
|
||||||
|
cdrSipCode: [],
|
||||||
|
cdrCallType: [],
|
||||||
|
ueAauthCode: [],
|
||||||
|
ueEventType: [],
|
||||||
|
ueEventCmState: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始字典数据
|
||||||
|
Promise.allSettled([
|
||||||
|
getDict('cdr_sip_code'),
|
||||||
|
getDict('cdr_call_type'),
|
||||||
|
getDict('ue_auth_code'),
|
||||||
|
getDict('ue_event_type'),
|
||||||
|
getDict('ue_event_cm_state'),
|
||||||
|
]).then(resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
dict.cdrSipCode = resArr[0].value;
|
||||||
|
}
|
||||||
|
if (resArr[1].status === 'fulfilled') {
|
||||||
|
dict.cdrCallType = resArr[1].value;
|
||||||
|
}
|
||||||
|
if (resArr[2].status === 'fulfilled') {
|
||||||
|
dict.ueAauthCode = resArr[2].value;
|
||||||
|
}
|
||||||
|
if (resArr[3].status === 'fulfilled') {
|
||||||
|
dict.ueEventType = resArr[3].value;
|
||||||
|
}
|
||||||
|
if (resArr[4].status === 'fulfilled') {
|
||||||
|
dict.ueEventCmState = resArr[4].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="activty">
|
||||||
|
<template v-for="item in eventData" :key="item.eId">
|
||||||
|
<!-- CDR事件IMS -->
|
||||||
|
<div
|
||||||
|
class="card-cdr"
|
||||||
|
:class="{ active: item.eId === eventId }"
|
||||||
|
v-if="item.eType === 'ims_cdr'"
|
||||||
|
>
|
||||||
|
<div class="card-cdr-item">
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||||
|
<span>
|
||||||
|
<DictTag
|
||||||
|
:options="dict.cdrCallType"
|
||||||
|
:value="item.data.callType"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||||
|
<span :title="parseDateToStr(item.data.releaseTime * 1000)">
|
||||||
|
{{ parseDateToStr(item.data.releaseTime * 1000) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-cdr-item">
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.caller') }}:
|
||||||
|
<span :title="item.data.callerParty">
|
||||||
|
{{ item.data.callerParty }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.called') }}:
|
||||||
|
<span :title="item.data.calledParty">
|
||||||
|
{{ item.data.calledParty }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.data.callType !== 'sms'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.duration') }}:
|
||||||
|
<span>{{ parseDuration(item.data.callDuration) }}</span>
|
||||||
|
</div>
|
||||||
|
<div v-else></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span v-if="item.data.callType !== 'sms'">
|
||||||
|
<DictTag
|
||||||
|
:options="dict.cdrSipCode"
|
||||||
|
:value="item.data.cause"
|
||||||
|
value-default="0"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.resultOK') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- UE事件AMF -->
|
||||||
|
<div
|
||||||
|
class="card-ue"
|
||||||
|
:class="{ active: item.eId === eventId }"
|
||||||
|
v-if="item.eType === 'amf_ue'"
|
||||||
|
>
|
||||||
|
<div class="card-ue-item">
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||||
|
<span>
|
||||||
|
<DictTag :options="dict.ueEventType" :value="item.type" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||||
|
<span
|
||||||
|
v-if="item.type === 'auth-result'"
|
||||||
|
:title="item.data.authTime"
|
||||||
|
>
|
||||||
|
{{ item.data.authTime }}
|
||||||
|
</span>
|
||||||
|
<span v-if="item.type === 'detach'" :title="item.data.detachTime">
|
||||||
|
{{ item.data.detachTime }}
|
||||||
|
</span>
|
||||||
|
<span v-if="item.type === 'cm-state'" :title="item.data.changeTime">
|
||||||
|
{{ item.data.changeTime }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||||
|
<div>
|
||||||
|
GNB ID: <span>{{ item.data.gNBID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Cell ID: <span>{{ item.data.cellID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="item.type === 'auth-result'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span>
|
||||||
|
<DictTag :options="dict.ueAauthCode" :value="item.data.authCode" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.type === 'detach'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span>{{ t('views.dashboard.overview.userActivity.resultOK') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-ue-w33" v-if="item.type === 'cm-state'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span>
|
||||||
|
<DictTag :options="dict.ueEventCmState" :value="item.data.status" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- UE事件MME -->
|
||||||
|
<div
|
||||||
|
class="card-ue"
|
||||||
|
:class="{ active: item.eId === eventId }"
|
||||||
|
v-if="item.eType === 'mme_ue'"
|
||||||
|
>
|
||||||
|
<div class="card-ue-item">
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||||
|
<span v-if="item.type === 'cm-state'">
|
||||||
|
{{
|
||||||
|
dict.ueEventType
|
||||||
|
.find(s => s.value === item.type)
|
||||||
|
?.label.replace('CM', 'ECM')
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
<DictTag :options="dict.ueEventType" :value="item.type" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||||
|
<span :title="item.data.timestamp">
|
||||||
|
{{ parseDateToStr(+item.data.timestamp * 1000) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||||
|
<div>
|
||||||
|
ENB ID: <span>{{ item.data.eNBID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Cell ID: <span>{{ item.data.cellID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.type === 'auth-result'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span>
|
||||||
|
<DictTag :options="dict.ueAauthCode" :value="item.data.result" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="item.type === 'detach'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span>{{ t('views.dashboard.overview.userActivity.resultOK') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-ue-w33" v-if="item.type === 'cm-state'">
|
||||||
|
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||||
|
<span>
|
||||||
|
<DictTag :options="dict.ueEventCmState" :value="item.data.result" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.activty {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
height: 94%;
|
||||||
|
color: #61a8ff;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
& .card-ue {
|
||||||
|
border: 1px #61a8ff solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
& span {
|
||||||
|
color: #68d8fe;
|
||||||
|
}
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
& > div {
|
||||||
|
width: 50%;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: start;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-w33 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
& > div {
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .card-cdr {
|
||||||
|
border: 1px #61a8ff solid;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
& span {
|
||||||
|
color: #68d8fe;
|
||||||
|
}
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
& > div {
|
||||||
|
flex: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: start;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .active {
|
||||||
|
color: #faad14;
|
||||||
|
border: 1px #faad14 solid;
|
||||||
|
animation: backInRight 0.3s alternate;
|
||||||
|
& span {
|
||||||
|
color: #faad14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 兼容当行显示字内容 */
|
||||||
|
@media (max-width: 1720px) {
|
||||||
|
& .card-cdr {
|
||||||
|
&-item {
|
||||||
|
display: block;
|
||||||
|
& > div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& .card-ue {
|
||||||
|
&-item {
|
||||||
|
display: block;
|
||||||
|
& > div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修改滚动条的样式 */
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 8px; /* 设置滚动条宽度 */
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background-color: #101129; /* 设置滚动条轨道背景颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: #28293f; /* 设置滚动条滑块颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: #68d8fe; /* 设置鼠标悬停时滚动条滑块颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes backInRight {
|
||||||
|
0% {
|
||||||
|
opacity: 0.7;
|
||||||
|
-webkit-transform: translateX(2000px) scale(0.7);
|
||||||
|
transform: translateX(2000px) scale(0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
80% {
|
||||||
|
opacity: 0.7;
|
||||||
|
-webkit-transform: translateX(0) scale(0.7);
|
||||||
|
transform: translateX(0) scale(0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: scale(1);
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
277
practical_training/views/dashboard/overview/css/index.css
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
.viewport {
|
||||||
|
/* 限定大小 */
|
||||||
|
min-width: 1024px;
|
||||||
|
max-width: 1920px;
|
||||||
|
min-height: 780px;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
padding: 5rem 0.833rem 0;
|
||||||
|
line-height: 1.15;
|
||||||
|
background-color: #101129;
|
||||||
|
height: 100vh;
|
||||||
|
margin-bottom: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
flex: 3;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 边框 */
|
||||||
|
.panel {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2px solid red;
|
||||||
|
border-image: url(../images/border.png) 51 38 21 132;
|
||||||
|
border-width: 2.125rem 1.583rem 0.875rem 5.5rem;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 0.833rem;
|
||||||
|
}
|
||||||
|
.panel .inner {
|
||||||
|
/* 装内容 */
|
||||||
|
/* height: 60px; */
|
||||||
|
position: absolute;
|
||||||
|
top: -2.125rem;
|
||||||
|
right: -1.583rem;
|
||||||
|
bottom: -0.875rem;
|
||||||
|
left: -5.5rem;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
}
|
||||||
|
.panel h3 {
|
||||||
|
font-size: 0.833rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 总览标题 */
|
||||||
|
.brand {
|
||||||
|
background-image: url(../images/brand.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0.833rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.brand .brand-title {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding-top: 1rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.brand .brand-desc {
|
||||||
|
color: #d9d9d9;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 实时流量 */
|
||||||
|
.upfFlow {
|
||||||
|
/* min-height: 16rem; */
|
||||||
|
height: 40%;
|
||||||
|
}
|
||||||
|
.upfFlow .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 网络拓扑 */
|
||||||
|
.topology {
|
||||||
|
/* min-height: 27.8rem; */
|
||||||
|
height: 56.4%;
|
||||||
|
}
|
||||||
|
.topology .inner h3 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
.topology .inner h3 .normal {
|
||||||
|
color: #52c41a;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.topology .inner h3 .abnormal {
|
||||||
|
color: #f5222d;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
.topology .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 概览区域 */
|
||||||
|
.skim {
|
||||||
|
/* min-height: 7.78rem; */
|
||||||
|
height: 14.4%;
|
||||||
|
}
|
||||||
|
.skim .inner .data {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
height: 90%;
|
||||||
|
}
|
||||||
|
.skim .inner .data .item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: baseline;
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
.skim .inner .data .item div {
|
||||||
|
font-size: 1.467rem;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
|
.skim .inner .data .item span {
|
||||||
|
color: #4c9bfd;
|
||||||
|
font-size: 0.833rem;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
line-height: 2rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: start;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.skim .inner .data .item span::before {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 0;
|
||||||
|
background-image: linear-gradient(to right, #fff, #fff0);
|
||||||
|
height: 1px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 概览区域 衍生基站信息 */
|
||||||
|
.skim.base {
|
||||||
|
height: 12%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skim.base .inner .data {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
height: 75%;
|
||||||
|
}
|
||||||
|
.skim.base .inner .data .item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: baseline;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 用户行为 */
|
||||||
|
.userActivity {
|
||||||
|
/* min-height: 35.8rem; */
|
||||||
|
height: 54.6%;
|
||||||
|
}
|
||||||
|
.userActivity .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 流量统计 */
|
||||||
|
.upfFlowTotal {
|
||||||
|
/* min-height: 7.5rem; */
|
||||||
|
height: 14.4%;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner h3 {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner h3 .filter {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner h3 .filter span {
|
||||||
|
display: block;
|
||||||
|
height: 0.75rem;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0 0.75rem;
|
||||||
|
color: #1950c4;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border-right: 0.083rem solid #00f2f1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner h3 .filter span:first-child {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner h3 .filter span:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner h3 .filter span.active {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.833rem;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner .chart .data {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
height: 60%;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner .chart .data .item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner .chart .data .item h4 {
|
||||||
|
font-size: 1.467rem;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.upfFlowTotal .inner .chart .data .item span {
|
||||||
|
color: #4c9bfd;
|
||||||
|
font-size: 0.867rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 资源情况 */
|
||||||
|
.resources {
|
||||||
|
/* min-height: 18rem; */
|
||||||
|
height: 34.4%;
|
||||||
|
}
|
||||||
|
.resources .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 告警统计 */
|
||||||
|
.alarmType {
|
||||||
|
/* min-height: 25rem; */
|
||||||
|
height: 46%;
|
||||||
|
}
|
||||||
|
.alarmType .inner .chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 跳转鼠标悬浮 */
|
||||||
|
.toRouter:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
.toRouter:hover > *,
|
||||||
|
.toRouter:hover > * > * {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
172
practical_training/views/dashboard/overview/hooks/useTopology.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import { computed, reactive, ref } from 'vue';
|
||||||
|
|
||||||
|
/**非网元元素 */
|
||||||
|
export const notNeNodes = [
|
||||||
|
'5GC',
|
||||||
|
'DN',
|
||||||
|
'UE',
|
||||||
|
'Base',
|
||||||
|
'lan',
|
||||||
|
'lan1',
|
||||||
|
'lan2',
|
||||||
|
'lan3',
|
||||||
|
'lan4',
|
||||||
|
'lan5',
|
||||||
|
'lan6',
|
||||||
|
'lan7',
|
||||||
|
'LAN',
|
||||||
|
'NR',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**图状态 */
|
||||||
|
export const graphState = reactive<Record<string, any>>({
|
||||||
|
/**当前图组名 */
|
||||||
|
group: '5GC System Architecture',
|
||||||
|
/**图数据 */
|
||||||
|
data: {
|
||||||
|
combos: [],
|
||||||
|
edges: [],
|
||||||
|
nodes: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**图实例对象 */
|
||||||
|
export const graphG6 = ref<any>(null);
|
||||||
|
|
||||||
|
/**图点击选择 */
|
||||||
|
export const graphNodeClickID = ref<string>('UPF');
|
||||||
|
|
||||||
|
/**图节点网元信息状态 */
|
||||||
|
export const graphNodeState = computed(() =>
|
||||||
|
graphState.data.nodes.map((item: any) => ({
|
||||||
|
id: item.id,
|
||||||
|
label: item.label,
|
||||||
|
neInfo: item.neInfo,
|
||||||
|
neState: item.neState,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
/**图节点网元状态数量 */
|
||||||
|
export const graphNodeStateNum = computed(() => {
|
||||||
|
let normal = 0;
|
||||||
|
let abnormal = 0;
|
||||||
|
for (const item of graphState.data.nodes) {
|
||||||
|
const neId = item.neState.neId;
|
||||||
|
if (neId) {
|
||||||
|
if (item.neState.online) {
|
||||||
|
normal += 1;
|
||||||
|
} else {
|
||||||
|
abnormal += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [normal, abnormal];
|
||||||
|
});
|
||||||
|
|
||||||
|
/**网元状态请求标记 */
|
||||||
|
export const neStateRequestMap = ref<Map<string, boolean>>(new Map());
|
||||||
|
|
||||||
|
/**neStateParse 网元状态 数据解析 */
|
||||||
|
export function neStateParse(neType: string, data: Record<string, any>) {
|
||||||
|
const { combos, edges, nodes } = graphState.data;
|
||||||
|
const node = nodes.find((item: Record<string, any>) => item.id === neType);
|
||||||
|
|
||||||
|
// 更新网元状态
|
||||||
|
const newNeState = Object.assign(node.neState, data, {
|
||||||
|
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'),
|
||||||
|
online: !!data.cpu,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通过 ID 查询节点实例
|
||||||
|
const item = graphG6.value.findById(node.id);
|
||||||
|
if (item) {
|
||||||
|
const stateColor = newNeState.online ? '#52c41a' : '#f5222d'; // 状态颜色
|
||||||
|
// 图片类型不能填充
|
||||||
|
if (node.type.startsWith('image')) {
|
||||||
|
// 更新节点
|
||||||
|
if (node.label !== newNeState.neName) {
|
||||||
|
graphG6.value.updateItem(item, {
|
||||||
|
label: newNeState.neName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 设置状态
|
||||||
|
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
|
||||||
|
} else {
|
||||||
|
// 更新节点
|
||||||
|
graphG6.value.updateItem(item, {
|
||||||
|
label: newNeState.neName,
|
||||||
|
// neState: newNeState,
|
||||||
|
style: {
|
||||||
|
fill: stateColor, // 填充色
|
||||||
|
stroke: stateColor, // 填充色
|
||||||
|
},
|
||||||
|
// labelCfg: {
|
||||||
|
// style: {
|
||||||
|
// fill: '#ffffff', // 标签文本色
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
});
|
||||||
|
// 设置状态
|
||||||
|
graphG6.value.setItemState(item, 'stroke', newNeState.online);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置边状态
|
||||||
|
for (const edge of edges) {
|
||||||
|
const edgeSource: string = edge.source;
|
||||||
|
const edgeTarget: string = edge.target;
|
||||||
|
const neS = nodes.find((n: any) => n.id === edgeSource);
|
||||||
|
const neT = nodes.find((n: any) => n.id === edgeTarget);
|
||||||
|
// console.log(neS, edgeSource, neT, edgeTarget);
|
||||||
|
|
||||||
|
if (neS && neT) {
|
||||||
|
// 通过 ID 查询节点实例
|
||||||
|
// const item = graphG6.value.findById(edge.id);
|
||||||
|
// console.log(
|
||||||
|
// `${edgeSource} - ${edgeTarget}`,
|
||||||
|
// neS.neState.online && neT.neState.online
|
||||||
|
// );
|
||||||
|
// const stateColor = neS.neState.online && neT.neState.online ? '#000000' : '#ff4d4f'; // 状态颜色
|
||||||
|
// 更新边
|
||||||
|
// graphG6.value.updateItem(item, {
|
||||||
|
// label: `${edgeSource} - ${edgeTarget}`,
|
||||||
|
// style: {
|
||||||
|
// stroke: stateColor, // 填充色
|
||||||
|
// },
|
||||||
|
// labelCfg: {
|
||||||
|
// style: {
|
||||||
|
// fill: '#ffffff', // 标签文本色
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// 设置状态
|
||||||
|
graphG6.value.setItemState(
|
||||||
|
edge.id,
|
||||||
|
'circle-move',
|
||||||
|
neS.neState.online && neT.neState.online
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (neS && notNeNodes.includes(edgeTarget)) {
|
||||||
|
graphG6.value.setItemState(edge.id, 'line-dash', neS.neState.online);
|
||||||
|
}
|
||||||
|
if (neT && notNeNodes.includes(edgeSource)) {
|
||||||
|
graphG6.value.setItemState(edge.id, 'line-dash', neT.neState.online);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求标记复位
|
||||||
|
neStateRequestMap.value.set(neType, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**属性复位 */
|
||||||
|
export function topologyReset() {
|
||||||
|
graphState.data = {
|
||||||
|
combos: [],
|
||||||
|
edges: [],
|
||||||
|
nodes: [],
|
||||||
|
};
|
||||||
|
graphG6.value = null;
|
||||||
|
graphNodeClickID.value = 'UPF';
|
||||||
|
neStateRequestMap.value = new Map();
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import { parseSizeFromBits, parseSizeFromKbs } from '@/utils/parse-utils';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
type FDType = {
|
||||||
|
/**时间 */
|
||||||
|
lineXTime: string[];
|
||||||
|
/**上行 N3 */
|
||||||
|
lineYUp: number[];
|
||||||
|
/**下行 N6 */
|
||||||
|
lineYDown: number[];
|
||||||
|
/**容量 */
|
||||||
|
cap: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**UPF-流量数据 */
|
||||||
|
export const upfFlowData = ref<FDType>({
|
||||||
|
lineXTime: [],
|
||||||
|
lineYUp: [],
|
||||||
|
lineYDown: [],
|
||||||
|
cap: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**UPF-流量数据 数据解析 */
|
||||||
|
export function upfFlowParse(data: Record<string, string>) {
|
||||||
|
upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup']));
|
||||||
|
const upN3 = parseSizeFromKbs(+data['UPF.03'], 5);
|
||||||
|
upfFlowData.value.lineYUp.push(upN3[0]);
|
||||||
|
const downN6 = parseSizeFromKbs(+data['UPF.06'], 5);
|
||||||
|
upfFlowData.value.lineYDown.push(downN6[0]);
|
||||||
|
upfFlowData.value.cap += 1;
|
||||||
|
// 超过 25 弹出
|
||||||
|
if (upfFlowData.value.cap > 25) {
|
||||||
|
upfFlowData.value.lineXTime.shift();
|
||||||
|
upfFlowData.value.lineYUp.shift();
|
||||||
|
upfFlowData.value.lineYDown.shift();
|
||||||
|
upfFlowData.value.cap -= 1;
|
||||||
|
}
|
||||||
|
// UPF-总流量数0天 当天24小时
|
||||||
|
upfTFParse('0', {
|
||||||
|
up: upfTotalFlow.value['0'].up + +data['UPF.03'],
|
||||||
|
down: upfTotalFlow.value['0'].down + +data['UPF.06'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
type TFType = {
|
||||||
|
/**上行 N3 */
|
||||||
|
up: number;
|
||||||
|
upFrom: string;
|
||||||
|
/**下行 N6 */
|
||||||
|
down: number;
|
||||||
|
downFrom: string;
|
||||||
|
/**请求标记 */
|
||||||
|
requestFlag: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**UPF-总流量数 */
|
||||||
|
export const upfTotalFlow = ref<Record<string, TFType>>({
|
||||||
|
'0': {
|
||||||
|
up: 0,
|
||||||
|
upFrom: '0 B',
|
||||||
|
down: 0,
|
||||||
|
downFrom: '0 B',
|
||||||
|
requestFlag: false,
|
||||||
|
},
|
||||||
|
'7': {
|
||||||
|
up: 0,
|
||||||
|
upFrom: '0 B',
|
||||||
|
down: 0,
|
||||||
|
downFrom: '0 B',
|
||||||
|
requestFlag: false,
|
||||||
|
},
|
||||||
|
'30': {
|
||||||
|
up: 0,
|
||||||
|
upFrom: '0 B',
|
||||||
|
down: 0,
|
||||||
|
downFrom: '0 B',
|
||||||
|
requestFlag: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**UPF-总流量数 数据解析 */
|
||||||
|
export function upfTFParse(day: string, data: Record<string, number>) {
|
||||||
|
let { up, down } = data;
|
||||||
|
upfTotalFlow.value[day] = {
|
||||||
|
up: up,
|
||||||
|
upFrom: parseSizeFromBits(up),
|
||||||
|
down: down,
|
||||||
|
downFrom: parseSizeFromBits(down),
|
||||||
|
requestFlag: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**UPF-总流量数 选中 */
|
||||||
|
export const upfTFActive = ref<string>('0');
|
||||||
|
|
||||||
|
/**属性复位 */
|
||||||
|
export function upfTotalFlowReset() {
|
||||||
|
upfFlowData.value = {
|
||||||
|
lineXTime: [],
|
||||||
|
lineYUp: [],
|
||||||
|
lineYDown: [],
|
||||||
|
cap: 0,
|
||||||
|
};
|
||||||
|
for (const key of Object.keys(upfTotalFlow.value)) {
|
||||||
|
upfTotalFlow.value[key] = {
|
||||||
|
up: 0,
|
||||||
|
upFrom: '0 B',
|
||||||
|
down: 0,
|
||||||
|
downFrom: '0 B',
|
||||||
|
requestFlag: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
upfTFActive.value = '0';
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
/**ueEventAMFParse UE会话事件AMF 数据解析 */
|
||||||
|
function ueEventAMFParse(
|
||||||
|
item: Record<string, any>
|
||||||
|
): false | Record<string, any> {
|
||||||
|
let evData: Record<string, any> = item.eventJSON;
|
||||||
|
if (typeof evData === 'string') {
|
||||||
|
try {
|
||||||
|
evData = JSON.parse(evData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
eType: 'amf_ue',
|
||||||
|
eId: `amf_ue_${item.id}_${Date.now()}`,
|
||||||
|
eTime: +item.timestamp,
|
||||||
|
id: item.id,
|
||||||
|
type: item.eventType,
|
||||||
|
data: evData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**ueEventMMEParse UE会话事件MME 数据解析 */
|
||||||
|
function ueEventMMEParse(
|
||||||
|
item: Record<string, any>
|
||||||
|
): false | Record<string, any> {
|
||||||
|
let evData: Record<string, any> = item.eventJSON;
|
||||||
|
if (typeof evData === 'string') {
|
||||||
|
try {
|
||||||
|
evData = JSON.parse(evData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
eType: 'mme_ue',
|
||||||
|
eId: `mme_ue_${item.id}_${Date.now()}`,
|
||||||
|
eTime: +item.timestamp,
|
||||||
|
id: item.id,
|
||||||
|
type: item.eventType,
|
||||||
|
data: evData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**cdrEventIMSParse CDR会话事件IMS 数据解析 */
|
||||||
|
function cdrEventIMSParse(
|
||||||
|
item: Record<string, any>
|
||||||
|
): false | Record<string, any> {
|
||||||
|
let evData: Record<string, any> = item.cdrJSON || item.CDR;
|
||||||
|
if (typeof evData === 'string') {
|
||||||
|
try {
|
||||||
|
evData = JSON.parse(evData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指定显示CDR类型MOC/MTSM
|
||||||
|
if (!['MOC', 'MTSM'].includes(evData.recordType)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
eType: 'ims_cdr',
|
||||||
|
eId: `ims_cdr_${item.id}_${Date.now()}`,
|
||||||
|
eTime: +item.timestamp,
|
||||||
|
id: item.id,
|
||||||
|
data: evData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**eventListParse 事件列表解析 */
|
||||||
|
export function eventListParse(
|
||||||
|
type: 'ims_cdr' | 'amf_ue' | 'mme_ue',
|
||||||
|
data: any
|
||||||
|
) {
|
||||||
|
eventTotal.value += data.total;
|
||||||
|
for (const item of data.rows) {
|
||||||
|
let v: false | Record<string, any> = false;
|
||||||
|
if (type === 'ims_cdr') {
|
||||||
|
v = cdrEventIMSParse(item);
|
||||||
|
}
|
||||||
|
if (type === 'amf_ue') {
|
||||||
|
v = ueEventAMFParse(item);
|
||||||
|
}
|
||||||
|
if (type === 'mme_ue') {
|
||||||
|
v = ueEventMMEParse(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v) {
|
||||||
|
eventData.value.push(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 有数据进行排序
|
||||||
|
if (eventData.value.length > 5) {
|
||||||
|
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||||
|
}
|
||||||
|
if (eventData.value.length > 0) {
|
||||||
|
eventId.value = eventData.value[0].eId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**eventItemParseAndPush 事件项解析并添加 */
|
||||||
|
export async function eventItemParseAndPush(
|
||||||
|
type: 'ims_cdr' | 'amf_ue' | 'mme_ue',
|
||||||
|
item: any
|
||||||
|
) {
|
||||||
|
let v: false | Record<string, any> = false;
|
||||||
|
if (type === 'ims_cdr') {
|
||||||
|
v = cdrEventIMSParse(item);
|
||||||
|
}
|
||||||
|
if (type === 'amf_ue') {
|
||||||
|
v = ueEventAMFParse(item);
|
||||||
|
}
|
||||||
|
if (type === 'mme_ue') {
|
||||||
|
v = ueEventMMEParse(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v) {
|
||||||
|
eventData.value.unshift(v);
|
||||||
|
eventTotal.value += 1;
|
||||||
|
eventId.value = v.eId;
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800));
|
||||||
|
if (eventData.value.length > 20) {
|
||||||
|
eventData.value.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**CDR+UE事件数据 */
|
||||||
|
export const eventData = ref<Record<string, any>[]>([]);
|
||||||
|
/**CDR+UE事件总量 */
|
||||||
|
export const eventTotal = ref<number>(0);
|
||||||
|
/**CDR/UE事件推送id */
|
||||||
|
export const eventId = ref<string>('');
|
||||||
|
|
||||||
|
/**属性复位 */
|
||||||
|
export function userActivityReset() {
|
||||||
|
eventData.value = [];
|
||||||
|
eventTotal.value = 0;
|
||||||
|
eventId.value = '';
|
||||||
|
}
|
||||||
204
practical_training/views/dashboard/overview/hooks/useWS.ts
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
|
||||||
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
|
import { onBeforeUnmount, onMounted } from 'vue';
|
||||||
|
import {
|
||||||
|
eventListParse,
|
||||||
|
eventItemParseAndPush,
|
||||||
|
userActivityReset,
|
||||||
|
} from './useUserActivity';
|
||||||
|
import {
|
||||||
|
upfTotalFlow,
|
||||||
|
upfTFParse,
|
||||||
|
upfFlowParse,
|
||||||
|
upfTotalFlowReset,
|
||||||
|
} from './useUPFTotalFlow';
|
||||||
|
import { topologyReset, neStateParse } from './useTopology';
|
||||||
|
import PQueue from 'p-queue';
|
||||||
|
|
||||||
|
/**websocket连接 */
|
||||||
|
export default function useWS() {
|
||||||
|
const ws = new WS();
|
||||||
|
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||||
|
|
||||||
|
/**发消息 */
|
||||||
|
function wsSend(data: Record<string, any>) {
|
||||||
|
ws.send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsMessage(res: Record<string, any>) {
|
||||||
|
// console.log(res);
|
||||||
|
const { code, requestId, data } = res;
|
||||||
|
if (code === RESULT_CODE_ERROR) {
|
||||||
|
console.warn(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 网元状态
|
||||||
|
if (requestId && requestId.startsWith('neState')) {
|
||||||
|
const neType = requestId.split('_')[1];
|
||||||
|
neStateParse(neType, data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通信息
|
||||||
|
switch (requestId) {
|
||||||
|
// AMF_UE会话事件
|
||||||
|
case 'amf_1010':
|
||||||
|
if (Array.isArray(data.rows)) {
|
||||||
|
eventListParse('amf_ue', data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// MME_UE会话事件
|
||||||
|
case 'mme_1011_001':
|
||||||
|
if (Array.isArray(data.rows)) {
|
||||||
|
eventListParse('mme_ue', data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// IMS_CDR会话事件
|
||||||
|
case 'ims_1005_001':
|
||||||
|
if (Array.isArray(data.rows)) {
|
||||||
|
eventListParse('ims_cdr', data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
//UPF-总流量数
|
||||||
|
case 'upf_001_0':
|
||||||
|
upfTFParse('0', data);
|
||||||
|
break;
|
||||||
|
case 'upf_001_7':
|
||||||
|
upfTFParse('7', data);
|
||||||
|
break;
|
||||||
|
case 'upf_001_30':
|
||||||
|
upfTFParse('30', data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅组信息
|
||||||
|
if (!data?.groupId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (data.groupId) {
|
||||||
|
// kpiEvent 指标UPF
|
||||||
|
case '12_001':
|
||||||
|
if (data.data) {
|
||||||
|
upfFlowParse(data.data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// AMF_UE会话事件
|
||||||
|
case '1010':
|
||||||
|
if (data.data) {
|
||||||
|
queue.add(() => eventItemParseAndPush('amf_ue', data.data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// MME_UE会话事件
|
||||||
|
case '1011_001':
|
||||||
|
if (data.data) {
|
||||||
|
queue.add(() => eventItemParseAndPush('mme_ue', data.data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// IMS_CDR会话事件
|
||||||
|
case '1005_001':
|
||||||
|
if (data.data) {
|
||||||
|
queue.add(() => eventItemParseAndPush('ims_cdr', data.data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**UPF-总流量数 发消息*/
|
||||||
|
function upfTFSend(day: '0' | '7' | '30') {
|
||||||
|
// 请求标记检查避免重复发送
|
||||||
|
if (upfTotalFlow.value[day].requestFlag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
upfTotalFlow.value[day].requestFlag = true;
|
||||||
|
|
||||||
|
ws.send({
|
||||||
|
requestId: `upf_001_${day}`,
|
||||||
|
type: 'upf_tf',
|
||||||
|
data: {
|
||||||
|
neType: 'UPF',
|
||||||
|
neId: '001',
|
||||||
|
day: Number(day),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**userActivitySend 用户行为事件基础列表数据 发消息*/
|
||||||
|
function userActivitySend() {
|
||||||
|
// AMF_UE会话事件
|
||||||
|
ws.send({
|
||||||
|
requestId: 'amf_1010',
|
||||||
|
type: 'amf_ue',
|
||||||
|
data: {
|
||||||
|
neType: 'AMF',
|
||||||
|
neId: '001',
|
||||||
|
sortField: 'timestamp',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// MME_UE会话事件
|
||||||
|
ws.send({
|
||||||
|
requestId: 'mme_1011_001',
|
||||||
|
type: 'mme_ue',
|
||||||
|
data: {
|
||||||
|
neType: 'MME',
|
||||||
|
neId: '001',
|
||||||
|
sortField: 'timestamp',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// IMS_CDR会话事件
|
||||||
|
ws.send({
|
||||||
|
requestId: 'ims_1005_001',
|
||||||
|
type: 'ims_cdr',
|
||||||
|
data: {
|
||||||
|
neType: 'IMS',
|
||||||
|
neId: '001',
|
||||||
|
recordType: 'MOC',
|
||||||
|
sortField: 'timestamp',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: '/ws',
|
||||||
|
params: {
|
||||||
|
/**订阅通道组
|
||||||
|
*
|
||||||
|
* 指标UPF (GroupID:12_neId)
|
||||||
|
* AMF_UE会话事件(GroupID:1010)
|
||||||
|
* MME_UE会话事件(GroupID:1011_neId)
|
||||||
|
* IMS_CDR会话事件(GroupID:1005_neId)
|
||||||
|
*/
|
||||||
|
subGroupID: '12_001,1010,1011_001,1005_001',
|
||||||
|
},
|
||||||
|
onmessage: wsMessage,
|
||||||
|
onerror: (ev: any) => {
|
||||||
|
console.error(ev);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ws.connect(options);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
ws.close();
|
||||||
|
userActivityReset();
|
||||||
|
upfTotalFlowReset();
|
||||||
|
topologyReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
wsSend,
|
||||||
|
userActivitySend,
|
||||||
|
upfTFSend,
|
||||||
|
};
|
||||||
|
}
|
||||||
BIN
practical_training/views/dashboard/overview/images/border.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
practical_training/views/dashboard/overview/images/brand.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
practical_training/views/dashboard/overview/images/line.png
Normal file
|
After Width: | Height: | Size: 237 B |
BIN
practical_training/views/dashboard/overview/images/rect.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
493
practical_training/views/dashboard/overview/index.vue
Normal file
@@ -0,0 +1,493 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||||
|
import svgBase from '@/assets/svg/base.svg';
|
||||||
|
import svgUserIMS from '@/assets/svg/userIMS.svg';
|
||||||
|
import svgUserSMF from '@/assets/svg/userSMF.svg';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import Topology from './components/Topology/index.vue';
|
||||||
|
import NeResources from './components/NeResources/index.vue';
|
||||||
|
import UserActivity from './components/UserActivity/index.vue';
|
||||||
|
import AlarnTypeBar from './components/AlarnTypeBar/index.vue';
|
||||||
|
import UPFFlow from './components/UPFFlow/index.vue';
|
||||||
|
import { listUDMSub } from '@/api/neData/udm_sub';
|
||||||
|
import { listUENumBySMF } from '@/api/neUser/smf';
|
||||||
|
import { listUENumByIMS } from '@/api/neUser/ims';
|
||||||
|
import { listBase5G } from '@/api/neUser/base5G';
|
||||||
|
import {
|
||||||
|
graphNodeClickID,
|
||||||
|
graphState,
|
||||||
|
notNeNodes,
|
||||||
|
graphNodeStateNum,
|
||||||
|
neStateRequestMap,
|
||||||
|
} from './hooks/useTopology';
|
||||||
|
import { upfTotalFlow, upfTFActive } from './hooks/useUPFTotalFlow';
|
||||||
|
import { useFullscreen } from '@vueuse/core';
|
||||||
|
import useWS from './hooks/useWS';
|
||||||
|
import useAppStore from '@/store/modules/app';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { hasRoles } from '@/plugins/auth-user';
|
||||||
|
const router = useRouter();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { wsSend, userActivitySend, upfTFSend } = useWS();
|
||||||
|
|
||||||
|
/**概览状态类型 */
|
||||||
|
type SkimStateType = {
|
||||||
|
/**UDM签约用户数量 */
|
||||||
|
udmSubNum: number;
|
||||||
|
/**SMF在线用户数 */
|
||||||
|
smfUeNum: number;
|
||||||
|
/**IMS在线用户数 */
|
||||||
|
imsUeNum: number;
|
||||||
|
/**5G基站数量 */
|
||||||
|
gnbNum: number;
|
||||||
|
/**5G在线用户数量 */
|
||||||
|
gnbUeNum: number;
|
||||||
|
/**4G基站数量 */
|
||||||
|
enbNum: number;
|
||||||
|
/**4G在线用户数量 */
|
||||||
|
enbUeNum: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**概览状态信息 */
|
||||||
|
let skimState: SkimStateType = reactive({
|
||||||
|
udmSubNum: 0,
|
||||||
|
smfUeNum: 0,
|
||||||
|
imsUeNum: 0,
|
||||||
|
gnbNum: 0,
|
||||||
|
gnbUeNum: 0,
|
||||||
|
enbNum: 0,
|
||||||
|
enbUeNum: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**总览节点 */
|
||||||
|
const viewportDom = ref<HTMLElement | null>(null);
|
||||||
|
const { isFullscreen, toggle } = useFullscreen(viewportDom);
|
||||||
|
|
||||||
|
/**10s调度器 */
|
||||||
|
const interval10s = ref<any>(null);
|
||||||
|
|
||||||
|
/**5s调度器 */
|
||||||
|
const interval5s = ref<any>(null);
|
||||||
|
|
||||||
|
/**查询网元状态 */
|
||||||
|
function fnGetNeState() {
|
||||||
|
// 获取节点状态
|
||||||
|
for (const node of graphState.data.nodes) {
|
||||||
|
if (notNeNodes.includes(node.id)) continue;
|
||||||
|
const { neType, neId } = node.neInfo;
|
||||||
|
if (!neType || !neId) continue;
|
||||||
|
// 请求标记检查避免重复发送
|
||||||
|
if (neStateRequestMap.value.get(neType)) continue;
|
||||||
|
neStateRequestMap.value.set(neType, true);
|
||||||
|
|
||||||
|
wsSend({
|
||||||
|
requestId: `neState_${neType}_${neId}`,
|
||||||
|
type: 'ne_state',
|
||||||
|
data: {
|
||||||
|
neType: neType,
|
||||||
|
neId: neId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**获取概览信息 */
|
||||||
|
async function fnGetSkim() {
|
||||||
|
const resArr = await Promise.allSettled([
|
||||||
|
listUDMSub({
|
||||||
|
neid: '001',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 1,
|
||||||
|
}),
|
||||||
|
listUENumBySMF('001'),
|
||||||
|
listUENumByIMS('001'),
|
||||||
|
listBase5G({
|
||||||
|
neType: 'AMF',
|
||||||
|
neId: '001',
|
||||||
|
}),
|
||||||
|
listBase5G({
|
||||||
|
neType: 'MME',
|
||||||
|
neId: '001',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
const res0 = resArr[0].value;
|
||||||
|
if (res0.code === RESULT_CODE_SUCCESS) {
|
||||||
|
skimState.udmSubNum = res0.total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resArr[1].status === 'fulfilled') {
|
||||||
|
const res1 = resArr[1].value;
|
||||||
|
if (res1.code === RESULT_CODE_SUCCESS) {
|
||||||
|
skimState.smfUeNum = res1.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resArr[2].status === 'fulfilled') {
|
||||||
|
const res2 = resArr[2].value;
|
||||||
|
if (res2.code === RESULT_CODE_SUCCESS) {
|
||||||
|
skimState.imsUeNum = res2.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resArr[3].status === 'fulfilled') {
|
||||||
|
const res3 = resArr[3].value;
|
||||||
|
if (res3.code === RESULT_CODE_SUCCESS) {
|
||||||
|
skimState.gnbNum = res3.total;
|
||||||
|
skimState.gnbUeNum = 0;
|
||||||
|
res3.rows.map((item: any) => {
|
||||||
|
skimState.gnbUeNum += item.ueNum;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (resArr[4].status === 'fulfilled') {
|
||||||
|
const res4 = resArr[4].value;
|
||||||
|
if (res4.code === RESULT_CODE_SUCCESS) {
|
||||||
|
skimState.enbNum = res4.total;
|
||||||
|
skimState.enbUeNum = 0;
|
||||||
|
res4.rows.map((item: any) => {
|
||||||
|
skimState.enbUeNum += item.ueNum;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**初始数据函数 */
|
||||||
|
function loadData() {
|
||||||
|
fnGetNeState(); // 获取网元状态
|
||||||
|
userActivitySend();
|
||||||
|
upfTFSend('0');
|
||||||
|
upfTFSend('7');
|
||||||
|
upfTFSend('30');
|
||||||
|
|
||||||
|
clearInterval(interval10s.value);
|
||||||
|
interval10s.value = setInterval(() => {
|
||||||
|
if (!interval10s.value) return
|
||||||
|
if (upfTFActive.value === '0') {
|
||||||
|
upfTFSend('7');
|
||||||
|
upfTFActive.value = '7';
|
||||||
|
} else if (upfTFActive.value === '7') {
|
||||||
|
upfTFSend('30');
|
||||||
|
upfTFActive.value = '30';
|
||||||
|
} else if (upfTFActive.value === '30') {
|
||||||
|
upfTFSend('0');
|
||||||
|
upfTFActive.value = '0';
|
||||||
|
}
|
||||||
|
}, 10_000);
|
||||||
|
|
||||||
|
clearInterval(interval5s.value);
|
||||||
|
interval5s.value = setInterval(() => {
|
||||||
|
if (!interval5s.value) return
|
||||||
|
fnGetSkim(); // 获取概览信息
|
||||||
|
fnGetNeState(); // 获取网元状态
|
||||||
|
}, 5_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**栏目信息跳转 */
|
||||||
|
function fnToRouter(name: string, query?: any) {
|
||||||
|
if (hasRoles(['student'])) return;
|
||||||
|
router.push({ name, query });
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fnGetSkim().then(() => {
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearInterval(interval10s.value);
|
||||||
|
interval10s.value = null;
|
||||||
|
clearInterval(interval5s.value);
|
||||||
|
interval5s.value = null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="viewport" ref="viewportDom">
|
||||||
|
<div class="brand">
|
||||||
|
<div
|
||||||
|
class="brand-title"
|
||||||
|
@click="toggle"
|
||||||
|
:title="t('views.dashboard.overview.fullscreen')"
|
||||||
|
>
|
||||||
|
{{ t('views.dashboard.overview.title') }}
|
||||||
|
<FullscreenExitOutlined v-if="isFullscreen" />
|
||||||
|
<FullscreenOutlined v-else />
|
||||||
|
</div>
|
||||||
|
<div class="brand-desc">{{ appStore.appName }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column">
|
||||||
|
<!--概览-->
|
||||||
|
<div class="skim panel">
|
||||||
|
<div class="inner">
|
||||||
|
<h3>
|
||||||
|
<IdcardOutlined style="color: #68d8fe" />
|
||||||
|
{{ 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" /> 5G
|
||||||
|
{{ t('views.dashboard.overview.skim.baseTitle') }}
|
||||||
|
</h3>
|
||||||
|
<div class="data">
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
@click="fnToRouter('Base5G_2082', { neType: 'AMF' })"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<div style="align-items: flex-start">
|
||||||
|
<img
|
||||||
|
:src="svgBase"
|
||||||
|
style="width: 18px; margin-right: 8px; height: 2rem"
|
||||||
|
/>
|
||||||
|
{{ skimState.gnbNum }}
|
||||||
|
</div>
|
||||||
|
<span>{{ t('views.dashboard.overview.skim.gnbBase') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
@click="fnToRouter('Base5G_2082', { neType: 'AMF' })"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<div style="align-items: flex-start">
|
||||||
|
<UserOutlined
|
||||||
|
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||||
|
/>
|
||||||
|
{{ skimState.gnbUeNum }}
|
||||||
|
</div>
|
||||||
|
<span>{{ t('views.dashboard.overview.skim.gnbUeNum') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="skim panel base">
|
||||||
|
<div class="inner">
|
||||||
|
<h3>
|
||||||
|
<GlobalOutlined style="color: #68d8fe" /> 4G
|
||||||
|
{{ t('views.dashboard.overview.skim.baseTitle') }}
|
||||||
|
</h3>
|
||||||
|
<div class="data">
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
@click="fnToRouter('Base5G_2082', { neType: 'MME' })"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<div style="align-items: flex-start">
|
||||||
|
<img
|
||||||
|
:src="svgBase"
|
||||||
|
style="width: 18px; margin-right: 8px; height: 2rem"
|
||||||
|
/>
|
||||||
|
{{ skimState.enbNum }}
|
||||||
|
</div>
|
||||||
|
<span>{{ t('views.dashboard.overview.skim.enbBase') }}</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="item toRouter"
|
||||||
|
@click="fnToRouter('Base5G_2082', { neType: 'MME' })"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<div style="align-items: flex-start">
|
||||||
|
<UserOutlined
|
||||||
|
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||||
|
/>
|
||||||
|
{{ skimState.enbUeNum }}
|
||||||
|
</div>
|
||||||
|
<span>{{ t('views.dashboard.overview.skim.enbUeNum') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户行为 -->
|
||||||
|
<div class="userActivity panel">
|
||||||
|
<div class="inner">
|
||||||
|
<h3>
|
||||||
|
<WhatsAppOutlined style="color: #68d8fe" />
|
||||||
|
{{ 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" />
|
||||||
|
{{ 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" />
|
||||||
|
{{ 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" />
|
||||||
|
{{ t('views.dashboard.overview.upfFlowTotal.title') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<!-- 筛选 -->
|
||||||
|
<div class="filter">
|
||||||
|
<span
|
||||||
|
:data-key="v"
|
||||||
|
:class="{ active: upfTFActive === v }"
|
||||||
|
v-for="v in ['0', '7', '30']"
|
||||||
|
:key="v"
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
upfTFActive = v;
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
v === '0'
|
||||||
|
? '24' + t('common.units.hour')
|
||||||
|
: v + t('common.units.day')
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</h3>
|
||||||
|
<div class="chart">
|
||||||
|
<!-- 数据 -->
|
||||||
|
<div class="data">
|
||||||
|
<div class="item">
|
||||||
|
<span>
|
||||||
|
<ArrowUpOutlined style="color: #597ef7" />
|
||||||
|
{{ t('views.dashboard.overview.upfFlowTotal.up') }}
|
||||||
|
</span>
|
||||||
|
<h4>{{ upfTotalFlow[upfTFActive].up }}</h4>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<span>
|
||||||
|
<ArrowDownOutlined style="color: #52c41a" />
|
||||||
|
{{ t('views.dashboard.overview.upfFlowTotal.down') }}
|
||||||
|
</span>
|
||||||
|
<h4>{{ upfTotalFlow[upfTFActive].down }}</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 告警统计 -->
|
||||||
|
<div class="alarmType panel">
|
||||||
|
<div class="inner">
|
||||||
|
<h3
|
||||||
|
class="toRouter"
|
||||||
|
@click="fnToRouter('HistoryAlarm_2097')"
|
||||||
|
:title="t('views.dashboard.overview.toRouter')"
|
||||||
|
>
|
||||||
|
<PieChartOutlined style="color: #68d8fe" />
|
||||||
|
{{ 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" />
|
||||||
|
{{ t('views.dashboard.overview.resources.title') }}:
|
||||||
|
{{ graphNodeClickID }}
|
||||||
|
</h3>
|
||||||
|
<div class="chart">
|
||||||
|
<NeResources />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
@import url('./css/index.css');
|
||||||
|
</style>
|
||||||
838
practical_training/views/dashboard/smfCDR/index.vue
Normal file
@@ -0,0 +1,838 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, onMounted, toRaw, onBeforeUnmount, ref } from 'vue';
|
||||||
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
|
import { Modal, message } from 'ant-design-vue/es';
|
||||||
|
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||||
|
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||||
|
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||||
|
import useNeInfoStore from '@/store/modules/neinfo';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import {
|
||||||
|
RESULT_CODE_ERROR,
|
||||||
|
RESULT_CODE_SUCCESS,
|
||||||
|
} from '@/constants/result-constants';
|
||||||
|
import {
|
||||||
|
delSMFDataCDR,
|
||||||
|
exportSMFDataCDR,
|
||||||
|
listSMFDataCDR,
|
||||||
|
} from '@/api/neData/smf';
|
||||||
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
|
import PQueue from 'p-queue';
|
||||||
|
import saveAs from 'file-saver';
|
||||||
|
import { hasRoles } from '@/plugins/auth-user';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const ws = new WS();
|
||||||
|
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||||
|
|
||||||
|
/**网元可选 */
|
||||||
|
let neOtions = ref<Record<string, any>[]>([]);
|
||||||
|
|
||||||
|
/**开始结束时间 */
|
||||||
|
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||||
|
|
||||||
|
/**查询参数 */
|
||||||
|
let queryParams = reactive({
|
||||||
|
/**网元类型 */
|
||||||
|
neType: 'SMF',
|
||||||
|
neId: '001',
|
||||||
|
subscriberID: '',
|
||||||
|
sortField: 'timestamp',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
/**开始时间 */
|
||||||
|
startTime: '',
|
||||||
|
/**结束时间 */
|
||||||
|
endTime: '',
|
||||||
|
/**当前页数 */
|
||||||
|
pageNum: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**查询参数重置 */
|
||||||
|
function fnQueryReset() {
|
||||||
|
queryParams = Object.assign(queryParams, {
|
||||||
|
subscriberID: '',
|
||||||
|
startTime: '',
|
||||||
|
endTime: '',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
queryRangePicker.value = ['', ''];
|
||||||
|
tablePagination.current = 1;
|
||||||
|
tablePagination.pageSize = 20;
|
||||||
|
fnGetList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格状态类型 */
|
||||||
|
type TabeStateType = {
|
||||||
|
/**加载等待 */
|
||||||
|
loading: boolean;
|
||||||
|
/**紧凑型 */
|
||||||
|
size: SizeType;
|
||||||
|
/**搜索栏 */
|
||||||
|
seached: boolean;
|
||||||
|
/**记录数据 */
|
||||||
|
data: object[];
|
||||||
|
/**勾选记录 */
|
||||||
|
selectedRowKeys: (string | number)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**表格状态 */
|
||||||
|
let tableState: TabeStateType = reactive({
|
||||||
|
loading: false,
|
||||||
|
size: 'middle',
|
||||||
|
seached: true,
|
||||||
|
data: [],
|
||||||
|
selectedRowKeys: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格字段列 */
|
||||||
|
let tableColumns: ColumnsType = [
|
||||||
|
{
|
||||||
|
title: t('common.rowId'),
|
||||||
|
dataIndex: 'id',
|
||||||
|
align: 'center',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.smfChargingID'), // 计费ID
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.chargingID;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.smfSubscriptionIDType'), // 订阅 ID 类型
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.subscriberIdentifier?.subscriptionIDType;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.smfSubscriptionIDData'), // 订阅 ID 数据
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.subscriberIdentifier?.subscriptionIDData;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.smfDataVolumeUplink'), // 数据量上行链路
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
const listOfMultipleUnitUsage = cdrJSON.listOfMultipleUnitUsage;
|
||||||
|
if (
|
||||||
|
!Array.isArray(listOfMultipleUnitUsage) ||
|
||||||
|
listOfMultipleUnitUsage.length < 1
|
||||||
|
) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
|
||||||
|
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return usedUnitContainer[0].dataVolumeUplink;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.smfDataVolumeDownlink'), // 数据量下行链路
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 180,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
const listOfMultipleUnitUsage = cdrJSON.listOfMultipleUnitUsage;
|
||||||
|
if (
|
||||||
|
!Array.isArray(listOfMultipleUnitUsage) ||
|
||||||
|
listOfMultipleUnitUsage.length < 1
|
||||||
|
) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
|
||||||
|
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return usedUnitContainer[0].dataVolumeDownlink;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.smfDataTotalVolume'), // 数据总量
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
const listOfMultipleUnitUsage = cdrJSON.listOfMultipleUnitUsage;
|
||||||
|
if (
|
||||||
|
!Array.isArray(listOfMultipleUnitUsage) ||
|
||||||
|
listOfMultipleUnitUsage.length < 1
|
||||||
|
) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
|
||||||
|
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return usedUnitContainer[0].dataTotalVolume;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.smfDuration'), // 持续时间
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.duration;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.smfInvocationTime'), // 调用时间
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 200,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.invocationTimestamp;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('common.operate'),
|
||||||
|
key: 'id',
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**表格分页器参数 */
|
||||||
|
let tablePagination = reactive({
|
||||||
|
/**当前页数 */
|
||||||
|
current: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
/**默认的每页条数 */
|
||||||
|
defaultPageSize: 20,
|
||||||
|
/**指定每页可以显示多少条 */
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
/**只有一页时是否隐藏分页器 */
|
||||||
|
hideOnSinglePage: false,
|
||||||
|
/**是否可以快速跳转至某页 */
|
||||||
|
showQuickJumper: true,
|
||||||
|
/**是否可以改变 pageSize */
|
||||||
|
showSizeChanger: true,
|
||||||
|
/**数据总数 */
|
||||||
|
total: 0,
|
||||||
|
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||||
|
onChange: (page: number, pageSize: number) => {
|
||||||
|
tablePagination.current = page;
|
||||||
|
tablePagination.pageSize = pageSize;
|
||||||
|
queryParams.pageNum = page;
|
||||||
|
queryParams.pageSize = pageSize;
|
||||||
|
fnGetList();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格紧凑型变更操作 */
|
||||||
|
function fnTableSize({ key }: MenuInfo) {
|
||||||
|
tableState.size = key as SizeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格多选 */
|
||||||
|
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||||
|
tableState.selectedRowKeys = keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**对话框对象信息状态类型 */
|
||||||
|
type ModalStateType = {
|
||||||
|
/**确定按钮 loading */
|
||||||
|
confirmLoading: boolean;
|
||||||
|
/**最大ID值 */
|
||||||
|
maxId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**对话框对象信息状态 */
|
||||||
|
let modalState: ModalStateType = reactive({
|
||||||
|
confirmLoading: false,
|
||||||
|
maxId: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录删除
|
||||||
|
* @param id 编号
|
||||||
|
*/
|
||||||
|
function fnRecordDelete(id: string) {
|
||||||
|
if (!id || modalState.confirmLoading) return;
|
||||||
|
let msg = id;
|
||||||
|
if (id === '0') {
|
||||||
|
msg = `${id}... ${tableState.selectedRowKeys.length}`;
|
||||||
|
id = tableState.selectedRowKeys.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.dashboard.cdr.delTip', { msg }),
|
||||||
|
onOk() {
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
delSMFDataCDR(id)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
fnGetList(1);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**查询列表, pageNum初始页数 */
|
||||||
|
function fnGetList(pageNum?: number) {
|
||||||
|
if (tableState.loading) return;
|
||||||
|
tableState.loading = true;
|
||||||
|
if (pageNum) {
|
||||||
|
queryParams.pageNum = pageNum;
|
||||||
|
}
|
||||||
|
if (!queryRangePicker.value) {
|
||||||
|
queryRangePicker.value = ['', ''];
|
||||||
|
}
|
||||||
|
queryParams.startTime = queryRangePicker.value[0];
|
||||||
|
queryParams.endTime = queryRangePicker.value[1];
|
||||||
|
listSMFDataCDR(toRaw(queryParams)).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||||
|
// 取消勾选
|
||||||
|
if (tableState.selectedRowKeys.length > 0) {
|
||||||
|
tableState.selectedRowKeys = [];
|
||||||
|
}
|
||||||
|
tablePagination.total = res.total;
|
||||||
|
// 遍历处理cdr字符串数据
|
||||||
|
tableState.data = res.rows.map(item => {
|
||||||
|
let cdrJSON = item.cdrJSON;
|
||||||
|
if (!cdrJSON) {
|
||||||
|
Reflect.set(item, 'cdrJSON', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cdrJSON = JSON.parse(cdrJSON);
|
||||||
|
Reflect.set(item, 'cdrJSON', cdrJSON);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
Reflect.set(item, 'cdrJSON', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 取最大值ID用作实时累加
|
||||||
|
if (res.total > 0) {
|
||||||
|
modalState.maxId = Number(res.rows[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableState.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**列表导出 */
|
||||||
|
function fnExportList() {
|
||||||
|
if (modalState.confirmLoading) return;
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.dashboard.cdr.exportTip'),
|
||||||
|
onOk() {
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
const querys = toRaw(queryParams);
|
||||||
|
querys.pageSize = 10000;
|
||||||
|
exportSMFDataCDR(querys)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
saveAs(res.data, `smf_cdr_event_export_${Date.now()}.xlsx`);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**实时数据开关 */
|
||||||
|
const realTimeData = ref<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实时数据
|
||||||
|
*/
|
||||||
|
function fnRealTime() {
|
||||||
|
realTimeData.value = !realTimeData.value;
|
||||||
|
if (realTimeData.value) {
|
||||||
|
tableState.seached = false;
|
||||||
|
// 建立链接
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: '/ws',
|
||||||
|
params: {
|
||||||
|
/**订阅通道组
|
||||||
|
*
|
||||||
|
* CDR会话事件-SMF (GroupID:1006)
|
||||||
|
*/
|
||||||
|
subGroupID: `1006_${queryParams.neId}`,
|
||||||
|
},
|
||||||
|
onmessage: wsMessage,
|
||||||
|
onerror: wsError,
|
||||||
|
};
|
||||||
|
ws.connect(options);
|
||||||
|
} else {
|
||||||
|
ws.close();
|
||||||
|
tableState.seached = true;
|
||||||
|
fnGetList(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsError(ev: any) {
|
||||||
|
// 接收数据后回调
|
||||||
|
console.error(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsMessage(res: Record<string, any>) {
|
||||||
|
const { code, requestId, data } = res;
|
||||||
|
if (code === RESULT_CODE_ERROR) {
|
||||||
|
console.warn(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅组信息
|
||||||
|
if (!data?.groupId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// cdrEvent CDR会话事件
|
||||||
|
if (data.groupId === `1006_${queryParams.neId}`) {
|
||||||
|
const cdrEvent = data.data;
|
||||||
|
queue.add(async () => {
|
||||||
|
modalState.maxId += 1;
|
||||||
|
tableState.data.unshift({
|
||||||
|
id: modalState.maxId,
|
||||||
|
neType: cdrEvent.neType,
|
||||||
|
neName: cdrEvent.neName,
|
||||||
|
rmUID: cdrEvent.rmUID,
|
||||||
|
timestamp: cdrEvent.timestamp,
|
||||||
|
cdrJSON: cdrEvent.CDR,
|
||||||
|
});
|
||||||
|
tablePagination.total += 1;
|
||||||
|
if (tableState.data.length > 100) {
|
||||||
|
tableState.data.pop();
|
||||||
|
}
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 获取网元网元列表
|
||||||
|
useNeInfoStore()
|
||||||
|
.fnNelist()
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||||
|
if (res.data.length > 0) {
|
||||||
|
let arr: Record<string, any>[] = [];
|
||||||
|
res.data.forEach(i => {
|
||||||
|
if (i.neType === 'SMF') {
|
||||||
|
arr.push({ value: i.neId, label: i.neName });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
neOtions.value = arr;
|
||||||
|
if (arr.length > 0) {
|
||||||
|
queryParams.neId = arr[0].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.warning({
|
||||||
|
content: t('common.noData'),
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// 获取列表数据
|
||||||
|
fnGetList();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (ws.state() !== -1) {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageContainer>
|
||||||
|
<a-card
|
||||||
|
v-show="tableState.seached"
|
||||||
|
:bordered="false"
|
||||||
|
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||||
|
>
|
||||||
|
<!-- 表格搜索栏 -->
|
||||||
|
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item label="SMF" name="neId ">
|
||||||
|
<a-select
|
||||||
|
v-model:value="queryParams.neId"
|
||||||
|
:options="neOtions"
|
||||||
|
:placeholder="t('common.selectPlease')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.cdr.smfSubscriptionIDData')"
|
||||||
|
name="subscriberID"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="queryParams.subscriberID"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
:maxlength="40"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="8" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.cdr.time')"
|
||||||
|
name="queryRangePicker"
|
||||||
|
>
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="queryRangePicker"
|
||||||
|
allow-clear
|
||||||
|
bordered
|
||||||
|
:show-time="{ format: 'HH:mm:ss' }"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="x"
|
||||||
|
style="width: 100%"
|
||||||
|
></a-range-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="4" :md="12" :xs="24">
|
||||||
|
<a-form-item>
|
||||||
|
<a-space :size="8">
|
||||||
|
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||||
|
<template #icon><SearchOutlined /></template>
|
||||||
|
{{ t('common.search') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="default" @click.prevent="fnQueryReset">
|
||||||
|
<template #icon><ClearOutlined /></template>
|
||||||
|
{{ t('common.reset') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||||
|
<!-- 插槽-卡片左侧侧 -->
|
||||||
|
<template #title>
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-popconfirm
|
||||||
|
placement="bottomLeft"
|
||||||
|
:title="
|
||||||
|
!realTimeData
|
||||||
|
? t('views.dashboard.cdr.realTimeDataStart')
|
||||||
|
: t('views.dashboard.cdr.realTimeDataStop')
|
||||||
|
"
|
||||||
|
ok-text="Yes"
|
||||||
|
cancel-text="No"
|
||||||
|
@confirm="fnRealTime()"
|
||||||
|
>
|
||||||
|
<a-button type="primary" :danger="realTimeData">
|
||||||
|
<template #icon><FundOutlined /> </template>
|
||||||
|
{{
|
||||||
|
!realTimeData
|
||||||
|
? t('views.dashboard.cdr.realTimeDataStart')
|
||||||
|
: t('views.dashboard.cdr.realTimeDataStop')
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
type="default"
|
||||||
|
danger
|
||||||
|
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||||
|
:loading="modalState.confirmLoading"
|
||||||
|
@click.prevent="fnRecordDelete('0')"
|
||||||
|
v-if="!hasRoles(['student'])"
|
||||||
|
>
|
||||||
|
<template #icon><DeleteOutlined /></template>
|
||||||
|
{{ t('common.deleteText') }}
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<a-button type="dashed" @click.prevent="fnExportList()">
|
||||||
|
<template #icon><ExportOutlined /></template>
|
||||||
|
{{ t('common.export') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 插槽-卡片右侧 -->
|
||||||
|
<template #extra>
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.searchBarText') }}</template>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="tableState.seached"
|
||||||
|
:checked-children="t('common.switch.show')"
|
||||||
|
:un-checked-children="t('common.switch.hide')"
|
||||||
|
size="small"
|
||||||
|
:disabled="realTimeData"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.reloadText') }}</template>
|
||||||
|
<a-button type="text" @click.prevent="fnGetList()">
|
||||||
|
<template #icon><ReloadOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.sizeText') }}</template>
|
||||||
|
<a-dropdown trigger="click" placement="bottomRight">
|
||||||
|
<a-button type="text">
|
||||||
|
<template #icon><ColumnHeightOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu
|
||||||
|
:selected-keys="[tableState.size as string]"
|
||||||
|
@click="fnTableSize"
|
||||||
|
>
|
||||||
|
<a-menu-item key="default">
|
||||||
|
{{ t('common.size.default') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="middle">
|
||||||
|
{{ t('common.size.middle') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="small">
|
||||||
|
{{ t('common.size.small') }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 表格列表 -->
|
||||||
|
<a-table
|
||||||
|
class="table"
|
||||||
|
row-key="id"
|
||||||
|
:columns="hasRoles(['student']) ? tableColumns.filter((s:any)=>s.key !== 'id'): tableColumns"
|
||||||
|
:loading="tableState.loading"
|
||||||
|
:data-source="tableState.data"
|
||||||
|
:size="tableState.size"
|
||||||
|
:pagination="tablePagination"
|
||||||
|
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
|
||||||
|
:row-selection="{
|
||||||
|
type: 'checkbox',
|
||||||
|
columnWidth: '48px',
|
||||||
|
selectedRowKeys: tableState.selectedRowKeys,
|
||||||
|
onChange: fnTableSelectedRowKeys,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'id'">
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.deleteText') }}</template>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
@click.prevent="fnRecordDelete(record.id)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #expandedRowRender="{ record }">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="8" :md="12" :xs="24" :offset="2">
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.dashboard.cdr.cdrInfo') }}
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.common.neName') }}: </span>
|
||||||
|
<span>{{ record.neName }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.common.rmUid') }}: </span>
|
||||||
|
<span>{{ record.rmUID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.time') }}: </span>
|
||||||
|
<span>{{ record.cdrJSON.invocationTimestamp }}</span>
|
||||||
|
</div>
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.dashboard.cdr.rowInfo') }}
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>Record Network Function ID: </span>
|
||||||
|
<span>{{ record.cdrJSON.recordingNetworkFunctionID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Record Type: </span>
|
||||||
|
<span>{{ record.cdrJSON.recordType }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Record Opening Time: </span>
|
||||||
|
<span>{{ record.cdrJSON.recordOpeningTime }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Charging ID: </span>
|
||||||
|
<span>{{ record.cdrJSON.chargingID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Duration: </span>
|
||||||
|
<span>{{ record.cdrJSON.duration }}</span>
|
||||||
|
</div>
|
||||||
|
<a-divider orientation="left"> Subscriber Identifier </a-divider>
|
||||||
|
<div>
|
||||||
|
<span>Subscription ID Type: </span>
|
||||||
|
<span>
|
||||||
|
{{ record.cdrJSON.subscriberIdentifier?.subscriptionIDType }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Subscription ID Data: </span>
|
||||||
|
<span>
|
||||||
|
{{ record.cdrJSON.subscriberIdentifier?.subscriptionIDData }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="8" :md="12" :xs="24">
|
||||||
|
<a-divider orientation="left">
|
||||||
|
List Of Multiple Unit Usage
|
||||||
|
</a-divider>
|
||||||
|
<div v-for="u in record.cdrJSON.listOfMultipleUnitUsage">
|
||||||
|
<div>RatingGroup: {{ u.ratingGroup }}</div>
|
||||||
|
<div v-for="udata in u.usedUnitContainer">
|
||||||
|
<div>
|
||||||
|
<span>Data Total Volume: </span>
|
||||||
|
<span>{{ udata.dataTotalVolume }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Data Volume Downlink: </span>
|
||||||
|
<span>{{ udata.dataVolumeDownlink }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Data Volume Uplink: </span>
|
||||||
|
<span>{{ udata.dataVolumeUplink }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Time: </span>
|
||||||
|
<span>{{ udata.time }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a-divider orientation="left">
|
||||||
|
PDU Session Charging Information
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>User Identifier: </span>
|
||||||
|
<span>{{
|
||||||
|
record.cdrJSON.pDUSessionChargingInformation?.userIdentifier
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>SSC Mode: </span>
|
||||||
|
<span>{{
|
||||||
|
record.cdrJSON.pDUSessionChargingInformation?.sSCMode
|
||||||
|
}}</span>
|
||||||
|
|
||||||
|
<span>RAT Type: </span>
|
||||||
|
<span>{{
|
||||||
|
record.cdrJSON.pDUSessionChargingInformation?.rATType
|
||||||
|
}}</span>
|
||||||
|
|
||||||
|
<span>DNN ID: </span>
|
||||||
|
<span>
|
||||||
|
{{ record.cdrJSON.pDUSessionChargingInformation?.dNNID }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>PDU Type: </span>
|
||||||
|
<span>
|
||||||
|
{{ record.cdrJSON.pDUSessionChargingInformation?.pDUType }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>PDU IPv4 Address: </span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
record.cdrJSON.pDUSessionChargingInformation?.pDUAddress
|
||||||
|
?.pDUIPv4Address
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>PDU IPv6 Addres Swith Prefix: </span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
record.cdrJSON.pDUSessionChargingInformation?.pDUAddress
|
||||||
|
?.pDUIPv6AddresswithPrefix
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>Network Function IPv4: </span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
record.cdrJSON.nFunctionConsumerInformation
|
||||||
|
?.networkFunctionIPv4Address
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-card>
|
||||||
|
</PageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.table :deep(.ant-pagination) {
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
770
practical_training/views/dashboard/smscCDR/index.vue
Normal file
@@ -0,0 +1,770 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, onMounted, toRaw, ref, onBeforeUnmount } from 'vue';
|
||||||
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
|
import { message, Modal } from 'ant-design-vue/es';
|
||||||
|
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||||
|
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||||
|
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import {
|
||||||
|
RESULT_CODE_ERROR,
|
||||||
|
RESULT_CODE_SUCCESS,
|
||||||
|
} from '@/constants/result-constants';
|
||||||
|
import useNeInfoStore from '@/store/modules/neinfo';
|
||||||
|
import {
|
||||||
|
delSMSCDataCDR,
|
||||||
|
exportSMSCDataCDR,
|
||||||
|
listSMSCDataCDR,
|
||||||
|
} from '@/api/neData/smsc';
|
||||||
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
|
import saveAs from 'file-saver';
|
||||||
|
import PQueue from 'p-queue';
|
||||||
|
import { hasRoles } from '@/plugins/auth-user';
|
||||||
|
const { getDict } = useDictStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const ws = new WS();
|
||||||
|
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||||
|
|
||||||
|
/**字典数据 */
|
||||||
|
let dict: {
|
||||||
|
/**CDR 响应原因代码类别类型 */
|
||||||
|
cdrCauseCode: DictType[];
|
||||||
|
} = reactive({
|
||||||
|
cdrCauseCode: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**网元可选 */
|
||||||
|
let neOtions = ref<Record<string, any>[]>([]);
|
||||||
|
|
||||||
|
/**开始结束时间 */
|
||||||
|
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||||
|
|
||||||
|
/**查询参数 */
|
||||||
|
let queryParams = reactive({
|
||||||
|
/**网元类型 */
|
||||||
|
neType: 'SMSC',
|
||||||
|
neId: '001',
|
||||||
|
recordType: '',
|
||||||
|
callerParty: '',
|
||||||
|
calledParty: '',
|
||||||
|
sortField: 'timestamp',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
/**开始时间 */
|
||||||
|
startTime: '',
|
||||||
|
/**结束时间 */
|
||||||
|
endTime: '',
|
||||||
|
/**当前页数 */
|
||||||
|
pageNum: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**查询参数重置 */
|
||||||
|
function fnQueryReset() {
|
||||||
|
recordTypes.value = [];
|
||||||
|
queryParams = Object.assign(queryParams, {
|
||||||
|
recordType: '',
|
||||||
|
callerParty: '',
|
||||||
|
calledParty: '',
|
||||||
|
startTime: '',
|
||||||
|
endTime: '',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
queryRangePicker.value = ['', ''];
|
||||||
|
tablePagination.current = 1;
|
||||||
|
tablePagination.pageSize = 20;
|
||||||
|
fnGetList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**记录类型 */
|
||||||
|
const recordTypes = ref<string[]>([]);
|
||||||
|
|
||||||
|
/**查询记录类型变更 */
|
||||||
|
function fnQueryRecordTypeChange(value: any) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
queryParams.recordType = value.join(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格状态类型 */
|
||||||
|
type TabeStateType = {
|
||||||
|
/**加载等待 */
|
||||||
|
loading: boolean;
|
||||||
|
/**紧凑型 */
|
||||||
|
size: SizeType;
|
||||||
|
/**搜索栏 */
|
||||||
|
seached: boolean;
|
||||||
|
/**记录数据 */
|
||||||
|
data: object[];
|
||||||
|
/**勾选记录 */
|
||||||
|
selectedRowKeys: (string | number)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**表格状态 */
|
||||||
|
let tableState: TabeStateType = reactive({
|
||||||
|
loading: false,
|
||||||
|
size: 'middle',
|
||||||
|
seached: true,
|
||||||
|
data: [],
|
||||||
|
selectedRowKeys: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格字段列 */
|
||||||
|
let tableColumns: ColumnsType = [
|
||||||
|
{
|
||||||
|
title: t('common.rowId'),
|
||||||
|
dataIndex: 'id',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.recordType'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.recordType;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.type'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.serviceType;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.caller'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
key: 'callerParty',
|
||||||
|
align: 'left',
|
||||||
|
width: 120,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.callerParty;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.called'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
key: 'calledParty',
|
||||||
|
align: 'left',
|
||||||
|
width: 120,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
return cdrJSON.calledParty;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.result'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
key: 'cause',
|
||||||
|
align: 'left',
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.dashboard.cdr.time'),
|
||||||
|
dataIndex: 'cdrJSON',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
customRender(opt) {
|
||||||
|
const cdrJSON = opt.value;
|
||||||
|
if (typeof cdrJSON.updateTime === 'number') {
|
||||||
|
return parseDateToStr(+cdrJSON.updateTime * 1000);
|
||||||
|
}
|
||||||
|
return cdrJSON.updateTime;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('common.operate'),
|
||||||
|
key: 'id',
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**表格分页器参数 */
|
||||||
|
let tablePagination = reactive({
|
||||||
|
/**当前页数 */
|
||||||
|
current: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
/**默认的每页条数 */
|
||||||
|
defaultPageSize: 20,
|
||||||
|
/**指定每页可以显示多少条 */
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
/**只有一页时是否隐藏分页器 */
|
||||||
|
hideOnSinglePage: false,
|
||||||
|
/**是否可以快速跳转至某页 */
|
||||||
|
showQuickJumper: true,
|
||||||
|
/**是否可以改变 pageSize */
|
||||||
|
showSizeChanger: true,
|
||||||
|
/**数据总数 */
|
||||||
|
total: 0,
|
||||||
|
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||||
|
onChange: (page: number, pageSize: number) => {
|
||||||
|
tablePagination.current = page;
|
||||||
|
tablePagination.pageSize = pageSize;
|
||||||
|
queryParams.pageNum = page;
|
||||||
|
queryParams.pageSize = pageSize;
|
||||||
|
fnGetList();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格紧凑型变更操作 */
|
||||||
|
function fnTableSize({ key }: MenuInfo) {
|
||||||
|
tableState.size = key as SizeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格多选 */
|
||||||
|
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||||
|
tableState.selectedRowKeys = keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**对话框对象信息状态类型 */
|
||||||
|
type ModalStateType = {
|
||||||
|
/**确定按钮 loading */
|
||||||
|
confirmLoading: boolean;
|
||||||
|
/**最大ID值 */
|
||||||
|
maxId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**对话框对象信息状态 */
|
||||||
|
let modalState: ModalStateType = reactive({
|
||||||
|
confirmLoading: false,
|
||||||
|
maxId: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录删除
|
||||||
|
* @param id 编号
|
||||||
|
*/
|
||||||
|
function fnRecordDelete(id: string) {
|
||||||
|
if (!id || modalState.confirmLoading) return;
|
||||||
|
let msg = id;
|
||||||
|
if (id === '0') {
|
||||||
|
msg = `${id}... ${tableState.selectedRowKeys.length}`;
|
||||||
|
id = tableState.selectedRowKeys.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.dashboard.cdr.delTip', { msg }),
|
||||||
|
onOk() {
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
delSMSCDataCDR(id)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
fnGetList(1);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**查询列表, pageNum初始页数 */
|
||||||
|
function fnGetList(pageNum?: number) {
|
||||||
|
if (tableState.loading) return;
|
||||||
|
tableState.loading = true;
|
||||||
|
if (pageNum) {
|
||||||
|
queryParams.pageNum = pageNum;
|
||||||
|
}
|
||||||
|
if (!queryRangePicker.value) {
|
||||||
|
queryRangePicker.value = ['', ''];
|
||||||
|
}
|
||||||
|
queryParams.startTime = queryRangePicker.value[0];
|
||||||
|
queryParams.endTime = queryRangePicker.value[1];
|
||||||
|
listSMSCDataCDR(toRaw(queryParams)).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||||
|
// 取消勾选
|
||||||
|
if (tableState.selectedRowKeys.length > 0) {
|
||||||
|
tableState.selectedRowKeys = [];
|
||||||
|
}
|
||||||
|
tablePagination.total = res.total;
|
||||||
|
// 遍历处理cdr字符串数据
|
||||||
|
tableState.data = res.rows.map(item => {
|
||||||
|
let cdrJSON = item.cdrJSON;
|
||||||
|
if (!cdrJSON) {
|
||||||
|
Reflect.set(item, 'cdrJSON', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
cdrJSON = JSON.parse(cdrJSON);
|
||||||
|
Reflect.set(item, 'cdrJSON', cdrJSON);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
Reflect.set(item, 'cdrJSON', {});
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 取最大值ID用作实时累加
|
||||||
|
if (res.total > 0) {
|
||||||
|
modalState.maxId = Number(res.rows[0].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableState.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**列表导出 */
|
||||||
|
function fnExportList() {
|
||||||
|
if (modalState.confirmLoading) return;
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.dashboard.cdr.exportTip'),
|
||||||
|
onOk() {
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
const querys = toRaw(queryParams);
|
||||||
|
querys.pageSize = 10000;
|
||||||
|
exportSMSCDataCDR(querys)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: t('common.operateOk'),
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
saveAs(res.data, `smsc_cdr_event_export_${Date.now()}.xlsx`);
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**实时数据开关 */
|
||||||
|
const realTimeData = ref<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实时数据
|
||||||
|
*/
|
||||||
|
function fnRealTime() {
|
||||||
|
realTimeData.value = !realTimeData.value;
|
||||||
|
if (realTimeData.value) {
|
||||||
|
tableState.seached = false;
|
||||||
|
// 建立链接
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: '/ws',
|
||||||
|
params: {
|
||||||
|
/**订阅通道组
|
||||||
|
*
|
||||||
|
* SMSC_CDR会话事件(GroupID:1007_neId)
|
||||||
|
*/
|
||||||
|
subGroupID: `1007_${queryParams.neId}`,
|
||||||
|
},
|
||||||
|
onmessage: wsMessage,
|
||||||
|
onerror: wsError,
|
||||||
|
};
|
||||||
|
ws.connect(options);
|
||||||
|
} else {
|
||||||
|
ws.close();
|
||||||
|
tableState.seached = true;
|
||||||
|
fnGetList(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsError(ev: any) {
|
||||||
|
// 接收数据后回调
|
||||||
|
console.error(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsMessage(res: Record<string, any>) {
|
||||||
|
const { code, requestId, data } = res;
|
||||||
|
if (code === RESULT_CODE_ERROR) {
|
||||||
|
console.warn(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅组信息
|
||||||
|
if (!data?.groupId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// cdrEvent CDR会话事件
|
||||||
|
if (data.groupId === `1007_${queryParams.neId}`) {
|
||||||
|
const cdrEvent = data.data;
|
||||||
|
queue.add(async () => {
|
||||||
|
modalState.maxId += 1;
|
||||||
|
tableState.data.unshift({
|
||||||
|
id: modalState.maxId,
|
||||||
|
neType: cdrEvent.neType,
|
||||||
|
neName: cdrEvent.neName,
|
||||||
|
rmUID: cdrEvent.rmUID,
|
||||||
|
timestamp: cdrEvent.timestamp,
|
||||||
|
cdrJSON: cdrEvent.CDR,
|
||||||
|
});
|
||||||
|
tablePagination.total += 1;
|
||||||
|
if (tableState.data.length > 100) {
|
||||||
|
tableState.data.pop();
|
||||||
|
}
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始字典数据
|
||||||
|
Promise.allSettled([getDict('cdr_cause_code')]).then(resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
dict.cdrCauseCode = resArr[0].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 获取网元网元列表
|
||||||
|
useNeInfoStore()
|
||||||
|
.fnNelist()
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||||
|
if (res.data.length > 0) {
|
||||||
|
let arr: Record<string, any>[] = [];
|
||||||
|
res.data.forEach(i => {
|
||||||
|
if (i.neType === 'SMSC') {
|
||||||
|
arr.push({ value: i.neId, label: i.neName });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
neOtions.value = arr;
|
||||||
|
if (arr.length > 0) {
|
||||||
|
queryParams.neId = arr[0].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
message.warning({
|
||||||
|
content: t('common.noData'),
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// 获取列表数据
|
||||||
|
fnGetList();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (ws.state() !== -1) {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageContainer>
|
||||||
|
<a-card
|
||||||
|
v-show="tableState.seached"
|
||||||
|
:bordered="false"
|
||||||
|
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||||
|
>
|
||||||
|
<!-- 表格搜索栏 -->
|
||||||
|
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item label="SMSC" name="neId ">
|
||||||
|
<a-select
|
||||||
|
v-model:value="queryParams.neId"
|
||||||
|
:options="neOtions"
|
||||||
|
:placeholder="t('common.selectPlease')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.cdr.called')"
|
||||||
|
name="calledParty"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="queryParams.calledParty"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.cdr.caller')"
|
||||||
|
name="callerParty "
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="queryParams.callerParty"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="4" :md="12" :xs="24">
|
||||||
|
<a-form-item>
|
||||||
|
<a-space :size="8">
|
||||||
|
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||||
|
<template #icon><SearchOutlined /></template>
|
||||||
|
{{ t('common.search') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="default" @click.prevent="fnQueryReset">
|
||||||
|
<template #icon><ClearOutlined /></template>
|
||||||
|
{{ t('common.reset') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="8" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.cdr.recordType')"
|
||||||
|
name="recordType"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="recordTypes"
|
||||||
|
mode="multiple"
|
||||||
|
:options="['MOSM', 'MTSM'].map(v => ({ value: v }))"
|
||||||
|
:placeholder="t('common.selectPlease')"
|
||||||
|
@change="fnQueryRecordTypeChange"
|
||||||
|
></a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="8" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.dashboard.cdr.time')"
|
||||||
|
name="queryRangePicker"
|
||||||
|
>
|
||||||
|
<a-range-picker
|
||||||
|
v-model:value="queryRangePicker"
|
||||||
|
allow-clear
|
||||||
|
bordered
|
||||||
|
:show-time="{ format: 'HH:mm:ss' }"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="x"
|
||||||
|
style="width: 100%"
|
||||||
|
></a-range-picker>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||||
|
<!-- 插槽-卡片左侧侧 -->
|
||||||
|
<template #title>
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-popconfirm
|
||||||
|
placement="bottomLeft"
|
||||||
|
:title="
|
||||||
|
!realTimeData
|
||||||
|
? t('views.dashboard.cdr.realTimeDataStart')
|
||||||
|
: t('views.dashboard.cdr.realTimeDataStop')
|
||||||
|
"
|
||||||
|
ok-text="Yes"
|
||||||
|
cancel-text="No"
|
||||||
|
@confirm="fnRealTime()"
|
||||||
|
>
|
||||||
|
<a-button type="primary" :danger="realTimeData">
|
||||||
|
<template #icon><FundOutlined /> </template>
|
||||||
|
{{
|
||||||
|
!realTimeData
|
||||||
|
? t('views.dashboard.cdr.realTimeDataStart')
|
||||||
|
: t('views.dashboard.cdr.realTimeDataStop')
|
||||||
|
}}
|
||||||
|
</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
type="default"
|
||||||
|
danger
|
||||||
|
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||||
|
:loading="modalState.confirmLoading"
|
||||||
|
@click.prevent="fnRecordDelete('0')"
|
||||||
|
>
|
||||||
|
<template #icon><DeleteOutlined /></template>
|
||||||
|
{{ t('common.deleteText') }}
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<a-button type="dashed" @click.prevent="fnExportList()">
|
||||||
|
<template #icon><ExportOutlined /></template>
|
||||||
|
{{ t('common.export') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 插槽-卡片右侧 -->
|
||||||
|
<template #extra>
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.searchBarText') }}</template>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="tableState.seached"
|
||||||
|
:checked-children="t('common.switch.show')"
|
||||||
|
:un-checked-children="t('common.switch.hide')"
|
||||||
|
size="small"
|
||||||
|
:disabled="realTimeData"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.reloadText') }}</template>
|
||||||
|
<a-button type="text" @click.prevent="fnGetList()">
|
||||||
|
<template #icon><ReloadOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.sizeText') }}</template>
|
||||||
|
<a-dropdown trigger="click" placement="bottomRight">
|
||||||
|
<a-button type="text">
|
||||||
|
<template #icon><ColumnHeightOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu
|
||||||
|
:selected-keys="[tableState.size as string]"
|
||||||
|
@click="fnTableSize"
|
||||||
|
>
|
||||||
|
<a-menu-item key="default">
|
||||||
|
{{ t('common.size.default') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="middle">
|
||||||
|
{{ t('common.size.middle') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="small">
|
||||||
|
{{ t('common.size.small') }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 表格列表 -->
|
||||||
|
<a-table
|
||||||
|
class="table"
|
||||||
|
row-key="id"
|
||||||
|
:columns="hasRoles(['student']) ? tableColumns.filter((s:any)=>s.key !== 'id'): tableColumns"
|
||||||
|
:loading="tableState.loading"
|
||||||
|
:data-source="tableState.data"
|
||||||
|
:size="tableState.size"
|
||||||
|
:pagination="tablePagination"
|
||||||
|
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
|
||||||
|
:row-selection="{
|
||||||
|
type: 'checkbox',
|
||||||
|
columnWidth: '48px',
|
||||||
|
selectedRowKeys: tableState.selectedRowKeys,
|
||||||
|
onChange: fnTableSelectedRowKeys,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'cause'">
|
||||||
|
<span v-if="record.cdrJSON.result === 0">
|
||||||
|
{{ t('views.dashboard.cdr.resultFail') }},
|
||||||
|
<DictTag
|
||||||
|
:options="dict.cdrCauseCode"
|
||||||
|
:value="record.cdrJSON.cause"
|
||||||
|
value-default="0"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ t('views.dashboard.cdr.resultOk') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'id'">
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.deleteText') }}</template>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
@click.prevent="fnRecordDelete(record.id)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<DeleteOutlined />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #expandedRowRender="{ record }">
|
||||||
|
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.dashboard.cdr.cdrInfo') }}
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.common.neName') }}: </span>
|
||||||
|
<span>{{ record.neName }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.common.rmUid') }}: </span>
|
||||||
|
<span>{{ record.rmUID }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.time') }}: </span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
typeof record.cdrJSON.updateTime === 'number'
|
||||||
|
? parseDateToStr(+record.cdrJSON.updateTime * 1000)
|
||||||
|
: record.cdrJSON.updateTime
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.dashboard.cdr.rowInfo') }}
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.type') }}: </span>
|
||||||
|
<span>{{ record.cdrJSON.serviceType }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.caller') }}: </span>
|
||||||
|
<span>{{ record.cdrJSON.callerParty }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.called') }}: </span>
|
||||||
|
<span>{{ record.cdrJSON.calledParty }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.dashboard.cdr.result') }}: </span>
|
||||||
|
<span v-if="record.cdrJSON.result === 0">
|
||||||
|
{{ t('views.dashboard.cdr.resultFail') }},
|
||||||
|
<DictTag
|
||||||
|
:options="dict.cdrCauseCode"
|
||||||
|
:value="record.cdrJSON.cause"
|
||||||
|
value-default="0"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ t('views.dashboard.cdr.resultOk') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-card>
|
||||||
|
</PageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.table :deep(.ant-pagination) {
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
554
practical_training/views/monitor/topologyArchitecture/index.vue
Normal file
@@ -0,0 +1,554 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, onMounted, ref, onBeforeUnmount } from 'vue';
|
||||||
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
|
import {
|
||||||
|
RESULT_CODE_ERROR,
|
||||||
|
RESULT_CODE_SUCCESS,
|
||||||
|
} from '@/constants/result-constants';
|
||||||
|
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||||
|
import { message } from 'ant-design-vue/es';
|
||||||
|
import { getGraphData } from '@/api/monitor/topology';
|
||||||
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
|
import { Graph, GraphData, Menu, Tooltip } from '@antv/g6';
|
||||||
|
import {
|
||||||
|
edgeCubicAnimateCircleMove,
|
||||||
|
edgeCubicAnimateLineDash,
|
||||||
|
edgeLineAnimateState,
|
||||||
|
} from '../topologyBuild/hooks/registerEdge';
|
||||||
|
import {
|
||||||
|
nodeCircleAnimateShapeR,
|
||||||
|
nodeCircleAnimateShapeStroke,
|
||||||
|
nodeImageAnimateState,
|
||||||
|
nodeRectAnimateState,
|
||||||
|
} from '../topologyBuild/hooks/registerNode';
|
||||||
|
import useNeOptions from '@/views/ne/neInfo/hooks/useNeOptions';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
|
import { hasRoles } from '@/plugins/auth-user';
|
||||||
|
import { parseBasePath } from '@/plugins/file-static-url';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { fnNeRestart, fnNeStop, fnNeLogFile } = useNeOptions();
|
||||||
|
const ws = new WS();
|
||||||
|
|
||||||
|
/**图DOM节点实例对象 */
|
||||||
|
const graphG6Dom = ref<HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
|
/**图状态 */
|
||||||
|
const graphState = reactive<Record<string, any>>({
|
||||||
|
/**当前图组名 */
|
||||||
|
group: '5GC System Architecture',
|
||||||
|
/**图数据 */
|
||||||
|
data: {
|
||||||
|
combos: [],
|
||||||
|
edges: [],
|
||||||
|
nodes: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**非网元元素 */
|
||||||
|
const notNeNodes = [
|
||||||
|
'5GC',
|
||||||
|
'DN',
|
||||||
|
'UE',
|
||||||
|
'Base',
|
||||||
|
'lan',
|
||||||
|
'lan1',
|
||||||
|
'lan2',
|
||||||
|
'lan3',
|
||||||
|
'lan4',
|
||||||
|
'lan5',
|
||||||
|
'lan6',
|
||||||
|
'lan7',
|
||||||
|
'LAN',
|
||||||
|
'NR',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**图实例对象 */
|
||||||
|
const graphG6 = ref<any>(null);
|
||||||
|
|
||||||
|
/**图节点右击菜单 */
|
||||||
|
const graphNodeMenu = new Menu({
|
||||||
|
offsetX: 6,
|
||||||
|
offseY: 10,
|
||||||
|
itemTypes: ['node'],
|
||||||
|
getContent(evt) {
|
||||||
|
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
|
||||||
|
const { id, label, neState }: any = evt.item?.getModel();
|
||||||
|
if (notNeNodes.includes(id)) {
|
||||||
|
return `<div><span>${label || id}</span></div>`;
|
||||||
|
}
|
||||||
|
if (!neState) {
|
||||||
|
return `<div><span>${label || id}</span></div>`;
|
||||||
|
}
|
||||||
|
if (hasRoles(['student'])) {
|
||||||
|
return 'Student';
|
||||||
|
}
|
||||||
|
return `
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 140px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<h3 style="margin-bottom: 8px">
|
||||||
|
${t('views.monitor.topology.name')}:
|
||||||
|
${neState.neName ?? '--'}
|
||||||
|
</h3>
|
||||||
|
<div id="restart" style="cursor: pointer; margin-bottom: 4px">
|
||||||
|
> ${t('views.ne.common.restart')}
|
||||||
|
</div>
|
||||||
|
<div id="stop" style="cursor: pointer; margin-bottom: 4px;">
|
||||||
|
> ${t('views.ne.common.stop')}
|
||||||
|
</div>
|
||||||
|
<div id="log" style="cursor: pointer; margin-bottom: 4px;">
|
||||||
|
> ${t('views.ne.common.log')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
handleMenuClick(target, item) {
|
||||||
|
const { neInfo }: any = item?.getModel();
|
||||||
|
const { neName, neType, neId } = neInfo;
|
||||||
|
const targetId = target.id;
|
||||||
|
switch (targetId) {
|
||||||
|
case 'restart':
|
||||||
|
fnNeRestart({ neName, neType, neId });
|
||||||
|
break;
|
||||||
|
case 'stop':
|
||||||
|
fnNeStop({ neName, neType, neId });
|
||||||
|
break;
|
||||||
|
case 'log':
|
||||||
|
fnNeLogFile({ neType, neId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**图节点展示 */
|
||||||
|
const graphNodeTooltip = new Tooltip({
|
||||||
|
offsetX: 10,
|
||||||
|
offsetY: 20,
|
||||||
|
getContent(evt) {
|
||||||
|
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
|
||||||
|
const { id, label, neState }: any = evt.item?.getModel();
|
||||||
|
if (notNeNodes.includes(id)) {
|
||||||
|
return `<div><span>${label || id}</span></div>`;
|
||||||
|
}
|
||||||
|
if (!neState) {
|
||||||
|
return `<div><span>${label || id}</span></div>`;
|
||||||
|
}
|
||||||
|
let notStudentInfo = '';
|
||||||
|
if (hasRoles(['teacher', 'admin'])) {
|
||||||
|
notStudentInfo = `
|
||||||
|
<div><strong>${t('views.monitor.topology.serialNum')}:</strong><span>
|
||||||
|
${neState.sn ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
|
||||||
|
${neState.expire ?? '--'}
|
||||||
|
</span></div> `;
|
||||||
|
}
|
||||||
|
return `
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 200px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||||
|
${
|
||||||
|
neState.online
|
||||||
|
? t('views.monitor.topology.normalcy')
|
||||||
|
: t('views.monitor.topology.exceptions')
|
||||||
|
}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.refreshTime')}:</strong><span>
|
||||||
|
${neState.refreshTime ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
<div>========================</div>
|
||||||
|
<div><strong>ID:</strong><span>${neState.neId}</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.name')}:</strong><span>
|
||||||
|
${neState.neName ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
<div><strong>IP:</strong><span>${neState.neIP}</span></div>
|
||||||
|
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
|
||||||
|
${neState.version ?? '--'}
|
||||||
|
</span></div>
|
||||||
|
${notStudentInfo}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
},
|
||||||
|
itemTypes: ['node'],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**注册自定义边或节点 */
|
||||||
|
function registerEdgeNode() {
|
||||||
|
// 边
|
||||||
|
edgeCubicAnimateLineDash();
|
||||||
|
edgeCubicAnimateCircleMove();
|
||||||
|
edgeLineAnimateState();
|
||||||
|
// 节点
|
||||||
|
nodeCircleAnimateShapeR();
|
||||||
|
nodeCircleAnimateShapeStroke();
|
||||||
|
nodeRectAnimateState();
|
||||||
|
nodeImageAnimateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**图数据渲染 */
|
||||||
|
function handleRanderGraph(
|
||||||
|
container: HTMLElement | undefined,
|
||||||
|
data: GraphData
|
||||||
|
) {
|
||||||
|
if (!container) return;
|
||||||
|
const { clientHeight, clientWidth } = container;
|
||||||
|
|
||||||
|
// 注册自定义边或节点
|
||||||
|
registerEdgeNode();
|
||||||
|
|
||||||
|
const graph = new Graph({
|
||||||
|
container: container,
|
||||||
|
width: clientWidth,
|
||||||
|
height: clientHeight,
|
||||||
|
fitCenter: true,
|
||||||
|
fitView: true,
|
||||||
|
fitViewPadding: [40],
|
||||||
|
autoPaint: true,
|
||||||
|
modes: {
|
||||||
|
default: [
|
||||||
|
'drag-combo',
|
||||||
|
'drag-canvas',
|
||||||
|
'zoom-canvas',
|
||||||
|
'collapse-expand-combo',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
groupByTypes: false,
|
||||||
|
nodeStateStyles: {
|
||||||
|
selected: {
|
||||||
|
fill: 'transparent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [graphNodeMenu, graphNodeTooltip],
|
||||||
|
animate: true, // 是否使用动画过度,默认为 false
|
||||||
|
animateCfg: {
|
||||||
|
duration: 500, // Number,一次动画的时长
|
||||||
|
easing: 'linearEasing', // String,动画函数
|
||||||
|
},
|
||||||
|
});
|
||||||
|
graph.data(data);
|
||||||
|
graph.render();
|
||||||
|
|
||||||
|
graphG6.value = graph;
|
||||||
|
|
||||||
|
// 创建 ResizeObserver 实例
|
||||||
|
var observer = new ResizeObserver(function (entries) {
|
||||||
|
// 当元素大小发生变化时触发回调函数
|
||||||
|
entries.forEach(function (entry) {
|
||||||
|
if (!graphG6.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
graphG6.value.changeSize(
|
||||||
|
entry.contentRect.width,
|
||||||
|
entry.contentRect.height - 30
|
||||||
|
);
|
||||||
|
graphG6.value.fitCenter();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// 监听元素大小变化
|
||||||
|
observer.observe(container);
|
||||||
|
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取图组数据渲染到画布
|
||||||
|
* @param reload 是否重载数据
|
||||||
|
*/
|
||||||
|
function fnGraphDataLoad(reload: boolean = false) {
|
||||||
|
Promise.all([
|
||||||
|
getGraphData(graphState.group),
|
||||||
|
listAllNeInfo({
|
||||||
|
bandStatus: false,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
.then(resArr => {
|
||||||
|
const graphRes = resArr[0];
|
||||||
|
const neRes = resArr[1];
|
||||||
|
if (
|
||||||
|
graphRes.code === RESULT_CODE_SUCCESS &&
|
||||||
|
Array.isArray(graphRes.data.nodes) &&
|
||||||
|
graphRes.data.nodes.length > 0 &&
|
||||||
|
neRes.code === RESULT_CODE_SUCCESS &&
|
||||||
|
Array.isArray(neRes.data) &&
|
||||||
|
neRes.data.length > 0
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
graphData: graphRes.data,
|
||||||
|
neList: neRes.data,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
message.warning({
|
||||||
|
content: t('views.monitor.topology.noData'),
|
||||||
|
duration: 5,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (!res) return;
|
||||||
|
const { combos, edges, nodes } = res.graphData;
|
||||||
|
|
||||||
|
// 节点过滤
|
||||||
|
const nf: Record<string, any>[] = nodes.filter(
|
||||||
|
(node: Record<string, any>) => {
|
||||||
|
Reflect.set(node, 'neState', { online: false });
|
||||||
|
// 图片路径处理
|
||||||
|
if (node.img) node.img = parseBasePath(node.img);
|
||||||
|
if (node.icon.show && node.icon?.img)
|
||||||
|
node.icon.img = parseBasePath(node.icon.img);
|
||||||
|
// 遍历是否有网元数据
|
||||||
|
const nodeID: string = node.id;
|
||||||
|
const hasNe = res.neList.some(ne => {
|
||||||
|
Reflect.set(node, 'neInfo', ne.neType === nodeID ? ne : {});
|
||||||
|
return ne.neType === nodeID;
|
||||||
|
});
|
||||||
|
if (hasNe) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (notNeNodes.includes(nodeID)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 边过滤
|
||||||
|
const ef: Record<string, any>[] = edges.filter(
|
||||||
|
(edge: Record<string, any>) => {
|
||||||
|
const edgeSource: string = edge.source;
|
||||||
|
const edgeTarget: string = edge.target;
|
||||||
|
const hasNeS = nf.some(n => n.id === edgeSource);
|
||||||
|
const hasNeT = nf.some(n => n.id === edgeTarget);
|
||||||
|
// console.log(hasNeS, edgeSource, hasNeT, edgeTarget);
|
||||||
|
if (hasNeS && hasNeT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (hasNeS && notNeNodes.includes(edgeTarget)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (hasNeT && notNeNodes.includes(edgeSource)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 分组过滤
|
||||||
|
combos.forEach((combo: Record<string, any>) => {
|
||||||
|
const comboChildren: Record<string, any>[] = combo.children;
|
||||||
|
combo.children = comboChildren.filter(c => nf.some(n => n.id === c.id));
|
||||||
|
return combo;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 图数据
|
||||||
|
graphState.data = { combos, edges: ef, nodes: nf };
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (graphState.data.length < 0) return;
|
||||||
|
// 重载数据
|
||||||
|
if (reload) {
|
||||||
|
graphG6.value.read(graphState.data);
|
||||||
|
} else {
|
||||||
|
handleRanderGraph(graphG6Dom.value, graphState.data);
|
||||||
|
}
|
||||||
|
clearInterval(interval10s.value);
|
||||||
|
interval10s.value = null;
|
||||||
|
fnGetState();
|
||||||
|
interval10s.value = setInterval(async () => {
|
||||||
|
if (!interval10s.value) return;
|
||||||
|
fnGetState(); // 获取网元状态
|
||||||
|
}, 20_000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**网元状态调度器 */
|
||||||
|
const interval10s = ref<any>(null);
|
||||||
|
|
||||||
|
/**查询网元状态 */
|
||||||
|
function fnGetState() {
|
||||||
|
// 获取节点状态
|
||||||
|
for (const node of graphState.data.nodes) {
|
||||||
|
if (notNeNodes.includes(node.id)) continue;
|
||||||
|
const { neType, neId } = node.neInfo;
|
||||||
|
if (!neType || !neId) continue;
|
||||||
|
ws.send({
|
||||||
|
requestId: `${neType}_${neId}`,
|
||||||
|
type: 'ne_state',
|
||||||
|
data: {
|
||||||
|
neType: neType,
|
||||||
|
neId: neId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsError(ev: any) {
|
||||||
|
// 接收数据后回调
|
||||||
|
console.error(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收数据后回调 */
|
||||||
|
function wsMessage(res: Record<string, any>) {
|
||||||
|
const { code, requestId, data } = res;
|
||||||
|
if (code === RESULT_CODE_ERROR) {
|
||||||
|
console.warn(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!requestId) return;
|
||||||
|
const [neType, neId] = requestId.split('_');
|
||||||
|
const { combos, edges, nodes } = graphState.data;
|
||||||
|
const node = nodes.find((item: Record<string, any>) => item.id === neType);
|
||||||
|
|
||||||
|
// 更新网元状态
|
||||||
|
const newNeState = Object.assign(node.neState, data, {
|
||||||
|
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'),
|
||||||
|
online: !!data.cpu,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 通过 ID 查询节点实例
|
||||||
|
const item = graphG6.value.findById(node.id);
|
||||||
|
if (item) {
|
||||||
|
const stateColor = newNeState.online ? '#52c41a' : '#f5222d'; // 状态颜色
|
||||||
|
// 图片类型不能填充
|
||||||
|
if (node.type.startsWith('image')) {
|
||||||
|
// 更新节点
|
||||||
|
if (node.label !== newNeState.neName) {
|
||||||
|
graphG6.value.updateItem(item, {
|
||||||
|
label: newNeState.neName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 设置状态
|
||||||
|
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
|
||||||
|
} else {
|
||||||
|
// 更新节点
|
||||||
|
graphG6.value.updateItem(item, {
|
||||||
|
label: newNeState.neName,
|
||||||
|
// neState: newNeState,
|
||||||
|
style: {
|
||||||
|
fill: stateColor, // 填充色
|
||||||
|
stroke: stateColor, // 填充色
|
||||||
|
},
|
||||||
|
// labelCfg: {
|
||||||
|
// style: {
|
||||||
|
// fill: '#ffffff', // 标签文本色
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
});
|
||||||
|
// 设置状态
|
||||||
|
graphG6.value.setItemState(item, 'stroke', newNeState.online);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置边状态
|
||||||
|
for (const edge of edges) {
|
||||||
|
const edgeSource: string = edge.source;
|
||||||
|
const edgeTarget: string = edge.target;
|
||||||
|
const neS = nodes.find((n: any) => n.id === edgeSource);
|
||||||
|
const neT = nodes.find((n: any) => n.id === edgeTarget);
|
||||||
|
// console.log(neS, edgeSource, neT, edgeTarget);
|
||||||
|
|
||||||
|
if (neS && neT) {
|
||||||
|
// 通过 ID 查询节点实例
|
||||||
|
// const item = graphG6.value.findById(edge.id);
|
||||||
|
// console.log(
|
||||||
|
// `${edgeSource} - ${edgeTarget}`,
|
||||||
|
// neS.neState.online && neT.neState.online
|
||||||
|
// );
|
||||||
|
// const stateColor = neS.neState.online && neT.neState.online ? '#000000' : '#ff4d4f'; // 状态颜色
|
||||||
|
// 更新边
|
||||||
|
// graphG6.value.updateItem(item, {
|
||||||
|
// label: `${edgeSource} - ${edgeTarget}`,
|
||||||
|
// style: {
|
||||||
|
// stroke: stateColor, // 填充色
|
||||||
|
// },
|
||||||
|
// labelCfg: {
|
||||||
|
// style: {
|
||||||
|
// fill: '#ffffff', // 标签文本色
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// 设置状态
|
||||||
|
graphG6.value.setItemState(
|
||||||
|
edge.id,
|
||||||
|
'circle-move',
|
||||||
|
neS.neState.online && neT.neState.online
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (neS && notNeNodes.includes(edgeTarget)) {
|
||||||
|
graphG6.value.setItemState(edge.id, 'line-dash', neS.neState.online);
|
||||||
|
}
|
||||||
|
if (neT && notNeNodes.includes(edgeSource)) {
|
||||||
|
graphG6.value.setItemState(edge.id, 'line-dash', neT.neState.online);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fnGraphDataLoad(false);
|
||||||
|
|
||||||
|
// 建立链接
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: '/ws',
|
||||||
|
onmessage: wsMessage,
|
||||||
|
onerror: wsError,
|
||||||
|
};
|
||||||
|
ws.connect(options);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
ws.close();
|
||||||
|
clearInterval(interval10s.value);
|
||||||
|
interval10s.value = null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageContainer>
|
||||||
|
<a-card
|
||||||
|
:bordered="false"
|
||||||
|
:body-style="{ marginBottom: '24px' }"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<!-- 插槽-卡片左侧侧 -->
|
||||||
|
<template #title>
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<span>
|
||||||
|
{{ t('views.monitor.topologyBuild.graphGroup') }}:
|
||||||
|
{{ graphState.group }}
|
||||||
|
</span>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<!-- 插槽-卡片右侧 -->
|
||||||
|
<template #extra>
|
||||||
|
<a-button
|
||||||
|
type="default"
|
||||||
|
size="small"
|
||||||
|
@click.prevent="fnGraphDataLoad(true)"
|
||||||
|
>
|
||||||
|
<template #icon><ReloadOutlined /></template>
|
||||||
|
{{ t('common.reloadText') }}
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div ref="graphG6Dom" class="chart"></div>
|
||||||
|
</a-card>
|
||||||
|
</PageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100vh - 300px);
|
||||||
|
background-color: rgb(43, 47, 51);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
374
practical_training/views/ne/neInfo/components/BackConfModal.vue
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, toRaw, watch } from 'vue';
|
||||||
|
import { ProModal } from 'antdv-pro-modal';
|
||||||
|
import { Form, Modal, Upload, message, notification } from 'ant-design-vue/es';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { UploadRequestOption } from 'ant-design-vue/es/vc-upload/interface';
|
||||||
|
import { FileType, UploadFile } from 'ant-design-vue/es/upload/interface';
|
||||||
|
import {
|
||||||
|
exportNeConfigBackup,
|
||||||
|
importNeConfigBackup,
|
||||||
|
listNeConfigBackup,
|
||||||
|
} from '@/api/ne/neConfigBackup';
|
||||||
|
import saveAs from 'file-saver';
|
||||||
|
import { uploadFile } from '@/api/tool/file';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const emit = defineEmits(['ok', 'cancel', 'update:open']);
|
||||||
|
const props = defineProps({
|
||||||
|
open: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/**网元ID */
|
||||||
|
neId: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
neType: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**导入状态数据 */
|
||||||
|
const importState = reactive({
|
||||||
|
typeOption: [
|
||||||
|
{ label: t('views.ne.neInfo.backConf.server'), value: 'backup' },
|
||||||
|
{ label: t('views.ne.neInfo.backConf.local'), value: 'upload' },
|
||||||
|
],
|
||||||
|
backupData: <any[]>[],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**查询网元远程服务器备份文件 */
|
||||||
|
function backupSearch(name?: string) {
|
||||||
|
const { neType, neId } = modalState.from;
|
||||||
|
listNeConfigBackup({
|
||||||
|
neType,
|
||||||
|
neId,
|
||||||
|
name,
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
}).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||||
|
importState.backupData = [];
|
||||||
|
res.rows.forEach((item: any) => {
|
||||||
|
importState.backupData.push({
|
||||||
|
label: item.name,
|
||||||
|
value: item.path,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**服务器备份文件选择切换 */
|
||||||
|
function backupChange(value: any) {
|
||||||
|
if (!value) {
|
||||||
|
backupSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**类型切换 */
|
||||||
|
function typeChange(value: any) {
|
||||||
|
modalState.from.path = undefined;
|
||||||
|
if (value === 'backup') {
|
||||||
|
backupSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**对话框对象信息状态类型 */
|
||||||
|
type ModalStateType = {
|
||||||
|
/**新增框或修改框是否显示 */
|
||||||
|
openByEdit: boolean;
|
||||||
|
/**标题 */
|
||||||
|
title: string;
|
||||||
|
/**表单数据 */
|
||||||
|
from: {
|
||||||
|
neType: string;
|
||||||
|
neId: string;
|
||||||
|
type: 'upload' | 'backup';
|
||||||
|
path: string | undefined;
|
||||||
|
};
|
||||||
|
/**确定按钮 loading */
|
||||||
|
confirmLoading: boolean;
|
||||||
|
/**上传文件 */
|
||||||
|
uploadFiles: any[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**对话框对象信息状态 */
|
||||||
|
let modalState: ModalStateType = reactive({
|
||||||
|
openByEdit: false,
|
||||||
|
title: '配置文件导入',
|
||||||
|
from: {
|
||||||
|
neType: '',
|
||||||
|
neId: '',
|
||||||
|
type: 'upload',
|
||||||
|
path: undefined,
|
||||||
|
},
|
||||||
|
confirmLoading: false,
|
||||||
|
uploadFiles: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**对话框内表单属性和校验规则 */
|
||||||
|
const modalStateFrom = Form.useForm(
|
||||||
|
modalState.from,
|
||||||
|
reactive({
|
||||||
|
path: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.ne.neInfo.backConf.pathPlease'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出确认执行函数
|
||||||
|
* 进行表达规则校验
|
||||||
|
*/
|
||||||
|
function fnModalOk() {
|
||||||
|
if (modalState.confirmLoading) return;
|
||||||
|
const from = toRaw(modalState.from);
|
||||||
|
modalStateFrom
|
||||||
|
.validate()
|
||||||
|
.then(e => {
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
importNeConfigBackup(from)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success(t('common.operateOk'), 3);
|
||||||
|
// 返回无引用信息
|
||||||
|
emit('ok', JSON.parse(JSON.stringify(from)));
|
||||||
|
fnModalCancel();
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出关闭执行函数
|
||||||
|
* 进行表达规则校验
|
||||||
|
*/
|
||||||
|
function fnModalCancel() {
|
||||||
|
modalState.openByEdit = false;
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
modalStateFrom.resetFields();
|
||||||
|
modalState.uploadFiles = [];
|
||||||
|
emit('cancel');
|
||||||
|
emit('update:open', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表单上传前删除 */
|
||||||
|
function fnBeforeRemoveFile(file: UploadFile) {
|
||||||
|
modalState.from.path = undefined;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表单上传前检查或转换压缩 */
|
||||||
|
function fnBeforeUploadFile(file: FileType) {
|
||||||
|
if (modalState.confirmLoading) return false;
|
||||||
|
if (!file.name.endsWith('.zip')) {
|
||||||
|
const msg = `${t('components.UploadModal.onlyAllow')} .zip`;
|
||||||
|
message.error(msg, 3);
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
}
|
||||||
|
const isLt3M = file.size / 1024 / 1024 < 100;
|
||||||
|
if (!isLt3M) {
|
||||||
|
const msg = `${t('components.UploadModal.allowFilter')} 100MB`;
|
||||||
|
message.error(msg, 3);
|
||||||
|
return Upload.LIST_IGNORE;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表单上传文件 */
|
||||||
|
function fnUploadFile(up: UploadRequestOption) {
|
||||||
|
// 发送请求
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append('file', up.file);
|
||||||
|
formData.append('subPath', 'import');
|
||||||
|
uploadFile(formData)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
// 改为完成状态
|
||||||
|
const file = modalState.uploadFiles[0];
|
||||||
|
file.percent = 100;
|
||||||
|
file.status = 'done';
|
||||||
|
// 预置到表单
|
||||||
|
const { fileName } = res.data;
|
||||||
|
modalState.from.path = fileName;
|
||||||
|
} else {
|
||||||
|
message.error(res.msg, 3);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**监听是否显示,初始数据 */
|
||||||
|
watch(
|
||||||
|
() => props.open,
|
||||||
|
val => {
|
||||||
|
if (val) {
|
||||||
|
if (props.neType && props.neId) {
|
||||||
|
modalState.from.neType = props.neType;
|
||||||
|
modalState.from.neId = props.neId;
|
||||||
|
modalState.title = t('views.ne.neInfo.backConf.title');
|
||||||
|
modalState.openByEdit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元导出配置
|
||||||
|
* @param row 网元编号ID
|
||||||
|
*/
|
||||||
|
function fnExportConf(neType: string, neId: string) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.ne.neInfo.backConf.exportTip'),
|
||||||
|
onOk() {
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
exportNeConfigBackup({ neType, neId })
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
notification.success({
|
||||||
|
message: t('common.tipTitle'),
|
||||||
|
description: t('views.ne.neInfo.backConf.exportMsg'),
|
||||||
|
});
|
||||||
|
saveAs(
|
||||||
|
res.data,
|
||||||
|
`${neType}_${neId}_config_backup_${Date.now()}.zip`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
message.error(`${res.msg}`, 3);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 给组件设置属性 ref="xxxBackConf"
|
||||||
|
// setup内使用 const xxxBackConf = ref();
|
||||||
|
defineExpose({
|
||||||
|
/**导出文件 */
|
||||||
|
exportConf: fnExportConf,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ProModal
|
||||||
|
:drag="true"
|
||||||
|
:width="800"
|
||||||
|
:keyboard="false"
|
||||||
|
:mask-closable="false"
|
||||||
|
:open="modalState.openByEdit"
|
||||||
|
:title="modalState.title"
|
||||||
|
:confirm-loading="modalState.confirmLoading"
|
||||||
|
@ok="fnModalOk"
|
||||||
|
@cancel="fnModalCancel"
|
||||||
|
>
|
||||||
|
<a-form name="modalStateFrom" layout="horizontal" :label-col="{ span: 6 }">
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item :label="t('views.ne.common.neType')" name="neType">
|
||||||
|
{{ modalState.from.neType }}
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.backConf.importType')"
|
||||||
|
name="type"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="modalState.from.type"
|
||||||
|
default-value="server"
|
||||||
|
:options="importState.typeOption"
|
||||||
|
@change="typeChange"
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item :label="t('views.ne.common.neId')" name="neId">
|
||||||
|
{{ modalState.from.neId }}
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.backConf.server')"
|
||||||
|
name="fileName"
|
||||||
|
v-bind="modalStateFrom.validateInfos.path"
|
||||||
|
v-if="modalState.from.type === 'backup'"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="modalState.from.path"
|
||||||
|
:options="importState.backupData"
|
||||||
|
:placeholder="t('common.selectPlease')"
|
||||||
|
:show-search="true"
|
||||||
|
:default-active-first-option="false"
|
||||||
|
:show-arrow="false"
|
||||||
|
:allow-clear="true"
|
||||||
|
:filter-option="false"
|
||||||
|
:not-found-content="null"
|
||||||
|
@search="backupSearch"
|
||||||
|
@change="backupChange"
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.backConf.local')"
|
||||||
|
name="file"
|
||||||
|
v-bind="modalStateFrom.validateInfos.path"
|
||||||
|
v-if="modalState.from.type === 'upload'"
|
||||||
|
>
|
||||||
|
<a-upload
|
||||||
|
name="file"
|
||||||
|
v-model:file-list="modalState.uploadFiles"
|
||||||
|
accept=".zip"
|
||||||
|
list-type="text"
|
||||||
|
:max-count="1"
|
||||||
|
:show-upload-list="{
|
||||||
|
showPreviewIcon: false,
|
||||||
|
showRemoveIcon: true,
|
||||||
|
showDownloadIcon: false,
|
||||||
|
}"
|
||||||
|
:remove="fnBeforeRemoveFile"
|
||||||
|
:before-upload="fnBeforeUploadFile"
|
||||||
|
:custom-request="fnUploadFile"
|
||||||
|
:disabled="modalState.confirmLoading"
|
||||||
|
>
|
||||||
|
<a-button type="primary">
|
||||||
|
<template #icon>
|
||||||
|
<UploadOutlined />
|
||||||
|
</template>
|
||||||
|
{{ t('views.ne.neInfo.backConf.localUpload') }}
|
||||||
|
</a-button>
|
||||||
|
</a-upload>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</ProModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped></style>
|
||||||
834
practical_training/views/ne/neInfo/components/EditModal.vue
Normal file
@@ -0,0 +1,834 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, onMounted, toRaw, watch } from 'vue';
|
||||||
|
import { ProModal } from 'antdv-pro-modal';
|
||||||
|
import { message, Form, Modal } from 'ant-design-vue/es';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { NE_TYPE_LIST } from '@/constants/ne-constants';
|
||||||
|
import { regExpIPv4, regExpIPv6 } from '@/utils/regular-utils';
|
||||||
|
import { getNeInfo, addNeInfo, updateNeInfo } from '@/api/ne/neInfo';
|
||||||
|
import { neHostAuthorizedRSA, testNeHost } from '@/api/ne/neHost';
|
||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
const { getDict } = useDictStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const emit = defineEmits(['ok', 'cancel', 'update:open']);
|
||||||
|
const props = defineProps({
|
||||||
|
open: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
editId: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**字典数据 */
|
||||||
|
let dict: {
|
||||||
|
/**主机类型 */
|
||||||
|
neHostType: DictType[];
|
||||||
|
/**分组 */
|
||||||
|
neHostGroupId: DictType[];
|
||||||
|
/**认证模式 */
|
||||||
|
neHostAuthMode: DictType[];
|
||||||
|
} = reactive({
|
||||||
|
neHostType: [],
|
||||||
|
neHostGroupId: [],
|
||||||
|
neHostAuthMode: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试主机连接
|
||||||
|
*/
|
||||||
|
function fnHostTest(row: Record<string, any>) {
|
||||||
|
if (modalState.confirmLoading || !row.addr || !row.port) return;
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
testNeHost(row)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success({
|
||||||
|
content: `${row.addr}:${row.port} ${t('views.ne.neHost.testOk')}`,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${row.addr}:${row.port} ${res.msg}`,
|
||||||
|
duration: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**测试主机连接-免密直连 */
|
||||||
|
function fnHostAuthorized(row: Record<string, any>) {
|
||||||
|
if (modalState.confirmLoading) return;
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.ne.neHost.authRSATip'),
|
||||||
|
onOk: () => {
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
neHostAuthorizedRSA(row).then(res => {
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success(t('common.operateOk'), 3);
|
||||||
|
} else {
|
||||||
|
message.error(t('common.operateErr'), 3);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**对话框对象信息状态类型 */
|
||||||
|
type ModalStateType = {
|
||||||
|
/**新增框或修改框是否显示 */
|
||||||
|
openByEdit: boolean;
|
||||||
|
/**标题 */
|
||||||
|
title: string;
|
||||||
|
/**表单数据 */
|
||||||
|
from: Record<string, any>;
|
||||||
|
/**确定按钮 loading */
|
||||||
|
confirmLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**对话框对象信息状态 */
|
||||||
|
let modalState: ModalStateType = reactive({
|
||||||
|
openByEdit: false,
|
||||||
|
title: '网元',
|
||||||
|
from: {
|
||||||
|
id: undefined,
|
||||||
|
neId: '001',
|
||||||
|
neType: 'AMF',
|
||||||
|
neName: '',
|
||||||
|
ip: '',
|
||||||
|
port: 33030,
|
||||||
|
pvFlag: 'PNF',
|
||||||
|
rmUid: '4400HXAMF001',
|
||||||
|
neAddress: '',
|
||||||
|
dn: '',
|
||||||
|
vendorName: '',
|
||||||
|
province: '',
|
||||||
|
remark: '',
|
||||||
|
// 主机
|
||||||
|
hosts: [
|
||||||
|
{
|
||||||
|
hostId: undefined,
|
||||||
|
hostType: 'ssh',
|
||||||
|
groupId: '1',
|
||||||
|
title: 'SSH_NE_22',
|
||||||
|
addr: '',
|
||||||
|
port: 22,
|
||||||
|
user: 'omcuser',
|
||||||
|
authMode: '2',
|
||||||
|
password: '',
|
||||||
|
privateKey: '',
|
||||||
|
passPhrase: '',
|
||||||
|
remark: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hostId: undefined,
|
||||||
|
hostType: 'telnet',
|
||||||
|
groupId: '1',
|
||||||
|
title: 'Telnet_NE_4100',
|
||||||
|
addr: '',
|
||||||
|
port: 4100,
|
||||||
|
user: 'admin',
|
||||||
|
authMode: '0',
|
||||||
|
password: 'admin',
|
||||||
|
remark: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
confirmLoading: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**对话框内表单属性和校验规则 */
|
||||||
|
const modalStateFrom = Form.useForm(
|
||||||
|
modalState.from,
|
||||||
|
reactive({
|
||||||
|
neType: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.ne.common.neTypePlease'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
neId: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.ne.common.neIdPlease'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rmUid: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.ne.common.rmUidPlease'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ip: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: modalStateFromEqualIPV4AndIPV6,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
neName: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.ne.common.neNamePlease'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
/**表单验证IP地址是否有效 */
|
||||||
|
function modalStateFromEqualIPV4AndIPV6(
|
||||||
|
rule: Record<string, any>,
|
||||||
|
value: string,
|
||||||
|
callback: (error?: string) => void
|
||||||
|
) {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject(t('views.ne.common.ipAddrPlease'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.indexOf('.') === -1 && value.indexOf(':') === -1) {
|
||||||
|
return Promise.reject(t('valid.ipPlease'));
|
||||||
|
}
|
||||||
|
if (value.indexOf('.') !== -1 && !regExpIPv4.test(value)) {
|
||||||
|
return Promise.reject(t('valid.ipv4Reg'));
|
||||||
|
}
|
||||||
|
if (value.indexOf(':') !== -1 && !regExpIPv6.test(value)) {
|
||||||
|
return Promise.reject(t('valid.ipv6Reg'));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出显示为 新增或者修改
|
||||||
|
* @param editId 网元id, 不传为新增
|
||||||
|
*/
|
||||||
|
function fnModalVisibleByEdit(editId: string) {
|
||||||
|
if (!editId) {
|
||||||
|
modalStateFrom.resetFields();
|
||||||
|
modalState.title = t('views.ne.neInfo.addTitle');
|
||||||
|
modalState.openByEdit = true;
|
||||||
|
} else {
|
||||||
|
if (modalState.confirmLoading) return;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
getNeInfo(editId).then(res => {
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
hide();
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
Object.assign(modalState.from, res.data);
|
||||||
|
modalState.title = t('views.ne.neInfo.editTitle');
|
||||||
|
modalState.openByEdit = true;
|
||||||
|
} else {
|
||||||
|
message.error(t('common.getInfoFail'), 2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出确认执行函数
|
||||||
|
* 进行表达规则校验
|
||||||
|
*/
|
||||||
|
function fnModalOk() {
|
||||||
|
modalStateFrom
|
||||||
|
.validate()
|
||||||
|
.then(e => {
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
const from = toRaw(modalState.from);
|
||||||
|
const result = from.id ? updateNeInfo(from) : addNeInfo(from);
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
result
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success(t('common.operateOk'), 3);
|
||||||
|
// 返回无引用信息
|
||||||
|
emit('ok', JSON.parse(JSON.stringify(from)));
|
||||||
|
fnModalCancel();
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出关闭执行函数
|
||||||
|
* 进行表达规则校验
|
||||||
|
*/
|
||||||
|
function fnModalCancel() {
|
||||||
|
modalState.openByEdit = false;
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
modalStateFrom.resetFields();
|
||||||
|
emit('cancel');
|
||||||
|
emit('update:open', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表单修改网元类型 */
|
||||||
|
function fnNeTypeChange(v: any) {
|
||||||
|
// 网元默认只含22和4100
|
||||||
|
if (modalState.from.hosts.length === 3) {
|
||||||
|
modalState.from.hosts.pop();
|
||||||
|
}
|
||||||
|
const hostsLen = modalState.from.hosts.length;
|
||||||
|
// UPF标准版本可支持5002
|
||||||
|
if (hostsLen === 2 && v === 'UPF') {
|
||||||
|
modalState.from.hosts.push({
|
||||||
|
hostId: undefined,
|
||||||
|
hostType: 'telnet',
|
||||||
|
groupId: '1',
|
||||||
|
title: 'Telnet_NE_5002',
|
||||||
|
addr: modalState.from.ip,
|
||||||
|
port: 5002,
|
||||||
|
user: 'admin',
|
||||||
|
authMode: '0',
|
||||||
|
password: 'admin',
|
||||||
|
remark: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// UDM可支持6379
|
||||||
|
if (hostsLen === 2 && v === 'UDM') {
|
||||||
|
modalState.from.hosts.push({
|
||||||
|
hostId: undefined,
|
||||||
|
hostType: 'redis',
|
||||||
|
groupId: '1',
|
||||||
|
title: 'REDIS_NE_6379',
|
||||||
|
addr: modalState.from.ip,
|
||||||
|
port: 6379,
|
||||||
|
user: 'udmdb',
|
||||||
|
authMode: '0',
|
||||||
|
password: 'helloearth',
|
||||||
|
dbName: '0',
|
||||||
|
remark: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
modalState.from.rmUid = `4400HX${v}${modalState.from.neId}`; // 4400HX1AMF001
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表单修改网元neId */
|
||||||
|
function fnNeIdChange(e: any) {
|
||||||
|
const v = e.target.value;
|
||||||
|
if (v.length < 1) return;
|
||||||
|
modalState.from.rmUid = `4400HX${modalState.from.neType}${v}`; // 4400HX1AMF001
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表单修改网元IP */
|
||||||
|
function fnNeIPChange(e: any) {
|
||||||
|
const v = e.target.value;
|
||||||
|
if (v.length < 7) return;
|
||||||
|
for (const host of modalState.from.hosts) {
|
||||||
|
host.addr = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**监听是否显示,初始数据 */
|
||||||
|
watch(
|
||||||
|
() => props.open,
|
||||||
|
val => {
|
||||||
|
if (val) fnModalVisibleByEdit(props.editId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始字典数据
|
||||||
|
Promise.allSettled([
|
||||||
|
getDict('ne_host_type'),
|
||||||
|
getDict('ne_host_groupId'),
|
||||||
|
getDict('ne_host_authMode'),
|
||||||
|
]).then(resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
dict.neHostType = resArr[0].value;
|
||||||
|
}
|
||||||
|
if (resArr[1].status === 'fulfilled') {
|
||||||
|
dict.neHostGroupId = resArr[1].value;
|
||||||
|
}
|
||||||
|
if (resArr[2].status === 'fulfilled') {
|
||||||
|
dict.neHostAuthMode = resArr[2].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ProModal
|
||||||
|
:drag="true"
|
||||||
|
:width="800"
|
||||||
|
:destroyOnClose="true"
|
||||||
|
:body-style="{ maxHeight: '600px', 'overflow-y': 'auto' }"
|
||||||
|
:keyboard="false"
|
||||||
|
:mask-closable="false"
|
||||||
|
:open="modalState.openByEdit"
|
||||||
|
:title="modalState.title"
|
||||||
|
:confirm-loading="modalState.confirmLoading"
|
||||||
|
@ok="fnModalOk"
|
||||||
|
@cancel="fnModalCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
name="modalStateFrom"
|
||||||
|
layout="horizontal"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:labelWrap="true"
|
||||||
|
>
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.common.neType')"
|
||||||
|
name="neType"
|
||||||
|
v-bind="modalStateFrom.validateInfos.neType"
|
||||||
|
>
|
||||||
|
<a-auto-complete
|
||||||
|
v-model:value="modalState.from.neType"
|
||||||
|
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
|
||||||
|
@change="fnNeTypeChange"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
:maxlength="32"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<a-tooltip placement="topLeft">
|
||||||
|
<template #title>
|
||||||
|
{{ t('views.ne.common.neTypeTip') }}
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-auto-complete>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.pvflag')"
|
||||||
|
name="pvFlag"
|
||||||
|
v-bind="modalStateFrom.validateInfos.pvFlag"
|
||||||
|
>
|
||||||
|
<a-select
|
||||||
|
v-model:value="modalState.from.pvFlag"
|
||||||
|
default-value="PNF"
|
||||||
|
>
|
||||||
|
<a-select-opt-group :label="t('views.ne.neInfo.pnf')">
|
||||||
|
<a-select-option value="PNF">PNF</a-select-option>
|
||||||
|
</a-select-opt-group>
|
||||||
|
<a-select-opt-group :label="t('views.ne.neInfo.vnf')">
|
||||||
|
<a-select-option value="VNF">VNF</a-select-option>
|
||||||
|
</a-select-opt-group>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.common.neId')"
|
||||||
|
name="neId"
|
||||||
|
v-bind="modalStateFrom.validateInfos.neId"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="modalState.from.neId"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
:maxlength="32"
|
||||||
|
@change="fnNeIdChange"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<a-tooltip placement="topLeft">
|
||||||
|
<template #title>
|
||||||
|
{{ t('views.ne.common.neIdTip') }}
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.common.neName')"
|
||||||
|
name="neName"
|
||||||
|
v-bind="modalStateFrom.validateInfos.neName"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="modalState.from.neName"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
:maxlength="64"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.common.ipAddr')"
|
||||||
|
name="ip"
|
||||||
|
v-bind="modalStateFrom.validateInfos.ip"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="modalState.from.ip"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
:maxlength="128"
|
||||||
|
@change="fnNeIPChange"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<a-tooltip placement="topLeft">
|
||||||
|
<template #title>
|
||||||
|
<div>
|
||||||
|
{{ t('views.ne.common.ipAddrTip') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.common.port')"
|
||||||
|
name="port"
|
||||||
|
v-bind="modalStateFrom.validateInfos.port"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="modalState.from.port"
|
||||||
|
style="width: 100%"
|
||||||
|
:min="1"
|
||||||
|
:max="65535"
|
||||||
|
:maxlength="5"
|
||||||
|
placeholder="<=65535"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<a-tooltip placement="topLeft">
|
||||||
|
<template #title>
|
||||||
|
<div>{{ t('views.ne.common.portTip') }}</div>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.common.rmUid')"
|
||||||
|
name="rmUid"
|
||||||
|
v-bind="modalStateFrom.validateInfos.rmUid"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:labelWrap="true"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="modalState.from.rmUid"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
:maxlength="40"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<a-tooltip placement="topLeft">
|
||||||
|
<template #title>
|
||||||
|
<div>
|
||||||
|
{{ t('views.ne.common.rmUidTip') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item :label="t('views.ne.neInfo.neAddress')" name="neAddress">
|
||||||
|
<a-input
|
||||||
|
v-model:value="modalState.from.neAddress"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
:maxlength="64"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<a-tooltip placement="topLeft">
|
||||||
|
<template #title>
|
||||||
|
<div>{{ t('views.ne.neInfo.neAddressTip') }}</div>
|
||||||
|
</template>
|
||||||
|
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item :label="t('views.ne.neInfo.dn')" name="dn">
|
||||||
|
<a-input
|
||||||
|
v-model:value="modalState.from.dn"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
:maxlength="255"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.vendorName')"
|
||||||
|
name="vendorName"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="modalState.from.vendorName"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
:maxlength="64"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item :label="t('views.ne.neInfo.province')" name="province">
|
||||||
|
<a-input
|
||||||
|
v-model:value="modalState.from.province"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
:maxlength="32"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
:label="t('common.remark')"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="modalState.from.remark"
|
||||||
|
:auto-size="{ minRows: 1, maxRows: 6 }"
|
||||||
|
:maxlength="450"
|
||||||
|
:show-count="true"
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 主机连接配置 -->
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.ne.neInfo.hostConfig') }}
|
||||||
|
</a-divider>
|
||||||
|
<a-collapse class="collapse" ghost>
|
||||||
|
<a-collapse-panel
|
||||||
|
v-for="host in modalState.from.hosts.filter(
|
||||||
|
(s:any) => !(s.hostType === 'telnet' && modalState.from.neType === 'OMC')
|
||||||
|
)"
|
||||||
|
:key="host.title"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<span v-if="host.hostType === 'redis'"> DB {{ host.port }} </span>
|
||||||
|
<span v-else>
|
||||||
|
{{ `${host.hostType.toUpperCase()} ${host.port}` }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item :label="t('views.ne.neHost.addr')">
|
||||||
|
<a-input
|
||||||
|
v-model:value="host.addr"
|
||||||
|
allow-clear
|
||||||
|
:maxlength="128"
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neHost.port')"
|
||||||
|
name="neHost.port"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="host.port"
|
||||||
|
:min="10"
|
||||||
|
:max="65535"
|
||||||
|
:step="1"
|
||||||
|
:maxlength="5"
|
||||||
|
style="width: 100%"
|
||||||
|
></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
v-if="host.hostType === 'telnet'"
|
||||||
|
:label="t('views.ne.neHost.user')"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="host.user"
|
||||||
|
allow-clear
|
||||||
|
:maxlength="32"
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-row v-if="host.hostType === 'ssh'">
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item :label="t('views.ne.neHost.user')">
|
||||||
|
<a-input
|
||||||
|
v-model:value="host.user"
|
||||||
|
allow-clear
|
||||||
|
:maxlength="32"
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item :label="t('views.ne.neHost.authMode')">
|
||||||
|
<a-select
|
||||||
|
v-model:value="host.authMode"
|
||||||
|
default-value="0"
|
||||||
|
:options="dict.neHostAuthMode"
|
||||||
|
>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
v-if="host.authMode === '0'"
|
||||||
|
:label="t('views.ne.neHost.password')"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="host.password"
|
||||||
|
:maxlength="128"
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
>
|
||||||
|
</a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="host.authMode === '1'">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neHost.privateKey')"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="host.privateKey"
|
||||||
|
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||||
|
:maxlength="3000"
|
||||||
|
:show-count="true"
|
||||||
|
:placeholder="t('views.ne.neHost.privateKeyPlease')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neHost.passPhrase')"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="host.passPhrase"
|
||||||
|
:maxlength="128"
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
>
|
||||||
|
</a-input-password>
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
v-if="host.hostType === 'mysql'"
|
||||||
|
:label="t('views.ne.neHost.database')"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="host.dbName"
|
||||||
|
allow-clear
|
||||||
|
:maxlength="32"
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
>
|
||||||
|
</a-input>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-form-item
|
||||||
|
:label="t('common.remark')"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-textarea
|
||||||
|
v-model:value="host.remark"
|
||||||
|
:auto-size="{ minRows: 1, maxRows: 6 }"
|
||||||
|
:maxlength="450"
|
||||||
|
:show-count="true"
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 测试 -->
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neHost.test')"
|
||||||
|
name="test"
|
||||||
|
:label-col="{ span: 3 }"
|
||||||
|
:label-wrap="true"
|
||||||
|
>
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
shape="round"
|
||||||
|
@click="fnHostTest(host)"
|
||||||
|
:loading="modalState.confirmLoading"
|
||||||
|
>
|
||||||
|
<template #icon><LinkOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
@click="fnHostAuthorized(host)"
|
||||||
|
:loading="modalState.confirmLoading"
|
||||||
|
v-if="host.hostType === 'ssh' && host.authMode !== '2'"
|
||||||
|
>
|
||||||
|
{{ t('views.ne.neHost.authRSA') }}
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
|
</ProModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.collapse :deep(.ant-collapse-item) > .ant-collapse-header {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.collapse-header {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
322
practical_training/views/ne/neInfo/components/OAMModal.vue
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, toRaw, watch } from 'vue';
|
||||||
|
import { ProModal } from 'antdv-pro-modal';
|
||||||
|
import { message, Form } from 'ant-design-vue/es';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { getOAMFile, saveOAMFile } from '@/api/ne/neInfo';
|
||||||
|
const { t } = useI18n();
|
||||||
|
const emit = defineEmits(['ok', 'cancel', 'update:open']);
|
||||||
|
const props = defineProps({
|
||||||
|
open: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/**网元ID */
|
||||||
|
neId: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
neType: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**对话框对象信息状态类型 */
|
||||||
|
type ModalStateType = {
|
||||||
|
/**新增框或修改框是否显示 */
|
||||||
|
openByEdit: boolean;
|
||||||
|
/**标题 */
|
||||||
|
title: string;
|
||||||
|
/**是否同步 */
|
||||||
|
sync: boolean;
|
||||||
|
/**表单数据 */
|
||||||
|
from: Record<string, any>;
|
||||||
|
/**确定按钮 loading */
|
||||||
|
confirmLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**对话框对象信息状态 */
|
||||||
|
let modalState: ModalStateType = reactive({
|
||||||
|
openByEdit: false,
|
||||||
|
title: 'OAM Configuration',
|
||||||
|
sync: true,
|
||||||
|
from: {
|
||||||
|
omcIP: '',
|
||||||
|
oamEnable: true,
|
||||||
|
oamPort: 33030,
|
||||||
|
snmpEnable: true,
|
||||||
|
snmpPort: 4957,
|
||||||
|
kpiEnable: true,
|
||||||
|
kpiTimer: 60,
|
||||||
|
},
|
||||||
|
confirmLoading: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**对话框内表单属性和校验规则 */
|
||||||
|
const modalStateFrom = Form.useForm(
|
||||||
|
modalState.from,
|
||||||
|
reactive({
|
||||||
|
kpiTimer: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('views.ne.neInfo.oam.kpiTimerPlease'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出显示为 新增或者修改
|
||||||
|
* @param neType 网元类型
|
||||||
|
* @param neId 网元ID
|
||||||
|
*/
|
||||||
|
function fnModalVisibleByTypeAndId(neType: string, neId: string) {
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
getOAMFile(neType, neId)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
const data = res.data;
|
||||||
|
Object.assign(modalState.from, {
|
||||||
|
omcIP: data.oamConfig[data.oamConfig.ipType],
|
||||||
|
oamEnable: data.oamConfig.enable,
|
||||||
|
oamPort: data.oamConfig.port,
|
||||||
|
snmpEnable: data.snmpConfig.enable,
|
||||||
|
snmpPort: data.snmpConfig.port,
|
||||||
|
kpiEnable: data.kpiConfig.enable,
|
||||||
|
kpiTimer: data.kpiConfig.timer,
|
||||||
|
});
|
||||||
|
modalState.title = t('views.ne.neInfo.oam.title');
|
||||||
|
modalState.openByEdit = true;
|
||||||
|
} else {
|
||||||
|
message.error(res.msg, 3);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出确认执行函数
|
||||||
|
* 进行表达规则校验
|
||||||
|
*/
|
||||||
|
function fnModalOk() {
|
||||||
|
modalStateFrom
|
||||||
|
.validate()
|
||||||
|
.then(e => {
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
const from = toRaw(modalState.from);
|
||||||
|
saveOAMFile({
|
||||||
|
neType: props.neType,
|
||||||
|
neId: props.neId,
|
||||||
|
content: from,
|
||||||
|
sync: modalState.sync,
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success(t('common.operateOk'), 3);
|
||||||
|
emit('ok');
|
||||||
|
fnModalCancel();
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出关闭执行函数
|
||||||
|
* 进行表达规则校验
|
||||||
|
*/
|
||||||
|
function fnModalCancel() {
|
||||||
|
modalState.openByEdit = false;
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
modalStateFrom.resetFields();
|
||||||
|
emit('cancel');
|
||||||
|
emit('update:open', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**监听是否显示,初始数据 */
|
||||||
|
watch(
|
||||||
|
() => props.open,
|
||||||
|
val => {
|
||||||
|
if (val) {
|
||||||
|
if (props.neType && props.neId) {
|
||||||
|
fnModalVisibleByTypeAndId(props.neType, props.neId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ProModal
|
||||||
|
:drag="true"
|
||||||
|
:destroyOnClose="true"
|
||||||
|
:body-style="{ maxHeight: '600px', 'overflow-y': 'auto' }"
|
||||||
|
:keyboard="false"
|
||||||
|
:mask-closable="false"
|
||||||
|
:open="modalState.openByEdit"
|
||||||
|
:title="modalState.title"
|
||||||
|
:confirm-loading="modalState.confirmLoading"
|
||||||
|
@ok="fnModalOk"
|
||||||
|
@cancel="fnModalCancel"
|
||||||
|
>
|
||||||
|
<a-form
|
||||||
|
name="modalStateFrom"
|
||||||
|
layout="horizontal"
|
||||||
|
:label-col="{ span: 12 }"
|
||||||
|
:labelWrap="true"
|
||||||
|
>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.oam.sync')"
|
||||||
|
name="sync"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:labelWrap="true"
|
||||||
|
>
|
||||||
|
<a-switch
|
||||||
|
:checked-children="t('common.switch.open')"
|
||||||
|
:un-checked-children="t('common.switch.shut')"
|
||||||
|
v-model:checked="modalState.sync"
|
||||||
|
></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<a-collapse class="collapse" ghost>
|
||||||
|
<a-collapse-panel header="OAM">
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.oam.oamEnable')"
|
||||||
|
name="oamEnable"
|
||||||
|
>
|
||||||
|
<a-switch
|
||||||
|
:checked-children="t('common.switch.open')"
|
||||||
|
:un-checked-children="t('common.switch.shut')"
|
||||||
|
v-model:checked="modalState.from.oamEnable"
|
||||||
|
></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.oam.oamPort')"
|
||||||
|
name="oamPort"
|
||||||
|
v-bind="modalStateFrom.validateInfos.oamPort"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
:min="3000"
|
||||||
|
:max="65535"
|
||||||
|
:step="1"
|
||||||
|
:maxlength="5"
|
||||||
|
v-model:value="modalState.from.oamPort"
|
||||||
|
style="width: 100%"
|
||||||
|
></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.oam.omcIP')"
|
||||||
|
name="omcIP"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:labelWrap="true"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:value="modalState.from.omcIP"
|
||||||
|
:maxlength="128"
|
||||||
|
></a-input>
|
||||||
|
</a-form-item>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header="SNMP">
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.oam.snmpEnable')"
|
||||||
|
name="snmpEnable"
|
||||||
|
>
|
||||||
|
<a-switch
|
||||||
|
:checked-children="t('common.switch.open')"
|
||||||
|
:un-checked-children="t('common.switch.shut')"
|
||||||
|
v-model:checked="modalState.from.snmpEnable"
|
||||||
|
></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.oam.snmpPort')"
|
||||||
|
name="snmpPort"
|
||||||
|
v-bind="modalStateFrom.validateInfos.snmpPort"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
:min="3000"
|
||||||
|
:max="65535"
|
||||||
|
:step="1"
|
||||||
|
:maxlength="5"
|
||||||
|
v-model:value="modalState.from.snmpPort"
|
||||||
|
style="width: 100%"
|
||||||
|
></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-collapse-panel>
|
||||||
|
<a-collapse-panel header="KPI">
|
||||||
|
<a-row>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.oam.kpiEnable')"
|
||||||
|
name="kpiEnable"
|
||||||
|
>
|
||||||
|
<a-switch
|
||||||
|
:checked-children="t('common.switch.open')"
|
||||||
|
:un-checked-children="t('common.switch.shut')"
|
||||||
|
v-model:checked="modalState.from.kpiEnable"
|
||||||
|
></a-switch>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="12" :md="12" :xs="24">
|
||||||
|
<a-form-item
|
||||||
|
:label="t('views.ne.neInfo.oam.kpiTimer')"
|
||||||
|
name="kpiTimer"
|
||||||
|
v-bind="modalStateFrom.validateInfos.kpiTimer"
|
||||||
|
>
|
||||||
|
<a-input-number
|
||||||
|
:min="5"
|
||||||
|
:max="3600"
|
||||||
|
:step="1"
|
||||||
|
:maxlength="4"
|
||||||
|
v-model:value="modalState.from.kpiTimer"
|
||||||
|
style="width: 100%"
|
||||||
|
></a-input-number>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-collapse-panel>
|
||||||
|
</a-collapse>
|
||||||
|
</a-form>
|
||||||
|
</ProModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.collapse :deep(.ant-collapse-item) > .ant-collapse-header {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
.collapse-header {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
153
practical_training/views/ne/neInfo/hooks/useNeOptions.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { Modal, message } from 'ant-design-vue/es';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { updateNeConfigReload } from '@/api/configManage/configParam';
|
||||||
|
import { serviceNeAction } from '@/api/ne/neInfo';
|
||||||
|
import useMaskStore from '@/store/modules/mask';
|
||||||
|
|
||||||
|
export default function useNeOptions() {
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const maskStore = useMaskStore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元启动
|
||||||
|
* @param row {neName,neType,neId}
|
||||||
|
*/
|
||||||
|
function fnNeStart(row: Record<string, any>) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.ne.common.startTip'),
|
||||||
|
onOk() {
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
serviceNeAction({
|
||||||
|
neType: row.neType,
|
||||||
|
neId: row.neId,
|
||||||
|
action: 'start',
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success(t('common.operateOk'), 3);
|
||||||
|
} else {
|
||||||
|
message.error(`${res.msg}`, 3);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元重启
|
||||||
|
* @param row {neName,neType,neId}
|
||||||
|
*/
|
||||||
|
function fnNeRestart(row: Record<string, any>) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.ne.common.restartTip'),
|
||||||
|
onOk() {
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
serviceNeAction({
|
||||||
|
neType: row.neType,
|
||||||
|
neId: row.neId,
|
||||||
|
action: 'restart',
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
// OMC自升级
|
||||||
|
if (row.neType.toUpperCase() === 'OMC') {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
maskStore.handleMaskType('reload');
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.success(t('common.operateOk'), 3);
|
||||||
|
} else {
|
||||||
|
message.error(`${res.msg}`, 3);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元停止
|
||||||
|
* @param row {neName,neType,neId}
|
||||||
|
*/
|
||||||
|
function fnNeStop(row: Record<string, any>) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.ne.common.stopTip'),
|
||||||
|
onOk() {
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
serviceNeAction({
|
||||||
|
neType: row.neType,
|
||||||
|
neId: row.neId,
|
||||||
|
action: 'stop',
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success(t('common.operateOk'), 3);
|
||||||
|
} else {
|
||||||
|
message.error(`${res.msg}`, 3);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元重新加载
|
||||||
|
* @param row {neName,neType,neId}
|
||||||
|
*/
|
||||||
|
function fnNeReload(row: Record<string, any>) {
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: t('views.ne.common.reloadTip'),
|
||||||
|
onOk() {
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
updateNeConfigReload(row.neType, row.neId)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success(t('common.operateOk'), 3);
|
||||||
|
} else {
|
||||||
|
message.error(`${res.msg}`, 3);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转网元日志文件页面
|
||||||
|
* @param row {neType,neId}
|
||||||
|
*/
|
||||||
|
function fnNeLogFile(row: Record<string, any>) {
|
||||||
|
router.push({
|
||||||
|
name: 'NeFile_2123',
|
||||||
|
query: {
|
||||||
|
neType: row.neType,
|
||||||
|
neId: row.neId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { fnNeStart, fnNeRestart, fnNeStop, fnNeReload, fnNeLogFile };
|
||||||
|
}
|
||||||
790
practical_training/views/ne/neInfo/index.vue
Normal file
@@ -0,0 +1,790 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, onMounted, toRaw, defineAsyncComponent, ref } from 'vue';
|
||||||
|
import { PageContainer } from 'antdv-pro-layout';
|
||||||
|
import { message, Modal } from 'ant-design-vue/es';
|
||||||
|
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||||
|
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||||
|
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||||
|
import useI18n from '@/hooks/useI18n';
|
||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import useNeInfoStore from '@/store/modules/neinfo';
|
||||||
|
import { listNeInfo, delNeInfo, stateNeInfo } from '@/api/ne/neInfo';
|
||||||
|
import { NE_TYPE_LIST } from '@/constants/ne-constants';
|
||||||
|
import { hasRoles } from '@/plugins/auth-user';
|
||||||
|
import useDictStore from '@/store/modules/dict';
|
||||||
|
import useNeOptions from './hooks/useNeOptions';
|
||||||
|
const { getDict } = useDictStore();
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { fnNeStart, fnNeRestart, fnNeStop, fnNeReload, fnNeLogFile } =
|
||||||
|
useNeOptions();
|
||||||
|
// 异步加载组件
|
||||||
|
const EditModal = defineAsyncComponent(
|
||||||
|
() => import('./components/EditModal.vue')
|
||||||
|
);
|
||||||
|
const OAMModal = defineAsyncComponent(
|
||||||
|
() => import('./components/OAMModal.vue')
|
||||||
|
);
|
||||||
|
// 配置备份文件导入
|
||||||
|
const BackConfModal = defineAsyncComponent(
|
||||||
|
() => import('./components/BackConfModal.vue')
|
||||||
|
);
|
||||||
|
const backConf = ref(); // 引用句柄,取导出函数
|
||||||
|
|
||||||
|
/**字典数据 */
|
||||||
|
let dict: {
|
||||||
|
/**网元信息状态 */
|
||||||
|
neInfoStatus: DictType[];
|
||||||
|
} = reactive({
|
||||||
|
neInfoStatus: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**查询参数 */
|
||||||
|
let queryParams = reactive({
|
||||||
|
/**网元类型 */
|
||||||
|
neType: '',
|
||||||
|
/**带状态信息 */
|
||||||
|
bandStatus: true,
|
||||||
|
/**当前页数 */
|
||||||
|
pageNum: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**查询参数重置 */
|
||||||
|
function fnQueryReset() {
|
||||||
|
queryParams = Object.assign(queryParams, {
|
||||||
|
neType: '',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
});
|
||||||
|
tablePagination.current = 1;
|
||||||
|
tablePagination.pageSize = 20;
|
||||||
|
fnGetList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格状态类型 */
|
||||||
|
type TabeStateType = {
|
||||||
|
/**加载等待 */
|
||||||
|
loading: boolean;
|
||||||
|
/**紧凑型 */
|
||||||
|
size: SizeType;
|
||||||
|
/**搜索栏 */
|
||||||
|
seached: boolean;
|
||||||
|
/**记录数据 */
|
||||||
|
data: Record<string, any>[];
|
||||||
|
/**勾选记录 */
|
||||||
|
selectedRowKeys: (string | number)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**表格状态 */
|
||||||
|
let tableState: TabeStateType = reactive({
|
||||||
|
loading: false,
|
||||||
|
size: 'middle',
|
||||||
|
seached: false,
|
||||||
|
data: [],
|
||||||
|
selectedRowKeys: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格字段列 */
|
||||||
|
let tableColumns: ColumnsType = [
|
||||||
|
{
|
||||||
|
title: t('views.ne.common.neType'),
|
||||||
|
dataIndex: 'neType',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.ne.common.neId'),
|
||||||
|
dataIndex: 'neId',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.ne.common.rmUid'),
|
||||||
|
dataIndex: 'rmUid',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.ne.common.neName'),
|
||||||
|
dataIndex: 'neName',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.ne.common.ipAddr'),
|
||||||
|
dataIndex: 'ip',
|
||||||
|
align: 'left',
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.ne.common.port'),
|
||||||
|
dataIndex: 'port',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('views.ne.neInfo.state'),
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
align: 'left',
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t('common.operate'),
|
||||||
|
key: 'id',
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/**表格分页器参数 */
|
||||||
|
let tablePagination = reactive({
|
||||||
|
/**当前页数 */
|
||||||
|
current: 1,
|
||||||
|
/**每页条数 */
|
||||||
|
pageSize: 20,
|
||||||
|
/**默认的每页条数 */
|
||||||
|
defaultPageSize: 20,
|
||||||
|
/**指定每页可以显示多少条 */
|
||||||
|
pageSizeOptions: ['10', '20', '50', '100'],
|
||||||
|
/**只有一页时是否隐藏分页器 */
|
||||||
|
hideOnSinglePage: false,
|
||||||
|
/**是否可以快速跳转至某页 */
|
||||||
|
showQuickJumper: true,
|
||||||
|
/**是否可以改变 pageSize */
|
||||||
|
showSizeChanger: true,
|
||||||
|
/**数据总数 */
|
||||||
|
total: 0,
|
||||||
|
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||||
|
onChange: (page: number, pageSize: number) => {
|
||||||
|
tablePagination.current = page;
|
||||||
|
tablePagination.pageSize = pageSize;
|
||||||
|
queryParams.pageNum = page;
|
||||||
|
queryParams.pageSize = pageSize;
|
||||||
|
fnGetList();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**表格紧凑型变更操作 */
|
||||||
|
function fnTableSize({ key }: MenuInfo) {
|
||||||
|
tableState.size = key as SizeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**表格多选 */
|
||||||
|
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||||
|
tableState.selectedRowKeys = keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**对话框对象信息状态类型 */
|
||||||
|
type ModalStateType = {
|
||||||
|
/**配置备份框是否显示 */
|
||||||
|
openByBackConf: boolean;
|
||||||
|
/**OAM文件配置框是否显示 */
|
||||||
|
openByOAM: boolean;
|
||||||
|
/**新增框或修改框是否显示 */
|
||||||
|
openByEdit: boolean;
|
||||||
|
/**新增框或修改框ID */
|
||||||
|
editId: string;
|
||||||
|
/**OAM框网元类型ID */
|
||||||
|
neId: string;
|
||||||
|
neType: string;
|
||||||
|
/**确定按钮 loading */
|
||||||
|
confirmLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**对话框对象信息状态 */
|
||||||
|
let modalState: ModalStateType = reactive({
|
||||||
|
openByBackConf: false,
|
||||||
|
openByOAM: false,
|
||||||
|
openByEdit: false,
|
||||||
|
editId: '',
|
||||||
|
neId: '',
|
||||||
|
neType: '',
|
||||||
|
confirmLoading: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出显示为 新增或者修改
|
||||||
|
* @param noticeId 网元id, 不传为新增
|
||||||
|
*/
|
||||||
|
function fnModalVisibleByEdit(row?: Record<string, any>) {
|
||||||
|
if (!row) {
|
||||||
|
modalState.editId = '';
|
||||||
|
} else {
|
||||||
|
modalState.editId = row.id;
|
||||||
|
}
|
||||||
|
modalState.openByEdit = !modalState.openByEdit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出确认执行函数
|
||||||
|
* 进行表达规则校验
|
||||||
|
*/
|
||||||
|
function fnModalEditOk(from: Record<string, any>) {
|
||||||
|
// 新增时刷新列表
|
||||||
|
if (!from.id) {
|
||||||
|
fnGetList();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 编辑时局部更新信息
|
||||||
|
stateNeInfo(from.neType, from.neId)
|
||||||
|
.then(res => {
|
||||||
|
// 找到编辑更新的网元
|
||||||
|
const item = tableState.data.find(s => s.id === from.id);
|
||||||
|
if (item && res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
item.neType = from.neType;
|
||||||
|
item.neId = from.neId;
|
||||||
|
item.rmUid = from.rmUid;
|
||||||
|
item.neName = from.neName;
|
||||||
|
item.ip = from.ip;
|
||||||
|
item.port = from.port;
|
||||||
|
if (item.status !== '2') {
|
||||||
|
item.status = res.data.online ? '1' : '0';
|
||||||
|
}
|
||||||
|
Object.assign(item.serverState, res.data);
|
||||||
|
const resouresUsage = parseResouresUsage(item.serverState);
|
||||||
|
Reflect.set(item, 'resoures', resouresUsage);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
useNeInfoStore().fnRefreshNelist();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对话框弹出关闭执行函数
|
||||||
|
* 进行表达规则校验
|
||||||
|
*/
|
||||||
|
function fnModalEditCancel() {
|
||||||
|
modalState.editId = '';
|
||||||
|
modalState.openByEdit = false;
|
||||||
|
modalState.openByOAM = false;
|
||||||
|
modalState.openByBackConf = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录删除
|
||||||
|
* @param id 编号
|
||||||
|
*/
|
||||||
|
function fnRecordDelete(id: string) {
|
||||||
|
if (!id || modalState.confirmLoading) return;
|
||||||
|
let msg = t('views.ne.neInfo.delTip');
|
||||||
|
if (id === '0') {
|
||||||
|
msg = `${msg} ...${tableState.selectedRowKeys.length}`;
|
||||||
|
id = tableState.selectedRowKeys.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
Modal.confirm({
|
||||||
|
title: t('common.tipTitle'),
|
||||||
|
content: msg,
|
||||||
|
onOk() {
|
||||||
|
modalState.confirmLoading = true;
|
||||||
|
const hide = message.loading(t('common.loading'), 0);
|
||||||
|
delNeInfo(id)
|
||||||
|
.then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS) {
|
||||||
|
message.success(t('common.operateOk'), 3);
|
||||||
|
// 过滤掉删除的id
|
||||||
|
tableState.data = tableState.data.filter(item => {
|
||||||
|
if (id.indexOf(',') > -1) {
|
||||||
|
return !tableState.selectedRowKeys.includes(item.id);
|
||||||
|
} else {
|
||||||
|
return item.id !== id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 刷新缓存
|
||||||
|
useNeInfoStore().fnRefreshNelist();
|
||||||
|
} else {
|
||||||
|
message.error({
|
||||||
|
content: `${res.msg}`,
|
||||||
|
duration: 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
hide();
|
||||||
|
modalState.confirmLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录多项选择
|
||||||
|
*/
|
||||||
|
function fnRecordMore(type: string | number, row: Record<string, any>) {
|
||||||
|
switch (type) {
|
||||||
|
case 'delete':
|
||||||
|
fnRecordDelete(row.id);
|
||||||
|
break;
|
||||||
|
case 'start':
|
||||||
|
fnNeStart(row);
|
||||||
|
break;
|
||||||
|
case 'restart':
|
||||||
|
fnNeRestart(row);
|
||||||
|
break;
|
||||||
|
case 'stop':
|
||||||
|
fnNeStop(row);
|
||||||
|
break;
|
||||||
|
case 'reload':
|
||||||
|
fnNeReload(row);
|
||||||
|
break;
|
||||||
|
case 'log':
|
||||||
|
fnNeLogFile(row);
|
||||||
|
break;
|
||||||
|
case 'oam':
|
||||||
|
modalState.neId = row.neId;
|
||||||
|
modalState.neType = row.neType;
|
||||||
|
modalState.openByOAM = !modalState.openByOAM;
|
||||||
|
break;
|
||||||
|
case 'backConfExport':
|
||||||
|
backConf.value.exportConf(row.neType, row.neId);
|
||||||
|
break;
|
||||||
|
case 'backConfImport':
|
||||||
|
modalState.neId = row.neId;
|
||||||
|
modalState.neType = row.neType;
|
||||||
|
modalState.openByBackConf = !modalState.openByBackConf;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn(type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**查询列表, pageNum初始页数 */
|
||||||
|
function fnGetList(pageNum?: number) {
|
||||||
|
if (tableState.loading) return;
|
||||||
|
tableState.loading = true;
|
||||||
|
if (pageNum) {
|
||||||
|
queryParams.pageNum = pageNum;
|
||||||
|
}
|
||||||
|
listNeInfo(toRaw(queryParams)).then(res => {
|
||||||
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
|
||||||
|
// 取消勾选
|
||||||
|
if (tableState.selectedRowKeys.length > 0) {
|
||||||
|
tableState.selectedRowKeys = [];
|
||||||
|
}
|
||||||
|
tablePagination.total = res.total;
|
||||||
|
// 遍历处理资源情况数值
|
||||||
|
tableState.data = res.rows.map(item => {
|
||||||
|
let resouresUsage = {
|
||||||
|
sysDiskUsage: 0,
|
||||||
|
sysMemUsage: 0,
|
||||||
|
sysCpuUsage: 0,
|
||||||
|
nfCpuUsage: 0,
|
||||||
|
};
|
||||||
|
const neState = item.serverState;
|
||||||
|
if (neState) {
|
||||||
|
resouresUsage = parseResouresUsage(neState);
|
||||||
|
} else {
|
||||||
|
item.serverState = { online: false };
|
||||||
|
}
|
||||||
|
Reflect.set(item, 'resoures', resouresUsage);
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tableState.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**解析网元状态携带的资源利用率 */
|
||||||
|
function parseResouresUsage(neState: Record<string, any>) {
|
||||||
|
let sysCpuUsage = 0;
|
||||||
|
let nfCpuUsage = 0;
|
||||||
|
if (neState.cpu) {
|
||||||
|
nfCpuUsage = neState.cpu.nfCpuUsage;
|
||||||
|
const nfCpu = +(nfCpuUsage / 100);
|
||||||
|
nfCpuUsage = +nfCpu.toFixed(2);
|
||||||
|
if (nfCpuUsage > 100) {
|
||||||
|
nfCpuUsage = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
sysCpuUsage = neState.cpu.sysCpuUsage;
|
||||||
|
const sysCpu = +(sysCpuUsage / 100);
|
||||||
|
sysCpuUsage = +sysCpu.toFixed(2);
|
||||||
|
if (sysCpuUsage > 100) {
|
||||||
|
sysCpuUsage = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sysMemUsage = 0;
|
||||||
|
if (neState.mem) {
|
||||||
|
const men = neState.mem.sysMemUsage;
|
||||||
|
sysMemUsage = +(men / 100).toFixed(2);
|
||||||
|
if (sysMemUsage > 100) {
|
||||||
|
sysMemUsage = 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sysDiskUsage = 0;
|
||||||
|
if (neState.disk && Array.isArray(neState.disk.partitionInfo)) {
|
||||||
|
let disks: any[] = neState.disk.partitionInfo;
|
||||||
|
disks = disks.sort((a, b) => +b.used - +a.used);
|
||||||
|
if (disks.length > 0) {
|
||||||
|
const { total, used } = disks[0];
|
||||||
|
sysDiskUsage = +((used / total) * 100).toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
sysDiskUsage,
|
||||||
|
sysMemUsage,
|
||||||
|
sysCpuUsage,
|
||||||
|
nfCpuUsage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始字典数据
|
||||||
|
Promise.allSettled([getDict('ne_info_status')]).then(resArr => {
|
||||||
|
if (resArr[0].status === 'fulfilled') {
|
||||||
|
dict.neInfoStatus = resArr[0].value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 刷新缓存的网元信息
|
||||||
|
useNeInfoStore()
|
||||||
|
.fnRefreshNelist()
|
||||||
|
.finally(() => {
|
||||||
|
// 获取列表数据
|
||||||
|
fnGetList();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<PageContainer>
|
||||||
|
<a-card
|
||||||
|
v-show="tableState.seached"
|
||||||
|
:bordered="false"
|
||||||
|
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||||
|
>
|
||||||
|
<!-- 表格搜索栏 -->
|
||||||
|
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item :label="t('views.ne.common.neType')" name="neType ">
|
||||||
|
<a-auto-complete
|
||||||
|
v-model:value="queryParams.neType"
|
||||||
|
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
|
||||||
|
allow-clear
|
||||||
|
:placeholder="t('common.inputPlease')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
|
<a-form-item>
|
||||||
|
<a-space :size="8">
|
||||||
|
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||||
|
<template #icon><SearchOutlined /></template>
|
||||||
|
{{ t('common.search') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button type="default" @click.prevent="fnQueryReset">
|
||||||
|
<template #icon><ClearOutlined /></template>
|
||||||
|
{{ t('common.reset') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-form-item>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||||
|
<!-- 插槽-卡片左侧侧 -->
|
||||||
|
<template #title>
|
||||||
|
<a-space :size="8" align="center" v-roles:has="['admin']">
|
||||||
|
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
|
||||||
|
<template #icon><PlusOutlined /></template>
|
||||||
|
{{ t('common.addText') }}
|
||||||
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="default"
|
||||||
|
danger
|
||||||
|
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||||
|
:loading="modalState.confirmLoading"
|
||||||
|
@click.prevent="fnRecordDelete('0')"
|
||||||
|
>
|
||||||
|
<template #icon><DeleteOutlined /></template>
|
||||||
|
{{ t('common.deleteText') }}
|
||||||
|
</a-button>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 插槽-卡片右侧 -->
|
||||||
|
<template #extra>
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.searchBarText') }}</template>
|
||||||
|
<a-switch
|
||||||
|
v-model:checked="tableState.seached"
|
||||||
|
:checked-children="t('common.switch.show')"
|
||||||
|
:un-checked-children="t('common.switch.hide')"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.reloadText') }}</template>
|
||||||
|
<a-button type="text" @click.prevent="fnGetList()">
|
||||||
|
<template #icon><ReloadOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.sizeText') }}</template>
|
||||||
|
<a-dropdown trigger="click" placement="bottomRight">
|
||||||
|
<a-button type="text">
|
||||||
|
<template #icon><ColumnHeightOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu
|
||||||
|
:selected-keys="[tableState.size as string]"
|
||||||
|
@click="fnTableSize"
|
||||||
|
>
|
||||||
|
<a-menu-item key="default">
|
||||||
|
{{ t('common.size.default') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="middle">
|
||||||
|
{{ t('common.size.middle') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="small">
|
||||||
|
{{ t('common.size.small') }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 表格列表 -->
|
||||||
|
<a-table
|
||||||
|
class="table"
|
||||||
|
row-key="id"
|
||||||
|
:columns="tableColumns"
|
||||||
|
:loading="tableState.loading"
|
||||||
|
:data-source="tableState.data"
|
||||||
|
:size="tableState.size"
|
||||||
|
:pagination="tablePagination"
|
||||||
|
:scroll="{ x: tableColumns.length * 120 }"
|
||||||
|
:row-selection="{
|
||||||
|
type: 'checkbox',
|
||||||
|
columnWidth: '48px',
|
||||||
|
selectedRowKeys: tableState.selectedRowKeys,
|
||||||
|
onChange: fnTableSelectedRowKeys,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'status'">
|
||||||
|
<DictTag :options="dict.neInfoStatus" :value="record.status" />
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'id'">
|
||||||
|
<a-space :size="8" align="center">
|
||||||
|
<span v-roles:has="['admin']">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>{{ t('common.editText') }}</template>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
@click.prevent="fnModalVisibleByEdit(record)"
|
||||||
|
>
|
||||||
|
<template #icon><FormOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
<span v-roles:has="['admin', 'teacher']">
|
||||||
|
<a-tooltip>
|
||||||
|
<template #title>
|
||||||
|
{{ t('views.ne.common.restart') }}
|
||||||
|
</template>
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
@click.prevent="fnRecordMore('restart', record)"
|
||||||
|
>
|
||||||
|
<template #icon><UndoOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<a-tooltip placement="left">
|
||||||
|
<template #title>{{ t('common.moreText') }}</template>
|
||||||
|
<a-dropdown placement="bottomRight" trigger="click">
|
||||||
|
<a-button type="link">
|
||||||
|
<template #icon><EllipsisOutlined /> </template>
|
||||||
|
</a-button>
|
||||||
|
<template #overlay>
|
||||||
|
<a-menu @click="({ key }:any) => fnRecordMore(key, record)">
|
||||||
|
<a-menu-item key="log">
|
||||||
|
<FileTextOutlined />
|
||||||
|
{{ t('views.ne.common.log') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="start" v-if="hasRoles(['admin'])">
|
||||||
|
<ThunderboltOutlined />
|
||||||
|
{{ t('views.ne.common.start') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="stop" v-if="hasRoles(['admin'])">
|
||||||
|
<CloseSquareOutlined />
|
||||||
|
{{ t('views.ne.common.stop') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item
|
||||||
|
key="reload"
|
||||||
|
v-if="
|
||||||
|
!['OMC', 'PCF', 'IMS', 'MME'].includes(
|
||||||
|
record.neType
|
||||||
|
) && hasRoles(['admin'])
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<SyncOutlined />
|
||||||
|
{{ t('views.ne.common.reload') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="delete" v-if="hasRoles(['admin'])">
|
||||||
|
<DeleteOutlined />
|
||||||
|
{{ t('common.deleteText') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="oam" v-if="hasRoles(['admin'])">
|
||||||
|
<FileTextOutlined />
|
||||||
|
{{ t('views.ne.common.oam') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<!-- 配置备份 -->
|
||||||
|
<a-menu-item key="backConfExport">
|
||||||
|
<ExportOutlined />
|
||||||
|
{{ t('views.ne.neInfo.backConf.export') }}
|
||||||
|
</a-menu-item>
|
||||||
|
<a-menu-item key="backConfImport">
|
||||||
|
<ImportOutlined />
|
||||||
|
{{ t('views.ne.neInfo.backConf.import') }}
|
||||||
|
</a-menu-item>
|
||||||
|
</a-menu>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
<template #expandedRowRender="{ record }">
|
||||||
|
<a-row :gutter="16">
|
||||||
|
<a-col :offset="2" :lg="8" :md="8" :xs="8">
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.ne.neInfo.info') }}
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.neInfo.serviceState') }}:</span>
|
||||||
|
<a-tag
|
||||||
|
:color="record.serverState.online ? 'processing' : 'error'"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
record.serverState.online
|
||||||
|
? t('views.ne.common.normalcy')
|
||||||
|
: t('views.ne.common.exceptions')
|
||||||
|
}}
|
||||||
|
</a-tag>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.neVersion.version') }}:</span>
|
||||||
|
<span>{{ record.serverState.version }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.common.serialNum') }}:</span>
|
||||||
|
<span>{{ record.serverState.sn }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.common.expiryDate') }}:</span>
|
||||||
|
<span>{{ record.serverState.expire }}</span>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
<a-col :offset="2" :lg="8" :md="8" :xs="8">
|
||||||
|
<a-divider orientation="left">
|
||||||
|
{{ t('views.ne.neInfo.resourceInfo') }}
|
||||||
|
</a-divider>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.neInfo.neCpu') }}:</span>
|
||||||
|
<a-progress
|
||||||
|
status="normal"
|
||||||
|
:stroke-color="
|
||||||
|
record.resoures.nfCpuUsage < 30
|
||||||
|
? '#52c41a'
|
||||||
|
: record.resoures.nfCpuUsage > 70
|
||||||
|
? '#ff4d4f'
|
||||||
|
: '#1890ff'
|
||||||
|
"
|
||||||
|
:percent="record.resoures.nfCpuUsage"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.neInfo.sysCpu') }}:</span>
|
||||||
|
<a-progress
|
||||||
|
status="normal"
|
||||||
|
:stroke-color="
|
||||||
|
record.resoures.sysCpuUsage < 30
|
||||||
|
? '#52c41a'
|
||||||
|
: record.resoures.sysCpuUsage > 70
|
||||||
|
? '#ff4d4f'
|
||||||
|
: '#1890ff'
|
||||||
|
"
|
||||||
|
:percent="record.resoures.sysCpuUsage"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.neInfo.sysMem') }}:</span>
|
||||||
|
<a-progress
|
||||||
|
status="normal"
|
||||||
|
:stroke-color="
|
||||||
|
record.resoures.sysMemUsage < 30
|
||||||
|
? '#52c41a'
|
||||||
|
: record.resoures.sysMemUsage > 70
|
||||||
|
? '#ff4d4f'
|
||||||
|
: '#1890ff'
|
||||||
|
"
|
||||||
|
:percent="record.resoures.sysMemUsage"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span>{{ t('views.ne.neInfo.sysDisk') }}:</span>
|
||||||
|
<a-progress
|
||||||
|
status="normal"
|
||||||
|
:stroke-color="
|
||||||
|
record.resoures.sysDiskUsage < 30
|
||||||
|
? '#52c41a'
|
||||||
|
: record.resoures.sysDiskUsage > 70
|
||||||
|
? '#ff4d4f'
|
||||||
|
: '#1890ff'
|
||||||
|
"
|
||||||
|
:percent="record.resoures.sysDiskUsage"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<!-- 新增框或修改框 -->
|
||||||
|
<EditModal
|
||||||
|
v-model:open="modalState.openByEdit"
|
||||||
|
:edit-id="modalState.editId"
|
||||||
|
@ok="fnModalEditOk"
|
||||||
|
@cancel="fnModalEditCancel"
|
||||||
|
></EditModal>
|
||||||
|
|
||||||
|
<!-- OAM编辑框 -->
|
||||||
|
<OAMModal
|
||||||
|
v-model:open="modalState.openByOAM"
|
||||||
|
:ne-id="modalState.neId"
|
||||||
|
:ne-type="modalState.neType"
|
||||||
|
@cancel="fnModalEditCancel"
|
||||||
|
></OAMModal>
|
||||||
|
|
||||||
|
<!-- 配置文件备份框 -->
|
||||||
|
<BackConfModal
|
||||||
|
ref="backConf"
|
||||||
|
v-model:open="modalState.openByBackConf"
|
||||||
|
:ne-id="modalState.neId"
|
||||||
|
:ne-type="modalState.neType"
|
||||||
|
@cancel="fnModalEditCancel"
|
||||||
|
></BackConfModal>
|
||||||
|
</PageContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.table :deep(.ant-pagination) {
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1584
practical_training/views/system/user/index.vue
Normal file
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
BIN
public/wiregasm/test_ethernet.pcap
Normal file
178925
public/wiregasm/wiregasm.data
Normal file
BIN
public/wiregasm/wiregasm.wasm
Normal file
9768
public/wiregasm/wiregasm_load.js
Normal file
166
public/wiregasm/wiregasm_new.js
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
/**
|
||||||
|
* Wraps the WiregasmLib lib functionality and manages a single DissectSession
|
||||||
|
*/
|
||||||
|
class Wiregasm {
|
||||||
|
constructor() {
|
||||||
|
this.initialized = false;
|
||||||
|
this.session = null;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Initialize the wrapper and the Wiregasm module
|
||||||
|
*
|
||||||
|
* @param loader Loader function for the Emscripten module
|
||||||
|
* @param overrides Overrides
|
||||||
|
*/
|
||||||
|
async init(loader, overrides = {}, beforeInit = null) {
|
||||||
|
if (this.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.lib = await loader(overrides);
|
||||||
|
this.uploadDir = this.lib.getUploadDirectory();
|
||||||
|
this.pluginsDir = this.lib.getPluginsDirectory();
|
||||||
|
if (beforeInit !== null) {
|
||||||
|
await beforeInit(this.lib);
|
||||||
|
}
|
||||||
|
this.lib.init();
|
||||||
|
this.initialized = true;
|
||||||
|
}
|
||||||
|
list_modules() {
|
||||||
|
return this.lib.listModules();
|
||||||
|
}
|
||||||
|
list_prefs(module) {
|
||||||
|
return this.lib.listPreferences(module);
|
||||||
|
}
|
||||||
|
apply_prefs() {
|
||||||
|
this.lib.applyPreferences();
|
||||||
|
}
|
||||||
|
set_pref(module, key, value) {
|
||||||
|
const ret = this.lib.setPref(module, key, value);
|
||||||
|
if (ret.code != PrefSetResult.PREFS_SET_OK) {
|
||||||
|
const message =
|
||||||
|
ret.error != '' ? ret.error : preferenceSetCodeToError(ret.code);
|
||||||
|
throw new Error(
|
||||||
|
`Failed to set preference (${module}.${key}): ${message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get_pref(module, key) {
|
||||||
|
const response = this.lib.getPref(module, key);
|
||||||
|
if (response.code != 0) {
|
||||||
|
throw new Error(`Failed to get preference (${module}.${key})`);
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Check the validity of a filter expression.
|
||||||
|
*
|
||||||
|
* @param filter A display filter expression
|
||||||
|
*/
|
||||||
|
test_filter(filter) {
|
||||||
|
return this.lib.checkFilter(filter);
|
||||||
|
}
|
||||||
|
complete_filter(filter) {
|
||||||
|
const out = this.lib.completeFilter(filter);
|
||||||
|
return {
|
||||||
|
err: out.err,
|
||||||
|
fields: vectorToArray(out.fields),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
reload_lua_plugins() {
|
||||||
|
this.lib.reloadLuaPlugins();
|
||||||
|
}
|
||||||
|
add_plugin(name, data, opts = {}) {
|
||||||
|
const path = this.pluginsDir + '/' + name;
|
||||||
|
this.lib.FS.writeFile(path, data, opts);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Load a packet trace file for analysis.
|
||||||
|
*
|
||||||
|
* @returns Response containing the status and summary
|
||||||
|
*/
|
||||||
|
load(name, data, opts = {}) {
|
||||||
|
if (this.session != null) {
|
||||||
|
this.session.delete();
|
||||||
|
}
|
||||||
|
const path = this.uploadDir + '/' + name;
|
||||||
|
this.lib.FS.writeFile(path, data, opts);
|
||||||
|
this.session = new this.lib.DissectSession(path);
|
||||||
|
return this.session.load();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get Packet List information for a range of packets.
|
||||||
|
*
|
||||||
|
* @param filter Output those frames that pass this filter expression
|
||||||
|
* @param skip Skip N frames
|
||||||
|
* @param limit Limit the output to N frames
|
||||||
|
*/
|
||||||
|
frames(filter, skip = 0, limit = 0) {
|
||||||
|
return this.session.getFrames(filter, skip, limit);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get full information about a frame including the protocol tree.
|
||||||
|
*
|
||||||
|
* @param number Frame number
|
||||||
|
*/
|
||||||
|
frame(num) {
|
||||||
|
return this.session.getFrame(num);
|
||||||
|
}
|
||||||
|
follow(follow, filter) {
|
||||||
|
return this.session.follow(follow, filter);
|
||||||
|
}
|
||||||
|
destroy() {
|
||||||
|
if (this.initialized) {
|
||||||
|
if (this.session !== null) {
|
||||||
|
this.session.delete();
|
||||||
|
this.session = null;
|
||||||
|
}
|
||||||
|
this.lib.destroy();
|
||||||
|
this.initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns the column headers
|
||||||
|
*/
|
||||||
|
columns() {
|
||||||
|
const vec = this.lib.getColumns();
|
||||||
|
// convert it from a vector to array
|
||||||
|
return vectorToArray(vec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a Vector to a JS array
|
||||||
|
*
|
||||||
|
* @param vec Vector
|
||||||
|
* @returns JS array of the Vector contents
|
||||||
|
*/
|
||||||
|
function vectorToArray(vec) {
|
||||||
|
return new Array(vec.size()).fill(0).map((_, id) => vec.get(id));
|
||||||
|
}
|
||||||
|
function preferenceSetCodeToError(code) {
|
||||||
|
switch (code) {
|
||||||
|
case PrefSetResult.PREFS_SET_SYNTAX_ERR:
|
||||||
|
return 'Syntax error in string';
|
||||||
|
case PrefSetResult.PREFS_SET_NO_SUCH_PREF:
|
||||||
|
return 'No such preference';
|
||||||
|
case PrefSetResult.PREFS_SET_OBSOLETE:
|
||||||
|
return 'Preference used to exist but no longer does';
|
||||||
|
default:
|
||||||
|
return 'Unknown error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof exports === 'object' && typeof module === 'object') {
|
||||||
|
module.exports = Wiregasm;
|
||||||
|
module.exports = vectorToArray;
|
||||||
|
} else if (typeof define === 'function' && define['amd']) {
|
||||||
|
define([], function () {
|
||||||
|
return Wiregasm;
|
||||||
|
});
|
||||||
|
define([], function () {
|
||||||
|
return vectorToArray;
|
||||||
|
});
|
||||||
|
} else if (typeof exports === 'object') {
|
||||||
|
exports['loadWiregasm'] = Wiregasm;
|
||||||
|
exports['vectorToArray'] = vectorToArray;
|
||||||
|
}
|
||||||
162
public/wiregasm/worker.js
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
// load the Wiregasm library
|
||||||
|
importScripts(
|
||||||
|
'wiregasm_new.js', // self-compilation es5
|
||||||
|
'wiregasm_load.js'
|
||||||
|
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.js'
|
||||||
|
);
|
||||||
|
|
||||||
|
const wg = new Wiregasm();
|
||||||
|
|
||||||
|
const inflateRemoteBuffer = async url => {
|
||||||
|
const res = await fetch(url);
|
||||||
|
return await res.arrayBuffer();
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchPackages = async () => {
|
||||||
|
console.log('Fetching packages');
|
||||||
|
let [wasmBuffer, dataBuffer] = await Promise.all([
|
||||||
|
await inflateRemoteBuffer(
|
||||||
|
'wiregasm.wasm'
|
||||||
|
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.wasm'
|
||||||
|
),
|
||||||
|
await inflateRemoteBuffer(
|
||||||
|
'wiregasm.data'
|
||||||
|
// 'https://cdn.jsdelivr.net/npm/@goodtools/wiregasm/dist/wiregasm.data'
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { wasmBuffer, dataBuffer };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load the Wiregasm Wasm data
|
||||||
|
fetchPackages()
|
||||||
|
.then(({ wasmBuffer, dataBuffer }) => {
|
||||||
|
return wg.init(loadWiregasm, {
|
||||||
|
wasmBinary: wasmBuffer,
|
||||||
|
getPreloadedPackage() {
|
||||||
|
return dataBuffer;
|
||||||
|
},
|
||||||
|
handleStatus: (type, status) => {
|
||||||
|
postMessage({ type: 'status', code: type, status: status });
|
||||||
|
},
|
||||||
|
printErr: error => {
|
||||||
|
postMessage({ type: 'error', error: error });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
postMessage({ type: 'init' });
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
postMessage({ type: 'error', error: e });
|
||||||
|
});
|
||||||
|
|
||||||
|
/**Converts a Vector to a JS array */
|
||||||
|
function replacer(key, value) {
|
||||||
|
if (value.constructor.name.startsWith('Vector')) {
|
||||||
|
return vectorToArray(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listener to receive messages from the main script
|
||||||
|
this.onmessage = ev => {
|
||||||
|
const data = ev.data;
|
||||||
|
switch (data.type) {
|
||||||
|
case 'close':
|
||||||
|
wg.destroy();
|
||||||
|
break;
|
||||||
|
case 'columns':
|
||||||
|
const columns = wg.columns();
|
||||||
|
if (Array.isArray(columns)) {
|
||||||
|
this.postMessage({ type: 'columns', data: columns });
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'select': // select a frame
|
||||||
|
const number = data.number;
|
||||||
|
const frameData = wg.frame(number);
|
||||||
|
const frameDataToJSON = JSON.parse(JSON.stringify(frameData, replacer));
|
||||||
|
this.postMessage({
|
||||||
|
type: 'selected',
|
||||||
|
data: frameDataToJSON,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'frames': // get frames list
|
||||||
|
const skip = data.skip;
|
||||||
|
const limit = data.limit;
|
||||||
|
const filter = data.filter;
|
||||||
|
const framesData = wg.frames(filter, skip, limit);
|
||||||
|
const framesDataToJSON = JSON.parse(JSON.stringify(framesData, replacer));
|
||||||
|
this.postMessage({
|
||||||
|
type: 'frames',
|
||||||
|
data: framesDataToJSON,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'process-data':
|
||||||
|
const loadData = wg.load(data.name, new Uint8Array(data.data));
|
||||||
|
this.postMessage({ type: 'processed', data: loadData });
|
||||||
|
break;
|
||||||
|
case 'process':
|
||||||
|
const f = data.file;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.addEventListener('load', event => {
|
||||||
|
// XXX: this blocks the worker thread
|
||||||
|
const loadData = wg.load(f.name, new Uint8Array(event.target.result));
|
||||||
|
postMessage({ type: 'processed', data: loadData });
|
||||||
|
});
|
||||||
|
reader.readAsArrayBuffer(f);
|
||||||
|
break;
|
||||||
|
case 'check-filter':
|
||||||
|
const filterStr = data.filter;
|
||||||
|
const checkFilterRes = wg.lib.checkFilter(filterStr);
|
||||||
|
this.postMessage({ type: 'filter', data: checkFilterRes });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type === 'reload-quick') {
|
||||||
|
if (wg.session) {
|
||||||
|
// TODO: this is a hack, we should be able to reload the session
|
||||||
|
const name = data.name;
|
||||||
|
const res = wg.session.load();
|
||||||
|
|
||||||
|
postMessage({ type: 'processed', name: name, data: res });
|
||||||
|
}
|
||||||
|
} else if (data.type === 'module-tree') {
|
||||||
|
const res = wg.list_modules();
|
||||||
|
// send it to the correct port
|
||||||
|
event.ports[0].postMessage({
|
||||||
|
result: JSON.parse(JSON.stringify(res, replacer)),
|
||||||
|
});
|
||||||
|
} else if (data.type === 'module-prefs') {
|
||||||
|
const res = wg.list_prefs(data.name);
|
||||||
|
// send it to the correct port
|
||||||
|
event.ports[0].postMessage({
|
||||||
|
result: JSON.parse(JSON.stringify(res, replacer)),
|
||||||
|
});
|
||||||
|
} else if (data.type === 'upload-file') {
|
||||||
|
const f = data.file;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.addEventListener('load', e => {
|
||||||
|
// XXX: this blocks the worker thread
|
||||||
|
const path = '/uploads/' + f.name;
|
||||||
|
wg.lib.FS.writeFile(path, Buffer.from(e.target.result));
|
||||||
|
event.ports[0].postMessage({ result: path });
|
||||||
|
});
|
||||||
|
reader.readAsArrayBuffer(f);
|
||||||
|
} else if (data.type === 'update-pref') {
|
||||||
|
try {
|
||||||
|
console.log(`set_pref(${data.module}, ${data.key}, ${data.value})`);
|
||||||
|
wg.set_pref(data.module, data.key, data.value);
|
||||||
|
event.ports[0].postMessage({ result: 'ok' });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(
|
||||||
|
`set_pref(${data.module}, ${data.key}, ${data.value}) failed: ${e.message}`
|
||||||
|
);
|
||||||
|
event.ports[0].postMessage({ error: e.message });
|
||||||
|
}
|
||||||
|
} else if (data.type === 'apply-prefs') {
|
||||||
|
console.log(`apply_prefs()`);
|
||||||
|
wg.apply_prefs();
|
||||||
|
event.ports[0].postMessage({ result: 'ok' });
|
||||||
|
}
|
||||||
|
};
|
||||||
119
src/App.vue
@@ -1,22 +1,48 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ConfigProvider } from 'ant-design-vue/lib';
|
import { onBeforeMount, ref, watch } from 'vue';
|
||||||
import { usePrimaryColor } from '@/hooks/useTheme';
|
import { message } from 'ant-design-vue/es';
|
||||||
import zhCN from 'ant-design-vue/lib/locale/zh_CN';
|
import zhCN from 'ant-design-vue/es/locale/zh_CN';
|
||||||
import enUS from 'ant-design-vue/lib/locale/en_US';
|
import enUS from 'ant-design-vue/es/locale/en_US';
|
||||||
|
import { usePrefersColorScheme, viewTransitionTheme } from 'antdv-pro-layout';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import advancedFormat from 'dayjs/plugin/advancedFormat';
|
import advancedFormat from 'dayjs/plugin/advancedFormat';
|
||||||
import 'dayjs/locale/zh-cn';
|
import useLayoutStore from '@/store/modules/layout';
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
const { t, currentLocale } = useI18n();
|
const { t, currentLocale } = useI18n();
|
||||||
const appStore = useAppStore();
|
const { themeConfig, initPrimaryColor, changeConf } = useLayoutStore();
|
||||||
|
|
||||||
dayjs.extend(advancedFormat);
|
dayjs.extend(advancedFormat);
|
||||||
dayjs.locale('zh-cn'); // 默认中文
|
// dayjs.locale('zh-cn'); // 默认中文
|
||||||
usePrimaryColor(); // 载入用户自定义主题色
|
let locale = ref(enUS); // 国际化初始中文
|
||||||
|
|
||||||
let locale = ref(zhCN); // 国际化初始中文
|
// 偏好设置
|
||||||
|
const colorScheme = usePrefersColorScheme();
|
||||||
|
watch(
|
||||||
|
() => colorScheme.value,
|
||||||
|
themeMode => {
|
||||||
|
viewTransitionTheme(() => {
|
||||||
|
changeConf('theme', themeMode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
// 全局message提示
|
||||||
|
message.config({
|
||||||
|
top: '100px', // 距离顶部位置100px
|
||||||
|
duration: 3,
|
||||||
|
maxCount: 15,
|
||||||
|
});
|
||||||
|
initPrimaryColor();
|
||||||
|
|
||||||
|
// 输出应用版本号
|
||||||
|
const appStore = useAppStore();
|
||||||
|
console.info(
|
||||||
|
`%c ${t('common.desc')} %c ${appStore.appCode} - ${appStore.appVersion} `,
|
||||||
|
'color: #fadfa3; background: #030307; padding: 4px 0;',
|
||||||
|
'color: #030307; background: #fadfa3; padding: 4px 0;'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// 国际化切换语言
|
// 国际化切换语言
|
||||||
function fnChangeLocale(v: string) {
|
function fnChangeLocale(v: string) {
|
||||||
@@ -37,26 +63,18 @@ fnChangeLocale(currentLocale.value);
|
|||||||
watch(currentLocale, val => {
|
watch(currentLocale, val => {
|
||||||
fnChangeLocale(val);
|
fnChangeLocale(val);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 输出应用版本号
|
|
||||||
console.info(
|
|
||||||
`%c ${t('common.title')} %c ${appStore.appCode} - ${appStore.appVersion} `,
|
|
||||||
'color: #fadfa3; background: #030307; padding: 4px 0;',
|
|
||||||
'color: #030307; background: #fadfa3; padding: 4px 0;'
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ConfigProvider :locale="locale">
|
<a-config-provider :theme="themeConfig" :locale="locale">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</ConfigProvider>
|
</a-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="css">
|
<style lang="css">
|
||||||
#app {
|
#app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body .ant-pro-basicLayout {
|
body .ant-pro-basicLayout {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -86,56 +104,23 @@ body .ant-pro-basicLayout {
|
|||||||
transform: translate(-2em, 0);
|
transform: translate(-2em, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**强制改表格边距 */
|
::view-transition-old(root),
|
||||||
.ant-table.ant-table-small .ant-table-tbody > tr > td,
|
::view-transition-new(root) {
|
||||||
.ant-table.ant-table-small .ant-table-thead > tr > th {
|
animation: none;
|
||||||
padding: 6px !important;
|
mix-blend-mode: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ==== 表格头按钮区域 S === **/
|
[data-theme='dark']::view-transition-old(root) {
|
||||||
/* 默认 */
|
z-index: 1;
|
||||||
.button-container {
|
}
|
||||||
display: flex;
|
[data-theme='dark']::view-transition-new(root) {
|
||||||
flex-wrap: wrap;
|
z-index: 999;
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-container > button,
|
::view-transition-old(root) {
|
||||||
.button-container > span {
|
z-index: 999;
|
||||||
margin-right: 12px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
}
|
||||||
|
::view-transition-new(root) {
|
||||||
.button-container > button:last-child,
|
z-index: 1;
|
||||||
.button-container > span:last-child {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
}
|
||||||
/* 平板端 */
|
|
||||||
@media (max-width: 992px) {
|
|
||||||
.button-container {
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-start;
|
|
||||||
align-items: left;
|
|
||||||
}
|
|
||||||
.button-container > button,
|
|
||||||
.button-container > span {
|
|
||||||
margin-right: 12px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* 手机端 */
|
|
||||||
@media (max-width: 576px) {
|
|
||||||
.button-container {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
align-items: left;
|
|
||||||
}
|
|
||||||
.button-container > button,
|
|
||||||
.button-container > span {
|
|
||||||
margin-right: 0px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/** ==== 表格头按钮区域 E === **/
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,55 +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 tag 配置ID
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export async function getConfigInfo(tag: string) {
|
|
||||||
// 发起请求
|
|
||||||
const result = await request({
|
|
||||||
url: `/api/rest/databaseManagement/v1/omc_db/config`,
|
|
||||||
method: 'get',
|
|
||||||
params: {
|
|
||||||
SQL: `SELECT * FROM config WHERE config_tag = '${tag}'`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// 解析数据
|
|
||||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
|
||||||
let data = result.data.data[0];
|
|
||||||
return Object.assign(result, {
|
|
||||||
data: parseObjLineToHump(data['config'][0]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改配置
|
|
||||||
* @param data 配置对象
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export async function updateConfig(tag: string, data: Record<string, any>) {
|
|
||||||
const result = await request({
|
|
||||||
url: `/api/rest/databaseManagement/v1/omc_db/config?WHERE=config_tag='${tag}'`,
|
|
||||||
method: 'put',
|
|
||||||
data: { data },
|
|
||||||
});
|
|
||||||
// 解析数据
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@@ -2,315 +2,8 @@ import {
|
|||||||
RESULT_CODE_ERROR,
|
RESULT_CODE_ERROR,
|
||||||
RESULT_CODE_SUCCESS,
|
RESULT_CODE_SUCCESS,
|
||||||
RESULT_MSG_ERROR,
|
RESULT_MSG_ERROR,
|
||||||
RESULT_MSG_SUCCESS,
|
|
||||||
} from '@/constants/result-constants';
|
} from '@/constants/result-constants';
|
||||||
import { language, request } from '@/plugins/http-fetch';
|
import { language, request } from '@/plugins/http-fetch';
|
||||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询配置参数标签栏
|
|
||||||
* @param neType 网元类型
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export async function getParamConfigTopTab(neType: string) {
|
|
||||||
// 发起请求
|
|
||||||
const result = await request({
|
|
||||||
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/param_config`,
|
|
||||||
method: 'get',
|
|
||||||
params: {
|
|
||||||
SQL: `SELECT id,top_display,top_tag,method FROM param_config WHERE ne_type = '${neType}' ORDER BY id ASC`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// 解析数据
|
|
||||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
|
||||||
let data = result.data.data[0];
|
|
||||||
data = data['param_config'];
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
return Object.assign(result, {
|
|
||||||
data: parseObjLineToHump(data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Object.assign(result, {
|
|
||||||
data: [],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询配置参数标签栏对应信息和规则
|
|
||||||
* @param neType 网元类型
|
|
||||||
* @param topTag
|
|
||||||
* @param neId
|
|
||||||
* @returns object { wrRule, dataArr }
|
|
||||||
*/
|
|
||||||
async function getParamConfigInfoAndRule(
|
|
||||||
neType: string,
|
|
||||||
topTag: string,
|
|
||||||
neId: string
|
|
||||||
) {
|
|
||||||
return await Promise.allSettled([
|
|
||||||
// 获取参数规则
|
|
||||||
request({
|
|
||||||
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/param_config`,
|
|
||||||
method: 'get',
|
|
||||||
params: {
|
|
||||||
SQL: `SELECT param_json FROM param_config WHERE ne_type = '${neType}' AND top_tag='${topTag}'`,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
// 获取对应信息
|
|
||||||
request({
|
|
||||||
url: `/api/rest/systemManagement/v1/elementType/${neType.toLowerCase()}/objectType/config/${topTag}`,
|
|
||||||
method: 'get',
|
|
||||||
params: {
|
|
||||||
ne_id: neId,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]).then(resArr => {
|
|
||||||
let wrRule: Record<string, any> = {};
|
|
||||||
// 规则数据
|
|
||||||
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;
|
|
||||||
const data = itemData[0]['param_config'];
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
const v = data[0]['param_json'];
|
|
||||||
try {
|
|
||||||
itemData = parseObjLineToHump(JSON.parse(v));
|
|
||||||
wrRule = itemData;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let dataArr: Record<string, any>[] = [];
|
|
||||||
// 对应信息
|
|
||||||
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;
|
|
||||||
dataArr = parseObjLineToHump(itemData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { wrRule, dataArr };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询配置参数标签栏对应信息-表单结构处理
|
|
||||||
* @param neType 网元类型
|
|
||||||
* @param topTag
|
|
||||||
* @param neId
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export async function getParamConfigInfoForm(
|
|
||||||
neType: string,
|
|
||||||
topTag: string,
|
|
||||||
neId: string
|
|
||||||
) {
|
|
||||||
const { wrRule, dataArr } = await getParamConfigInfoAndRule(
|
|
||||||
neType,
|
|
||||||
topTag,
|
|
||||||
neId
|
|
||||||
);
|
|
||||||
|
|
||||||
// 拼装数据
|
|
||||||
const result = {
|
|
||||||
code: RESULT_CODE_SUCCESS,
|
|
||||||
msg: RESULT_MSG_SUCCESS,
|
|
||||||
data: {
|
|
||||||
type: 'list' as 'list' | 'array',
|
|
||||||
data: [] as Record<string, any>[],
|
|
||||||
dataRule: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// kv单列表
|
|
||||||
if (Reflect.has(wrRule, 'list')) {
|
|
||||||
result.data.type = 'list';
|
|
||||||
const ruleArr = Object.freeze(wrRule['list']);
|
|
||||||
|
|
||||||
// 列表项数据
|
|
||||||
const dataList = [];
|
|
||||||
for (const item of dataArr) {
|
|
||||||
for (const key in 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;
|
|
||||||
}
|
|
||||||
// 多列表
|
|
||||||
if (Reflect.has(wrRule, 'array')) {
|
|
||||||
result.data.type = 'array';
|
|
||||||
const ruleArr = Object.freeze(wrRule['array']);
|
|
||||||
// 列表项数据
|
|
||||||
const dataArray = [];
|
|
||||||
for (const item of dataArr) {
|
|
||||||
const index = item['index'];
|
|
||||||
let record: Record<string, any>[] = [];
|
|
||||||
for (const key in item) {
|
|
||||||
// 规则为准
|
|
||||||
for (const rule of ruleArr) {
|
|
||||||
if (rule['name'] === key) {
|
|
||||||
const ruleItem = Object.assign({ optional: 'true' }, rule, {
|
|
||||||
value: item[key],
|
|
||||||
});
|
|
||||||
record.push(ruleItem);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dataArray.push({ title: `Index-${index}`, key: index, record });
|
|
||||||
}
|
|
||||||
result.data.data = dataArray;
|
|
||||||
|
|
||||||
// 无数据时,用于新增
|
|
||||||
result.data.dataRule = { title: `Index-0`, key: 0, record: ruleArr };
|
|
||||||
}
|
|
||||||
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 网元类型
|
|
||||||
* @param topTag
|
|
||||||
* @param neId
|
|
||||||
* @param loc 子节点(index/字段) 1/dnnList
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export async function getParamConfigInfoChild(
|
|
||||||
neType: string,
|
|
||||||
topTag: string,
|
|
||||||
neId: string,
|
|
||||||
loc: string
|
|
||||||
) {
|
|
||||||
// 发起请求
|
|
||||||
const result = await request({
|
|
||||||
url: `/api/rest/systemManagement/v1/elementType/${neType.toLowerCase()}/objectType/config/${topTag}`,
|
|
||||||
method: 'get',
|
|
||||||
params: {
|
|
||||||
ne_id: neId,
|
|
||||||
loc: loc,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// 解析数据
|
|
||||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
|
||||||
return Object.assign(result, {
|
|
||||||
data: parseObjLineToHump(result.data.data),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改配置参数标签栏对应信息
|
|
||||||
* @param args 对象 {neType,neId,topTag,loc}
|
|
||||||
* @param data 对象 {修改的数据kv}
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export function updateParamConfigInfo(
|
|
||||||
type: 'list' | 'array',
|
|
||||||
args: Record<string, any>,
|
|
||||||
data: Record<string, any>
|
|
||||||
) {
|
|
||||||
let url = `/api/rest/systemManagement/v1/elementType/${args.neType.toLowerCase()}/objectType/config/${
|
|
||||||
args.topTag
|
|
||||||
}?ne_id=${args.neId}`;
|
|
||||||
|
|
||||||
// 多列表需要loc
|
|
||||||
if (type === 'array') {
|
|
||||||
url += `&loc=${args.loc}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return request({
|
|
||||||
url,
|
|
||||||
method: 'put',
|
|
||||||
data: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增配置参数标签栏对应信息
|
|
||||||
* @param args 对象 {neType,neId,topTag,loc}
|
|
||||||
* @param data 行记录对象
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export function addParamConfigInfo(
|
|
||||||
args: Record<string, any>,
|
|
||||||
data: Record<string, any>
|
|
||||||
) {
|
|
||||||
return request({
|
|
||||||
url: `/api/rest/systemManagement/v1/elementType/${args.neType.toLowerCase()}/objectType/config/${
|
|
||||||
args.topTag
|
|
||||||
}?ne_id=${args.neId}&loc=${args.loc}`,
|
|
||||||
method: 'post',
|
|
||||||
data: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除配置参数标签栏对应信息
|
|
||||||
* @param args 对象 {neType,neId,topTag,loc}
|
|
||||||
* loc 多层表的定位信息{index0}/{paraName1}/{index1}
|
|
||||||
* @param data 行记录对象
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export function delParamConfigInfo(args: Record<string, any>) {
|
|
||||||
return request({
|
|
||||||
url: `/api/rest/systemManagement/v1/elementType/${args.neType.toLowerCase()}/objectType/config/${
|
|
||||||
args.topTag
|
|
||||||
}?ne_id=${args.neId}&loc=${args.loc}`,
|
|
||||||
method: 'delete',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新网元配置重新载入
|
* 更新网元配置重新载入
|
||||||
@@ -343,141 +36,50 @@ export async function updateNeConfigReload(neType: string, neId: string) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 从参数配置PCF中获取对应信息提供给PCC用户策略输入框
|
* 从参数配置PCF中获取对应信息提供给PCC用户策略输入框
|
||||||
* @param neType 网元类型
|
|
||||||
* @param topTag
|
|
||||||
* @param neId
|
* @param neId
|
||||||
* @returns object { wrRule, dataArr }
|
* @returns object {pccRules,sessionRules,qosTemplate,headerEnrichTemplate,serviceAreaRestriction}
|
||||||
*/
|
*/
|
||||||
export async function getPCCRule(neId: any) {
|
export async function getPCCRule(neId: any) {
|
||||||
return await Promise.allSettled([
|
const paramNameArr = [
|
||||||
// 获取参数规则
|
'pccRules',
|
||||||
request({
|
'sessionRules',
|
||||||
url: `/api/rest/systemManagement/v1/elementType/pcf/objectType/config/pccRules`,
|
'qosTemplate',
|
||||||
method: 'get',
|
'headerEnrichTemplate',
|
||||||
params: {
|
'serviceAreaRestriction',
|
||||||
ne_id: neId,
|
];
|
||||||
},
|
const reqArr = [];
|
||||||
timeout: 1_000,
|
for (const paramName of paramNameArr) {
|
||||||
}),
|
reqArr.push(
|
||||||
// 获取对应信息
|
request({
|
||||||
request({
|
url: `/ne/config/data`,
|
||||||
url: `/api/rest/systemManagement/v1/elementType/pcf/objectType/config/sessionRules`,
|
params: { neType: 'PCF', neId, paramName },
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
})
|
||||||
ne_id: neId,
|
);
|
||||||
},
|
}
|
||||||
timeout: 1_000,
|
return await Promise.allSettled(reqArr).then(resArr => {
|
||||||
}),
|
|
||||||
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 obj: any = {};
|
||||||
const itemV = resArr[0].value;
|
resArr.forEach((item, i: number) => {
|
||||||
// 解析数据
|
if (item.status === 'fulfilled') {
|
||||||
if (
|
const res = item.value;
|
||||||
itemV.code === RESULT_CODE_SUCCESS &&
|
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||||
Array.isArray(itemV.data?.data)
|
const key = paramNameArr[i];
|
||||||
) {
|
obj[key] = res.data.map((item: any) => {
|
||||||
let itemData = itemV.data.data;
|
if ('qosTemplate' === key) {
|
||||||
itemData.forEach((item: any) => {
|
return { value: item.qosId, label: item.qosId };
|
||||||
pccJson.set(item.ruleId, { value: item.ruleId, label: item.ruleId });
|
}
|
||||||
});
|
if ('headerEnrichTemplate' === key) {
|
||||||
}
|
return { value: item.templateName, label: item.templateName };
|
||||||
}
|
}
|
||||||
|
if ('serviceAreaRestriction' === key) {
|
||||||
if (resArr[1].status === 'fulfilled') {
|
return { value: item.name, label: item.name };
|
||||||
const itemV = resArr[1].value;
|
}
|
||||||
// 解析数据
|
return { value: item.ruleId, label: item.ruleId };
|
||||||
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,
|
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
return obj;
|
||||||
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,72 +0,0 @@
|
|||||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
|
||||||
import { request } from '@/plugins/http-fetch';
|
|
||||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询软件列表
|
|
||||||
* @param query 查询参数
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export async function listLicense(query: Record<string, any>) {
|
|
||||||
let totalSQL = 'select count(id) as total from ne_license ';
|
|
||||||
let rowsSQL = ' select * from ne_license ';
|
|
||||||
|
|
||||||
// 查询
|
|
||||||
let querySQL = 'where 1=1';
|
|
||||||
if (query.neType) {
|
|
||||||
querySQL += ` and ne_type like '%${query.neType}%' `;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分页
|
|
||||||
const pageNum = (query.pageNum - 1) * query.pageSize;
|
|
||||||
const limtSql = ` order by create_time desc limit ${pageNum},${query.pageSize} `;
|
|
||||||
|
|
||||||
// 发起请求
|
|
||||||
const result = await request({
|
|
||||||
url: `/api/rest/databaseManagement/v1/select/omc_db/ne_license`,
|
|
||||||
method: 'get',
|
|
||||||
params: {
|
|
||||||
totalSQL: totalSQL + querySQL,
|
|
||||||
rowsSQL: rowsSQL + querySQL + limtSql,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 解析数据
|
|
||||||
if (result.code === RESULT_CODE_SUCCESS) {
|
|
||||||
const data: DataList = {
|
|
||||||
total: 0,
|
|
||||||
rows: [],
|
|
||||||
code: result.code,
|
|
||||||
msg: result.msg,
|
|
||||||
};
|
|
||||||
result.data.data.forEach((item: any) => {
|
|
||||||
const itemData = item['ne_license'];
|
|
||||||
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 data 表单数据对象
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export function uploadLicense(data: FormData) {
|
|
||||||
return request({
|
|
||||||
url: `/api/rest/systemManagement/v1/elementType/${data.get(
|
|
||||||
'nfType'
|
|
||||||
)}/objectType/license?neId=${data.get('nfId')}`,
|
|
||||||
method: 'post',
|
|
||||||
data,
|
|
||||||
dataType: 'form-data',
|
|
||||||
timeout: 180_000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -241,6 +241,7 @@ export function listSync() {
|
|||||||
return request({
|
return request({
|
||||||
url: `/api/rest/faultManagement/v1/elementType/all/objectType/alarms`,
|
url: `/api/rest/faultManagement/v1/elementType/all/objectType/alarms`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
timeout: 180_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import { parseObjLineToHump } from '@/utils/parse-utils';
|
|||||||
import { parseDateToStr } from '@/utils/date-utils';
|
import { parseDateToStr } from '@/utils/date-utils';
|
||||||
import useUserStore from '@/store/modules/user';
|
import useUserStore from '@/store/modules/user';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询列表
|
* 查询列表
|
||||||
* @param query 查询参数
|
* @param query 查询参数
|
||||||
@@ -28,8 +26,6 @@ export async function listAct(query: Record<string, any>) {
|
|||||||
querySQL += ` and pv_flag = '${query.pvFlag}' `;
|
querySQL += ` and pv_flag = '${query.pvFlag}' `;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (query.neId) {
|
if (query.neId) {
|
||||||
querySQL += ` and ne_id like '%${query.neId}%' `;
|
querySQL += ` and ne_id like '%${query.neId}%' `;
|
||||||
}
|
}
|
||||||
@@ -69,7 +65,6 @@ export async function listAct(query: Record<string, any>) {
|
|||||||
msg: result.msg,
|
msg: result.msg,
|
||||||
};
|
};
|
||||||
result.data.data.forEach((item: any) => {
|
result.data.data.forEach((item: any) => {
|
||||||
console.log(item)
|
|
||||||
const itemData = item['alarm_event'];
|
const itemData = item['alarm_event'];
|
||||||
if (Array.isArray(itemData)) {
|
if (Array.isArray(itemData)) {
|
||||||
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
|
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
|
||||||
@@ -84,12 +79,6 @@ export async function listAct(query: Record<string, any>) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 事件告警导出
|
* 事件告警导出
|
||||||
* @param query 查询参数
|
* @param query 查询参数
|
||||||
@@ -133,7 +122,3 @@ export async function exportAll(query: Record<string, any>) {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
53
src/api/logManage/exportFile.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取下拉框数据
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function getBakFile() {
|
||||||
|
return request({
|
||||||
|
url: '/lm/table/list',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取对应类型的文件列表
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function getBakFileList(query: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/lm/file/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载远端文件
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function downFile(query: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/lm/file/${query.fileName}`,
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 180_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除远端获取文件
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function delFile(query: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/lm/file/${query.fileName}`,
|
||||||
|
method: 'delete',
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ export function login(data: Record<string, string>) {
|
|||||||
method: 'post',
|
method: 'post',
|
||||||
data: data,
|
data: data,
|
||||||
whithToken: false,
|
whithToken: false,
|
||||||
|
crypto: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ export function register(data: Record<string, any>) {
|
|||||||
method: 'post',
|
method: 'post',
|
||||||
data: data,
|
data: data,
|
||||||
whithToken: false,
|
whithToken: false,
|
||||||
|
crypto: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ export function getLoad(query: Record<string, any>) {
|
|||||||
url: '/monitor/load',
|
url: '/monitor/load',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: query,
|
params: query,
|
||||||
|
timeout: 60_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ export function getSystemInfo() {
|
|||||||
return request({
|
return request({
|
||||||
url: '/monitor/system-info',
|
url: '/monitor/system-info',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
timeout: 60_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
66
src/api/ne/neConfig.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置可用属性值列表指定网元类型全部无分页
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function getAllNeConfig(neType: string) {
|
||||||
|
return request({
|
||||||
|
url: `/ne/config/list/${neType}`,
|
||||||
|
method: 'get',
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置数据信息
|
||||||
|
* @param params 数据 {neType,neId,paramName}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function getNeConfigData(params: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/ne/config/data`,
|
||||||
|
params,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置数据更新
|
||||||
|
* @param data 数据 {neType,neId,paramName:"参数名",paramData:{参数},loc:"层级index仅array"}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function editNeConfigData(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/ne/config/data`,
|
||||||
|
method: 'put',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置数据新增(array)
|
||||||
|
* @param data 数据 {neType,neId,paramName:"参数名",paramData:{参数},loc:"层级index"}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function addNeConfigData(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/ne/config/data`,
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置数据删除(array)
|
||||||
|
* @param params 数据 {neType,neId,paramName:"参数名",loc:"层级index"}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function delNeConfigData(params: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/ne/config/data`,
|
||||||
|
method: 'delete',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -36,6 +36,8 @@ export function addNeInfo(data: Record<string, any>) {
|
|||||||
url: `/ne/info`,
|
url: `/ne/info`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: data,
|
data: data,
|
||||||
|
crypto: true,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +51,8 @@ export function updateNeInfo(data: Record<string, any>) {
|
|||||||
url: `/ne/info`,
|
url: `/ne/info`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data: data,
|
data: data,
|
||||||
|
crypto: true,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,3 +40,16 @@ export function exportSMFDataCDR(data: Record<string, any>) {
|
|||||||
timeout: 60_000,
|
timeout: 60_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMF-在线订阅用户列表信息
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function listSMFSubscribers(query: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/neData/smf/subscribers',
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
42
src/api/neData/smsc.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询SMSC-CDR会话事件
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function listSMSCDataCDR(query: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/neData/smsc/cdr/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMSC-CDR会话删除
|
||||||
|
* @param id 信息ID
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function delSMSCDataCDR(cdrIds: string | number) {
|
||||||
|
return request({
|
||||||
|
url: `/neData/smsc/cdr/${cdrIds}`,
|
||||||
|
method: 'delete',
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SMSC-CDR会话列表导出
|
||||||
|
* @param data 查询列表条件
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function exportSMSCDataCDR(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/neData/smsc/cdr/export',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ export function listUDMAuth(query: Record<string, any>) {
|
|||||||
url: '/neData/udm/auth/list',
|
url: '/neData/udm/auth/list',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: query,
|
params: query,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export function listUDMSub(query: Record<string, any>) {
|
|||||||
url: '/neData/udm/sub/list',
|
url: '/neData/udm/sub/list',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: query,
|
params: query,
|
||||||
|
timeout: 30_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
src/api/perfManage/customData.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新 查询自定义指标数据
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export async function listCustomData(query: Record<string, any>) {
|
||||||
|
// 发起请求
|
||||||
|
const result = await request({
|
||||||
|
url: `/pm/kpiC/report`,
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -8,57 +8,72 @@ import { parseDateToStr } from '@/utils/date-utils';
|
|||||||
* @param query 查询参数
|
* @param query 查询参数
|
||||||
* @returns object
|
* @returns object
|
||||||
*/
|
*/
|
||||||
export async function listCustom(query: Record<string, any>) {
|
// export async function listCustom(query: Record<string, any>) {
|
||||||
let totalSQL = 'select count(*) as total from pm_custom_title where 1=1 ';
|
// let totalSQL = 'select count(*) as total from pm_custom_title where 1=1 ';
|
||||||
let rowsSQL = 'select * from pm_custom_title where 1=1 ';
|
// let rowsSQL = 'select * from pm_custom_title where 1=1 ';
|
||||||
|
|
||||||
// 查询
|
// // 查询
|
||||||
let querySQL = '';
|
// let querySQL = '';
|
||||||
if (query.neType) {
|
// if (query.neType) {
|
||||||
querySQL += ` and ne_type like '%${query.neType}%' `;
|
// querySQL += ` and ne_type like '%${query.neType}%' `;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 排序
|
// // 排序
|
||||||
let sortSql = ' order by update_time ';
|
// let sortSql = ' order by update_time ';
|
||||||
if (query.sortOrder === 'asc') {
|
// if (query.sortOrder === 'asc') {
|
||||||
sortSql += ' asc ';
|
// sortSql += ' asc ';
|
||||||
} else {
|
// } else {
|
||||||
sortSql += ' desc ';
|
// sortSql += ' desc ';
|
||||||
}
|
// }
|
||||||
// 分页
|
// // 分页
|
||||||
const pageNum = (query.pageNum - 1) * query.pageSize;
|
// const pageNum = (query.pageNum - 1) * query.pageSize;
|
||||||
const limtSql = ` limit ${pageNum},${query.pageSize} `;
|
// const limtSql = ` limit ${pageNum},${query.pageSize} `;
|
||||||
|
|
||||||
|
// // 发起请求
|
||||||
|
// const result = await request({
|
||||||
|
// url: `/api/rest/databaseManagement/v1/select/omc_db/pm_custom_title`,
|
||||||
|
// 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['pm_custom_title'];
|
||||||
|
// 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 object
|
||||||
|
*/
|
||||||
|
export async function listCustom(query?: Record<string, any>) {
|
||||||
// 发起请求
|
// 发起请求
|
||||||
const result = await request({
|
const result = await request({
|
||||||
url: `/api/rest/databaseManagement/v1/select/omc_db/pm_custom_title`,
|
url: `/pm/kpiC/title/totalList`,
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params: query,
|
||||||
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['pm_custom_title'];
|
|
||||||
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,22 +83,10 @@ export async function listCustom(query: Record<string, any>) {
|
|||||||
* @returns object
|
* @returns object
|
||||||
*/
|
*/
|
||||||
export async function getCustom(id: string | number) {
|
export async function getCustom(id: string | number) {
|
||||||
// 发起请求
|
return request({
|
||||||
const result = await request({
|
url: `/pm/kpiC/title/${id}`,
|
||||||
url: `/api/rest/databaseManagement/v1/select/omc_db/pm_custom_title`,
|
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
|
||||||
SQL: `select * from pm_custom_title where id = ${id}`,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
// 解析数据
|
|
||||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
|
||||||
let data = result.data.data[0];
|
|
||||||
return Object.assign(result, {
|
|
||||||
data: parseObjLineToHump(data['pm_custom_title'][0]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,21 +95,10 @@ export async function getCustom(id: string | number) {
|
|||||||
* @returns object
|
* @returns object
|
||||||
*/
|
*/
|
||||||
export function addCustom(data: Record<string, any>) {
|
export function addCustom(data: Record<string, any>) {
|
||||||
let obj: any = {
|
|
||||||
title: data.title,
|
|
||||||
ne_type: data.neType,
|
|
||||||
kpi_id: data.kpiId,
|
|
||||||
object_type: data.objectType,
|
|
||||||
expression: data.expression,
|
|
||||||
period: data.period,
|
|
||||||
description: data.description,
|
|
||||||
kpi_set: data.kpiSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
return request({
|
return request({
|
||||||
url: `/api/rest/databaseManagement/v1/omc_db/pm_custom_title`,
|
url: `/pm/kpiC/title`,
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: { 'data': [obj] },
|
data: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,20 +108,10 @@ export function addCustom(data: Record<string, any>) {
|
|||||||
* @returns object
|
* @returns object
|
||||||
*/
|
*/
|
||||||
export function updateCustom(data: Record<string, any>) {
|
export function updateCustom(data: Record<string, any>) {
|
||||||
let obj: any = {
|
|
||||||
title: data.title,
|
|
||||||
ne_type: data.neType,
|
|
||||||
kpi_id: data.kpiId,
|
|
||||||
object_type: data.objectType,
|
|
||||||
expression: data.expression,
|
|
||||||
period: data.period,
|
|
||||||
description: data.description,
|
|
||||||
kpi_set: data.kpiSet,
|
|
||||||
};
|
|
||||||
return request({
|
return request({
|
||||||
url: `/api/rest/databaseManagement/v1/omc_db/pm_custom_title?WHERE=id=${data.id}`,
|
url: `/pm/kpiC/title/${data.id}`,
|
||||||
method: 'put',
|
method: 'put',
|
||||||
data: { data: obj },
|
data: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,8 +121,7 @@ export function updateCustom(data: Record<string, any>) {
|
|||||||
*/
|
*/
|
||||||
export async function delCustom(data: Record<string, any>) {
|
export async function delCustom(data: Record<string, any>) {
|
||||||
return request({
|
return request({
|
||||||
url: `/api/rest/databaseManagement/v1/omc_db/pm_custom_title?WHERE=id=${data.id}`,
|
url: `/pm/kpiC/title/${data.id}`,
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
120
src/api/pt/neConfig.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存为示例配置 (仅管理员操作)
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function ptSaveAsDefault(neType: string, neid: string) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData/saveAsDefault`,
|
||||||
|
method: 'post',
|
||||||
|
data: { neType, neid },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置为示例配置 (仅学生/教师操作)
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function ptResetAsDefault(neType: string) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData/resetAsDefault`,
|
||||||
|
method: 'post',
|
||||||
|
data: { neType },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据比较示例
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function ptContrastAsDefault(params: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData/contrast`,
|
||||||
|
params,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置数据导出Excel
|
||||||
|
* @param student 仅教师 student
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function ptExport(student: string | undefined) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData/export`,
|
||||||
|
method: 'get',
|
||||||
|
params: { student },
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 180_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置数据导出Excel (仅教师全量)
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function ptExportAll() {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData/export-all`,
|
||||||
|
method: 'get',
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 180_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置信息
|
||||||
|
* @param params 数据 {neType,paramName}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function getPtNeConfigData(params: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData`,
|
||||||
|
params,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置数据更新
|
||||||
|
* @param data 数据 {neType,paramName:"参数名",paramData:{参数},loc:"层级index仅array"}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function editPtNeConfigData(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData`,
|
||||||
|
method: 'put',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置新增(array)
|
||||||
|
* @param data 数据 {neType,paramName:"参数名",paramData:{参数},loc:"层级index"}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function addPtNeConfigData(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData`,
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置删除(array)
|
||||||
|
* @param params 数据 {neType,paramName:"参数名",loc:"层级index"}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function delPtNeConfigData(params: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigData`,
|
||||||
|
method: 'delete',
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
53
src/api/pt/neConfigApply.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 班级学生列表 (仅教师操作)
|
||||||
|
* @param params 数据 {userName}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function getPtClassStudents(params?: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigApply/students`,
|
||||||
|
params,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置应用申请列表
|
||||||
|
* @param params 数据 {neType,paramName}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function getPtNeConfigApplyList(params: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigApply/list`,
|
||||||
|
params,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置应用申请提交(仅学生操作)
|
||||||
|
* @param data 数据 { "neType": "MME", "status": "1" }
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function stuPtNeConfigApply(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigApply`,
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置应用申请状态变更(仅管理员/教师操作)
|
||||||
|
* @param data 数据 { "applyId": "1", "neType": "MME", "status": "3", "backInfo": "sgw参数错误" }
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function updatePtNeConfigApply(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigApply`,
|
||||||
|
method: 'put',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
27
src/api/pt/neConfigDataLog.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置数据变更日志信息
|
||||||
|
* @param params 数据 {neType,paramName}
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function getPtNeConfigDataLogList(params: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigDataLog`,
|
||||||
|
params,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网元参数配置数据变更日志还原到数据
|
||||||
|
* @param data 数据 { "id": "1", "value": "old" }
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function restorePtNeConfigDataLog(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/pt/neConfigDataLog/restore`,
|
||||||
|
method: 'put',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
42
src/api/pt/user.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入用户模板数据
|
||||||
|
* @param data 表单数据对象
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function importData(data: FormData) {
|
||||||
|
return request({
|
||||||
|
url: '/pt/system/user/importData',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
dataType: 'form-data',
|
||||||
|
timeout: 180_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入用户模板下载
|
||||||
|
* @returns bolb
|
||||||
|
*/
|
||||||
|
export function importTemplate() {
|
||||||
|
return request({
|
||||||
|
url: '/pt/system/user/importTemplate',
|
||||||
|
method: 'get',
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户列表导出
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns bolb
|
||||||
|
*/
|
||||||
|
export function exportUser(query: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/pt/system/user/export',
|
||||||
|
method: 'post',
|
||||||
|
data: query,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
}
|
||||||
20
src/api/tool/iperf.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
// iperf 版本信息
|
||||||
|
export function iperfV(data: Record<string, string>) {
|
||||||
|
return request({
|
||||||
|
url: '/tool/iperf/v',
|
||||||
|
method: 'get',
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// iperf 软件安装
|
||||||
|
export function iperfI(data: Record<string, string>) {
|
||||||
|
return request({
|
||||||
|
url: '/tool/iperf/i',
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { request } from '@/plugins/http-fetch';
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询文件列表列表
|
* 查询网元端文件列表
|
||||||
* @param query 查询参数
|
* @param query 查询参数
|
||||||
* @returns object
|
* @returns object
|
||||||
*/
|
*/
|
||||||
@@ -14,7 +14,7 @@ export function listNeFiles(query: Record<string, any>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从网元端获取文件
|
* 从网元到本地获取文件
|
||||||
* @param query 查询参数
|
* @param query 查询参数
|
||||||
* @returns object
|
* @returns object
|
||||||
*/
|
*/
|
||||||
@@ -27,3 +27,24 @@ export function getNeFile(query: Record<string, any>) {
|
|||||||
timeout: 180_000,
|
timeout: 180_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从网元到本地获取目录压缩为ZIP
|
||||||
|
export function getNeDirZip(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/ne/action/pullDirZip',
|
||||||
|
method: 'get',
|
||||||
|
params: data,
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看网元端文件内容
|
||||||
|
export function getNeViewFile(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/ne/action/viewFile',
|
||||||
|
method: 'get',
|
||||||
|
params: data,
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
10
src/api/tool/ping.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
// ping 网元端版本信息
|
||||||
|
export function pingV(data: Record<string, string>) {
|
||||||
|
return request({
|
||||||
|
url: '/tool/ping/v',
|
||||||
|
method: 'get',
|
||||||
|
params: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
64
src/api/trace/packet.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信令跟踪网卡设备列表
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function packetDevices() {
|
||||||
|
return request({
|
||||||
|
url: '/trace/packet/devices',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信令跟踪开始
|
||||||
|
* @param data 对象
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function packetStart(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/packet/start',
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信令跟踪结束
|
||||||
|
* @param data 对象
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function packetStop(taskNo: string) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/packet/stop',
|
||||||
|
method: 'post',
|
||||||
|
data: { taskNo },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信令跟踪过滤
|
||||||
|
* @param data 对象
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function packetFilter(taskNo: string, expr: string) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/packet/filter',
|
||||||
|
method: 'put',
|
||||||
|
data: { taskNo, expr },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 信令跟踪续期保活
|
||||||
|
* @param data 对象
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function packetKeep(taskNo: string, duration: number = 120) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/packet/keep-alive',
|
||||||
|
method: 'put',
|
||||||
|
data: { taskNo, duration },
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ export function dumpStart(data: Record<string, string>) {
|
|||||||
url: '/trace/tcpdump/start',
|
url: '/trace/tcpdump/start',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: data,
|
data: data,
|
||||||
|
timeout: 60_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,14 +16,16 @@ export function dumpStop(data: Record<string, string>) {
|
|||||||
url: '/trace/tcpdump/stop',
|
url: '/trace/tcpdump/stop',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: data,
|
data: data,
|
||||||
|
timeout: 60_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// UPF标准版内部抓包
|
// UPF标准版内部抓包
|
||||||
export function traceUPF(data: Record<string, string>) {
|
export function traceUPF(data: Record<string, string>) {
|
||||||
return request({
|
return request({
|
||||||
url: '/trace/tcpdump/traceUPF',
|
url: '/trace/tcpdump/upf',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: data,
|
data: data,
|
||||||
|
timeout: 60_000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
104
src/api/trace/task.ts
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询跟踪任务列表
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export async function listTraceTask(query: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/task/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询跟踪任务信息
|
||||||
|
* @param id 网元ID
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export async function getTraceTask(id: string | number) {
|
||||||
|
return request({
|
||||||
|
url: `/trace/task/${id}`,
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增任务
|
||||||
|
* @param data 网元对象
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function addTraceTask(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/trace/task`,
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改任务
|
||||||
|
* @param data 网元对象
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function updateTraceTask(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: `/trace/task`,
|
||||||
|
method: 'put',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跟踪任务删除
|
||||||
|
* @param ids ID多个逗号分隔
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export async function delTraceTask(ids: string) {
|
||||||
|
return request({
|
||||||
|
url: `/trace/task/${ids}`,
|
||||||
|
method: 'delete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跟踪任务文件
|
||||||
|
* @param query 对象
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function filePullTask(traceId: string) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/task/filePull',
|
||||||
|
method: 'get',
|
||||||
|
params: { traceId },
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取网元跟踪接口列表
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export async function getNeTraceInterfaceAll() {
|
||||||
|
// 发起请求
|
||||||
|
const result = await request({
|
||||||
|
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/ne_info`,
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
SQL: `SELECT ne_type,interface FROM trace_info GROUP BY ne_type,interface`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// 解析数据
|
||||||
|
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||||
|
let data = result.data.data[0];
|
||||||
|
return Object.assign(result, {
|
||||||
|
data: parseObjLineToHump(data['trace_info']),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
83
src/api/trace/taskHLR.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { request } from '@/plugins/http-fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询跟踪任务列表
|
||||||
|
* @param query 查询参数
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function listTaskHLR(query: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/task/hlr/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跟踪任务删除
|
||||||
|
* @param ids 任务ID
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function delTaskHLR(ids: string | number) {
|
||||||
|
return request({
|
||||||
|
url: `/trace/task/hlr/${ids}`,
|
||||||
|
method: 'delete',
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跟踪任务创建
|
||||||
|
* @param data 对象
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function startTaskHLR(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/task/hlr/start',
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跟踪任务停止
|
||||||
|
* @param data 对象
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function stopTaskHLR(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/task/hlr/stop',
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跟踪任务文件
|
||||||
|
* @param data 对象
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function fileTaskHLR(data: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/task/hlr/file',
|
||||||
|
method: 'post',
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跟踪任务文件从网元到本地
|
||||||
|
* @param query 对象
|
||||||
|
* @returns object
|
||||||
|
*/
|
||||||
|
export function filePullTaskHLR(query: Record<string, any>) {
|
||||||
|
return request({
|
||||||
|
url: '/trace/task/hlr/filePull',
|
||||||
|
method: 'get',
|
||||||
|
params: query,
|
||||||
|
responseType: 'blob',
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
|
||||||
import { request } from '@/plugins/http-fetch';
|
|
||||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询任务列表
|
|
||||||
* @param query 查询参数
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export async function listTraceTask(query: Record<string, any>) {
|
|
||||||
let totalSQL = 'select count(*) as total from trace_task where 1=1 ';
|
|
||||||
let rowsSQL = 'select * from trace_task where 1=1 ';
|
|
||||||
|
|
||||||
// 查询
|
|
||||||
let querySQL = '';
|
|
||||||
if (query.imsi) {
|
|
||||||
querySQL += ` and imsi like '%${query.imsi}%' `;
|
|
||||||
}
|
|
||||||
if (query.beginTime) {
|
|
||||||
querySQL += ` and start_time >= '${query.beginTime}' `;
|
|
||||||
}
|
|
||||||
if (query.endTime) {
|
|
||||||
querySQL += ` and end_time <= '${query.endTime}' `;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分页
|
|
||||||
const pageNum = (query.pageNum - 1) * query.pageSize;
|
|
||||||
const limtSql = ` limit ${pageNum},${query.pageSize} `;
|
|
||||||
|
|
||||||
// 排序
|
|
||||||
let sortSql = ' order by start_time ';
|
|
||||||
if (query.sortOrder === 'asc') {
|
|
||||||
sortSql += ' asc ';
|
|
||||||
} else {
|
|
||||||
sortSql += ' desc ';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发起请求
|
|
||||||
const result = await request({
|
|
||||||
url: `/api/rest/databaseManagement/v1/select/omc_db/trace_task`,
|
|
||||||
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['trace_task'];
|
|
||||||
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 id 网元ID
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export async function getTraceTask(id: string | number) {
|
|
||||||
// 发起请求
|
|
||||||
const result = await request({
|
|
||||||
url: `/api/rest/databaseManagement/v1/select/omc_db/trace_task`,
|
|
||||||
method: 'get',
|
|
||||||
params: {
|
|
||||||
SQL: `select * from trace_task where id = ${id}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// 解析数据
|
|
||||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
|
||||||
let data = result.data.data[0];
|
|
||||||
return Object.assign(result, {
|
|
||||||
data: parseObjLineToHump(data['trace_task'][0]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增任务
|
|
||||||
* @param data 网元对象
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export function addTraceTask(data: Record<string, any>) {
|
|
||||||
return request({
|
|
||||||
url: `/api/rest/traceManagement/v1/subscriptions`,
|
|
||||||
method: 'post',
|
|
||||||
data: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改任务
|
|
||||||
* @param data 网元对象
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export function updateTraceTask(data: Record<string, any>) {
|
|
||||||
return request({
|
|
||||||
url: `/api/rest/traceManagement/v1/subscriptions`,
|
|
||||||
method: 'put',
|
|
||||||
data: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除任务
|
|
||||||
* @param noticeId 网元ID
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export async function delTraceTask(id: string) {
|
|
||||||
return request({
|
|
||||||
url: `/api/rest/traceManagement/v1/subscriptions?id=${id}`,
|
|
||||||
method: 'delete',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取网元跟踪接口列表
|
|
||||||
* @returns object
|
|
||||||
*/
|
|
||||||
export async function getNeTraceInterfaceAll() {
|
|
||||||
// 发起请求
|
|
||||||
const result = await request({
|
|
||||||
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/ne_info`,
|
|
||||||
method: 'get',
|
|
||||||
params: {
|
|
||||||
SQL: `SELECT ne_type,interface FROM trace_info GROUP BY ne_type,interface`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// 解析数据
|
|
||||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
|
||||||
let data = result.data.data[0];
|
|
||||||
return Object.assign(result, {
|
|
||||||
data: parseObjLineToHump(data['trace_info']),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
BIN
src/assets/background_dark.jpg
Normal file
|
After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
11
src/assets/js/wiregasm_worker.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* worker文件-静态资源文件路径
|
||||||
|
*/
|
||||||
|
const baseUrl = import.meta.env.VITE_HISTORY_BASE_URL;
|
||||||
|
export const scriptUrl = `${
|
||||||
|
baseUrl.length === 1 && baseUrl.indexOf('/') === 0
|
||||||
|
? ''
|
||||||
|
: baseUrl.indexOf('/') === -1
|
||||||
|
? '/' + baseUrl
|
||||||
|
: baseUrl
|
||||||
|
}/wiregasm/worker.js`;
|
||||||
1
src/assets/svg/dark.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="1em" height="1em" class="icon-dark" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" style="vertical-align: -0.125em;color: rgba(255, 255, 255, 0.65);"><g id="Dark-\u9875\u9762-1" stroke="none" stroke-width="1px" fill="none" fill-rule="evenodd"><g id="Dark-\u9ED8\u8BA4" transform="translate(-9.000000, -49.500000)" fill="currentColor" fill-rule="nonzero"><g id="Dark-\u7F16\u7EC4-17" transform="translate(0.000000, 42.500000)"><g id="Dark-moon" transform="translate(9.268811, 7.500000)"><rect id="Dark-\u77E9\u5F62" opacity="0" x="0" y="0" width="16" height="16"></rect><path d="M8,1.33333333 C8.14933333,1.33333333 8.29688889,1.33844444 8.44266667,1.34866666 C8.14755556,1.98422221 8,2.64577777 8,3.33333333 C8,3.96533333 8.12333333,4.56955555 8.37,5.146 C8.61666667,5.72244445 8.94822222,6.21888889 9.36466667,6.63533333 C9.78111112,7.05177777 10.2775556,7.38333332 10.854,7.63 C11.4304444,7.87666668 12.0346667,8.00000001 12.6666667,8 C13.3542222,8 14.0157778,7.85244444 14.6513333,7.55733333 C14.6615556,7.70311111 14.6666667,7.85066667 14.6666667,8 C14.6666667,8.604 14.5868889,9.19422222 14.4273333,9.77066667 C14.2677778,10.3471111 14.0446667,10.8793333 13.758,11.3673333 C13.4713333,11.8553333 13.1233333,12.3042222 12.714,12.714 C12.3046667,13.1237778 11.8557778,13.4717778 11.3673333,13.758 C10.8788889,14.0442222 10.3466667,14.2673333 9.77066667,14.4273333 C9.19466667,14.5873333 8.60444445,14.6671111 8,14.6666685 C7.39555555,14.6662222 6.80533333,14.5864444 6.22933333,14.4273333 C5.65333333,14.2682222 5.1211111,14.0451111 4.63266666,13.758 C4.14422221,13.4708889 3.69533332,13.1228889 3.28599998,12.714 C2.87666665,12.3051111 2.52866665,11.8562222 2.24199998,11.3673333 C1.95533332,10.8784444 1.73222221,10.3462222 1.57266666,9.77066667 C1.4131111,9.19511112 1.33333333,8.6048889 1.33333333,8 C1.33333333,7.3951111 1.4131111,6.80488888 1.57266666,6.22933333 C1.73222221,5.65377778 1.95533332,5.12155555 2.24199998,4.63266666 C2.52866665,4.14377776 2.87666665,3.69488887 3.28599998,3.28599998 C3.69533332,2.8771111 4.14422221,2.5291111 4.63266666,2.24199998 C5.1211111,1.95488887 5.65333333,1.73177776 6.22933333,1.57266666 C6.80533333,1.41355555 7.39555555,1.33377778 8,1.33333333 Z M6.68733333,2.828 C6.11444444,2.97377778 5.58066667,3.20977778 5.086,3.536 C4.59133333,3.86222222 4.166,4.24933333 3.81,4.69733333 C3.454,5.14533333 3.17444444,5.65488889 2.97133333,6.226 C2.76822221,6.79711111 2.66666666,7.38822222 2.66666666,7.99933333 C2.66666666,8.72155555 2.80733332,9.41155555 3.08866666,10.0693333 C3.36999999,10.7271111 3.74933332,11.2948889 4.22666666,11.7726667 C4.70399999,12.2504444 5.27177777,12.6297778 5.92999998,12.9106667 C6.5882222,13.1915556 7.2782222,13.3322222 7.99999998,13.3326667 C8.6111111,13.3326667 9.20222221,13.2311111 9.77333331,13.028 C10.3444444,12.8248889 10.854,12.5453333 11.302,12.1893333 C11.75,11.8333333 12.1371111,11.408 12.4633333,10.9133333 C12.7895555,10.4186666 13.0255555,9.88488887 13.1713333,9.31199998 C13.022,9.32577777 12.8535555,9.33266666 12.666,9.33266666 C11.8535555,9.33266666 11.0775555,9.17377777 10.338,8.85599998 C9.59844443,8.5382222 8.96044443,8.11111109 8.42399998,7.57466666 C7.88755554,7.03822222 7.46044443,6.40022222 7.14266666,5.66066666 C6.82488889,4.92111109 6.66599999,4.14511109 6.66599998,3.33266666 C6.66599998,3.1451111 6.67288888,2.97666666 6.68666666,2.82733333 L6.68733333,2.828 Z" id="Dark-\u5F62\u72B6"></path></g></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 3.4 KiB |
1
src/assets/svg/light.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="1em" height="1em" class="icon-light" viewBox="0 0 13 13" xmlns="http://www.w3.org/2000/svg" style="vertical-align: -0.125em;color: rgba(0, 0, 0, 0.88);"><g id="Light-\u9875\u9762-1" stroke="none" stroke-width="1px" fill="none" fill-rule="evenodd"><g id="Light-\u4E3B\u9898\u5305" transform="translate(-2943.000000, -292.000000)" fill="currentColor" fill-rule="nonzero"><g id="Light-\u7F16\u7EC4-12" transform="translate(2415.000000, 222.000000)"><g id="Light-\u89C6\u56FE\u5207\u6362-\u7F16\u8F91\u6001" transform="translate(518.000000, 60.000000)"><g id="Light-eye" transform="translate(8.000000, 8.000000)"><g id="Light-sun" transform="translate(2.000000, 2.000000)"><rect id="Light-\u77E9\u5F62" opacity="0" x="0" y="0" width="13" height="13"></rect><path d="M6.5,9.75 C4.7051875,9.75 3.25,8.2948125 3.25,6.5 C3.25,4.7051875 4.7051875,3.25 6.5,3.25 C8.2948125,3.25 9.75,4.7051875 9.75,6.5 C9.75,8.2948125 8.2948125,9.75 6.5,9.75 Z M6.5,8.66666667 C7.69661696,8.66666667 8.66666667,7.69661696 8.66666667,6.5 C8.66666667,5.30338304 7.69661696,4.33333333 6.5,4.33333333 C5.30338305,4.33333333 4.33333336,5.30338305 4.33333336,6.5 C4.33333336,7.69661695 5.30338305,8.66666667 6.5,8.66666667 Z M5.95833333,1.08333333 C5.95833333,0.784179087 6.20084576,0.541666658 6.5,0.541666658 C6.79915424,0.541666658 7.04166667,0.784179087 7.04166667,1.08333333 L7.04166667,2.16666667 C7.04166667,2.46582091 6.79915424,2.70833334 6.5,2.70833334 C6.20084576,2.70833334 5.95833333,2.46582091 5.95833333,2.16666667 L5.95833333,1.08333333 L5.95833333,1.08333333 Z M5.95833333,10.8333333 C5.95833333,10.5341791 6.20084576,10.2916667 6.5,10.2916667 C6.79915424,10.2916667 7.04166667,10.5341791 7.04166667,10.8333333 L7.04166667,11.9166667 C7.04166667,12.2158209 6.79915424,12.4583333 6.5,12.4583333 C6.20084576,12.4583333 5.95833333,12.2158209 5.95833333,11.9166667 L5.95833333,10.8333333 L5.95833333,10.8333333 Z M1.08333333,7.04166667 C0.784179087,7.04166667 0.541666658,6.79915424 0.541666658,6.5 C0.541666658,6.20084576 0.784179087,5.95833333 1.08333333,5.95833333 L2.16666667,5.95833333 C2.46582091,5.95833333 2.70833334,6.20084576 2.70833334,6.5 C2.70833334,6.79915424 2.46582091,7.04166667 2.16666667,7.04166667 L1.08333333,7.04166667 L1.08333333,7.04166667 Z M10.8333333,7.04166667 C10.5341791,7.04166667 10.2916667,6.79915424 10.2916667,6.5 C10.2916667,6.20084576 10.5341791,5.95833333 10.8333333,5.95833333 L11.9166667,5.95833333 C12.2158209,5.95833333 12.4583333,6.20084576 12.4583333,6.5 C12.4583333,6.79915424 12.2158209,7.04166667 11.9166667,7.04166667 L10.8333333,7.04166667 L10.8333333,7.04166667 Z M2.05454167,2.82045833 C1.84926545,2.60791971 1.85220137,2.27007933 2.06114035,2.06114035 C2.27007933,1.85220137 2.60791971,1.84926545 2.82045833,2.05454167 L3.63295833,2.86704167 C3.83823455,3.07958029 3.83529863,3.41742067 3.62635965,3.62635965 C3.41742067,3.83529863 3.07958029,3.83823455 2.86704167,3.63295833 L2.05454167,2.82045833 L2.05454167,2.82045833 Z M9.36704167,10.1329583 C9.16176545,9.92041971 9.16470137,9.58257933 9.37364035,9.37364035 C9.58257933,9.16470137 9.92041971,9.16176545 10.1329583,9.36704167 L10.9454583,10.1795417 C11.1507346,10.3920803 11.1477986,10.7299207 10.9388596,10.9388596 C10.7299207,11.1477986 10.3920803,11.1507346 10.1795417,10.9454583 L9.36704167,10.1329583 L9.36704167,10.1329583 Z M2.82045833,10.9454583 C2.60791971,11.1507346 2.27007933,11.1477986 2.06114035,10.9388596 C1.85220137,10.7299207 1.84926545,10.3920803 2.05454167,10.1795417 L2.86704167,9.36704167 C3.07958029,9.16176545 3.41742067,9.16470137 3.62635965,9.37364035 C3.83529863,9.58257933 3.83823455,9.92041971 3.63295833,10.1329583 L2.82045833,10.9454583 L2.82045833,10.9454583 Z M10.1329583,3.63295833 C9.92041971,3.83823455 9.58257933,3.83529863 9.37364035,3.62635965 C9.16470137,3.41742067 9.16176545,3.07958029 9.36704167,2.86704167 L10.1795417,2.05454167 C10.3920803,1.84926545 10.7299207,1.85220137 10.9388596,2.06114035 C11.1477986,2.27007933 11.1507346,2.60791971 10.9454583,2.82045833 L10.1329583,3.63295833 L10.1329583,3.63295833 Z" id="Light-\u5F62\u72B6"></path></g></g></g></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -3,7 +3,7 @@
|
|||||||
:drag="true"
|
:drag="true"
|
||||||
:destroyOnClose="true"
|
:destroyOnClose="true"
|
||||||
:title="t('components.CronModal.title')"
|
:title="t('components.CronModal.title')"
|
||||||
:visible="props.visible"
|
:open="props.open"
|
||||||
:body-style="{ padding: '0 24px' }"
|
:body-style="{ padding: '0 24px' }"
|
||||||
@cancel="fnCronModal(false)"
|
@cancel="fnCronModal(false)"
|
||||||
@ok="fnCronModal(true)"
|
@ok="fnCronModal(true)"
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
</ProModal>
|
</ProModal>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ProModal } from 'antdv-pro-modal';
|
||||||
import CronSecond from './components/Second.vue';
|
import CronSecond from './components/Second.vue';
|
||||||
import CronMinute from './components/Minute.vue';
|
import CronMinute from './components/Minute.vue';
|
||||||
import CronHour from './components/Hour.vue';
|
import CronHour from './components/Hour.vue';
|
||||||
@@ -44,9 +45,9 @@ import { reactive, computed, watch } from 'vue';
|
|||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const emit = defineEmits(['cancel', 'ok', 'update:visible']);
|
const emit = defineEmits(['cancel', 'ok', 'update:open']);
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
open: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
@@ -75,7 +76,7 @@ const cronStr = computed(() => {
|
|||||||
|
|
||||||
/**监听是否显示,初始cron属性 */
|
/**监听是否显示,初始cron属性 */
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.open,
|
||||||
val => {
|
val => {
|
||||||
if (!val) return;
|
if (!val) return;
|
||||||
const arr = props.cron.split(' ');
|
const arr = props.cron.split(' ');
|
||||||
@@ -98,7 +99,7 @@ watch(
|
|||||||
* @param val modal触发事件
|
* @param val modal触发事件
|
||||||
*/
|
*/
|
||||||
function fnCronModal(val: boolean) {
|
function fnCronModal(val: boolean) {
|
||||||
emit('update:visible', false);
|
emit('update:open', false);
|
||||||
if (val) {
|
if (val) {
|
||||||
emit('ok', cronStr.value);
|
emit('ok', cronStr.value);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const router = useRouter();
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
/**显示遮罩 */
|
/**显示遮罩 */
|
||||||
const isVisible = computed(() => !['none', 'lock'].includes(maskStore.type));
|
const isOpen = computed(() => !['none', 'lock'].includes(maskStore.type));
|
||||||
|
|
||||||
// 用户无操作一段时间后进行锁屏
|
// 用户无操作一段时间后进行锁屏
|
||||||
function idleTimeout(time: number, callback: Function) {
|
function idleTimeout(time: number, callback: Function) {
|
||||||
@@ -67,7 +67,7 @@ onUnmounted(() => {});
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<a-modal
|
<a-modal
|
||||||
v-model:visible="isVisible"
|
v-model:open="isOpen"
|
||||||
get-container="#app"
|
get-container="#app"
|
||||||
:footer="null"
|
:footer="null"
|
||||||
:zIndex="1008"
|
:zIndex="1008"
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- https://github.com/jackocnr/intl-tel-input/blob/master/react/src/intl-tel-input/react.tsx -->
|
<!-- https://github.com/jackocnr/intl-tel-input/blob/master/react/src/intl-tel-input/react.tsx -->
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import intlTelInput, { Iti, SomeOptions } from 'intl-tel-input';
|
import { Iti } from 'intl-tel-input';
|
||||||
|
import intlTelInput from 'intl-tel-input/intlTelInputWithUtils';
|
||||||
import 'intl-tel-input/build/css/intlTelInput.min.css';
|
import 'intl-tel-input/build/css/intlTelInput.min.css';
|
||||||
import 'intl-tel-input/build/js/utils.js';
|
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||||
import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
|
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
const { currentLocale } = useI18n();
|
const { currentLocale } = useI18n();
|
||||||
const emit = defineEmits(['update:value', 'update:change']);
|
const emit = defineEmits(['update:value', 'update:change']);
|
||||||
@@ -45,13 +45,13 @@ const itiRef = ref<Iti | null>(null);
|
|||||||
function fnChange() {
|
function fnChange() {
|
||||||
if (!itiRef.value) return;
|
if (!itiRef.value) return;
|
||||||
|
|
||||||
const num = itiRef.value?.getNumber() || '';
|
const number = itiRef.value?.getNumber() || '';
|
||||||
const countryIso = itiRef.value?.getSelectedCountryData().iso2 || '';
|
const countryIso = itiRef.value?.getSelectedCountryData().iso2 || '';
|
||||||
// note: this number will be in standard E164 format, but any container component can use
|
// note: this number will be in standard E164 format, but any container component can use
|
||||||
// intlTelInputUtils.formatNumber() to convert this to another format
|
// intlTelInputUtils.formatNumber() to convert this to another format
|
||||||
// as well as intlTelInputUtils.getNumberType() etc. if need be
|
// as well as intlTelInputUtils.getNumberType() etc. if need be
|
||||||
let data = {
|
let data = {
|
||||||
num,
|
number,
|
||||||
countryIso,
|
countryIso,
|
||||||
validity: false,
|
validity: false,
|
||||||
errorCode: -1,
|
errorCode: -1,
|
||||||
@@ -69,21 +69,11 @@ function fnChange() {
|
|||||||
data.errorCode = errorCode;
|
data.errorCode = errorCode;
|
||||||
}
|
}
|
||||||
// console.log(data);
|
// console.log(data);
|
||||||
emit('update:value', num);
|
|
||||||
|
emit('update:value', number);
|
||||||
emit('update:change', data);
|
emit('update:change', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.value,
|
|
||||||
v => {
|
|
||||||
if (v) {
|
|
||||||
itiRef.value?.setNumber(v);
|
|
||||||
} else {
|
|
||||||
itiRef.value?.setNumber('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(async () => {
|
nextTick(async () => {
|
||||||
if (inputRef.value) {
|
if (inputRef.value) {
|
||||||
@@ -106,7 +96,13 @@ onMounted(() => {
|
|||||||
formatOnDisplay: true,
|
formatOnDisplay: true,
|
||||||
autoPlaceholder: 'polite',
|
autoPlaceholder: 'polite',
|
||||||
i18n: i18n,
|
i18n: i18n,
|
||||||
} as SomeOptions);
|
});
|
||||||
|
if (props.value) {
|
||||||
|
itiRef.value.setNumber(props.value);
|
||||||
|
}
|
||||||
|
if (props.disabled) {
|
||||||
|
itiRef.value.setDisabled(props.disabled);
|
||||||
|
}
|
||||||
inputRef.value.addEventListener('countrychange', fnChange);
|
inputRef.value.addEventListener('countrychange', fnChange);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -124,8 +120,7 @@ onBeforeUnmount(() => {
|
|||||||
<input
|
<input
|
||||||
type="tel"
|
type="tel"
|
||||||
class="ant-input"
|
class="ant-input"
|
||||||
ref="inputRef"
|
ref="inputRef"
|
||||||
:value="value"
|
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:maxlength="maxlength"
|
:maxlength="maxlength"
|
||||||
@@ -142,4 +137,32 @@ onBeforeUnmount(() => {
|
|||||||
.iti .iti__country-container .iti__search-input {
|
.iti .iti__country-container .iti__search-input {
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.iti .ant-input {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
font-variant: tabular-nums;
|
||||||
|
list-style: none;
|
||||||
|
font-feature-settings: 'tnum';
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 4px 11px;
|
||||||
|
color: #000000d9;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5715;
|
||||||
|
background-color: transparent;
|
||||||
|
background-image: none;
|
||||||
|
border: 1px solid #424242;
|
||||||
|
border-radius: 6px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
.iti .ant-input:focus,
|
||||||
|
.iti .ant-input-focused {
|
||||||
|
border-color: var(--ant-primary-color-hover);
|
||||||
|
box-shadow: 0 0 0 2px var(--ant-primary-color-outline);
|
||||||
|
border-right-width: 1px !important;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { reactive, watch, onMounted, PropType, nextTick } from 'vue';
|
import { reactive, watch, onMounted, PropType, nextTick } from 'vue';
|
||||||
import { Container, Draggable } from 'vue3-smooth-dnd';
|
import { Container, Draggable } from 'vue3-smooth-dnd';
|
||||||
import useI18n from '@/hooks/useI18n';
|
import useI18n from '@/hooks/useI18n';
|
||||||
import { type ColumnsType } from 'ant-design-vue/lib/table';
|
|
||||||
import { dbGetJSON, dbSetJSON } from '@/utils/cache-db-utils';
|
import { dbGetJSON, dbSetJSON } from '@/utils/cache-db-utils';
|
||||||
import { CACHE_DB_TABLE_DND } from '@/constants/cache-keys-constants';
|
import { CACHE_DB_TABLE_DND } from '@/constants/cache-keys-constants';
|
||||||
const { t, currentLocale } = useI18n();
|
const { t, currentLocale } = useI18n();
|
||||||
@@ -37,7 +36,7 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**表格字段列 */
|
/**表格字段列 */
|
||||||
const tableColumns = reactive<ColumnsType>(props.columns);
|
const tableColumns = reactive(props.columns);
|
||||||
|
|
||||||
/**表格字段列勾选状态 */
|
/**表格字段列勾选状态 */
|
||||||
const state = reactive<{
|
const state = reactive<{
|
||||||
@@ -56,7 +55,9 @@ const state = reactive<{
|
|||||||
function fnTableColumnsCheckAllChange(e: any) {
|
function fnTableColumnsCheckAllChange(e: any) {
|
||||||
const checked = e.target.checked;
|
const checked = e.target.checked;
|
||||||
state.indeterminate = false;
|
state.indeterminate = false;
|
||||||
state.columnsTitleList = checked ? tableColumns.map(s => `${s.title}`) : [];
|
state.columnsTitleList = checked
|
||||||
|
? tableColumns.map(s => `${s.title as string}`)
|
||||||
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**表格字段列拖拽操作 */
|
/**表格字段列拖拽操作 */
|
||||||
|
|||||||
225
src/components/TerminalRedis/index.vue
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
<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';
|
||||||
|
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
|
||||||
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
|
const ws = new WS();
|
||||||
|
const emit = defineEmits(['connect', 'close', 'message']);
|
||||||
|
const props = defineProps({
|
||||||
|
/**终端ID,必传 */
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/**连接主机ID,必传 */
|
||||||
|
hostId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/**初始发送命令 */
|
||||||
|
initCmd: {
|
||||||
|
type: [String, Boolean],
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**终端输入DOM节点实例对象 */
|
||||||
|
const terminalDom = ref<HTMLElement | undefined>(undefined);
|
||||||
|
|
||||||
|
/**终端输入实例对象 */
|
||||||
|
const terminal = ref<any>(null);
|
||||||
|
|
||||||
|
/**终端输入命令 */
|
||||||
|
const terminalCmd = ref<string>('');
|
||||||
|
|
||||||
|
/**终端输入渲染 */
|
||||||
|
function handleRanderXterm(container: HTMLElement | undefined) {
|
||||||
|
if (!container) return;
|
||||||
|
const xterm = new Terminal({
|
||||||
|
lineHeight: 1.2,
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
|
||||||
|
theme: {
|
||||||
|
background: '#000000',
|
||||||
|
},
|
||||||
|
cursorBlink: true, // 光标闪烁
|
||||||
|
cursorStyle: 'block',
|
||||||
|
scrollback: 1000,
|
||||||
|
scrollSensitivity: 15,
|
||||||
|
tabStopWidth: 4,
|
||||||
|
disableStdin: false, // 禁止输入
|
||||||
|
});
|
||||||
|
// 挂载
|
||||||
|
xterm.open(container);
|
||||||
|
// 自适应尺寸
|
||||||
|
const fitAddon = new FitAddon();
|
||||||
|
xterm.loadAddon(fitAddon);
|
||||||
|
// 终端输入字符按键监听
|
||||||
|
xterm.onKey(({ key, domEvent }) => {
|
||||||
|
// console.log(key, domEvent);
|
||||||
|
// 单键输入
|
||||||
|
switch (domEvent.key) {
|
||||||
|
case 'Enter':
|
||||||
|
const cmdStr = terminalCmd.value.trim();
|
||||||
|
// 发送文本
|
||||||
|
terminal.value.scrollToBottom();
|
||||||
|
terminal.value.writeln('\r\n');
|
||||||
|
ws.send({
|
||||||
|
requestId: `redis_${props.hostId}`,
|
||||||
|
type: 'redis',
|
||||||
|
data: `${cmdStr}\r\n`,
|
||||||
|
});
|
||||||
|
terminalCmd.value = '';
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
if ('quit' === cmdStr) {
|
||||||
|
setTimeout(() => {
|
||||||
|
ws.close();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Backspace':
|
||||||
|
// 处理退格键,删除最后一个字符
|
||||||
|
xterm.write('\b \b');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
xterm.write(key);
|
||||||
|
terminalCmd.value += key;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 创建 ResizeObserver 实例
|
||||||
|
var observer = new ResizeObserver(entries => {
|
||||||
|
fitAddon.fit();
|
||||||
|
});
|
||||||
|
// 监听元素大小变化
|
||||||
|
observer.observe(container);
|
||||||
|
|
||||||
|
terminal.value = xterm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**连接打开后回调 */
|
||||||
|
function wsOpen(ev: any) {
|
||||||
|
// console.info('wsOpen', ev);
|
||||||
|
nextTick(() => {
|
||||||
|
handleRanderXterm(terminalDom.value);
|
||||||
|
|
||||||
|
// 连接事件
|
||||||
|
emit('connect', {
|
||||||
|
timeStamp: ev.timeStamp,
|
||||||
|
cols: terminal.value.cols,
|
||||||
|
rows: terminal.value.rows,
|
||||||
|
hostId: props.hostId,
|
||||||
|
id: props.id,
|
||||||
|
});
|
||||||
|
// 初始发送命令
|
||||||
|
if (typeof props.initCmd === 'string') {
|
||||||
|
ws.send({
|
||||||
|
requestId: `redis_${props.hostId}`,
|
||||||
|
type: 'redis',
|
||||||
|
data: `${props.initCmd}\r\n`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**连接错误后回调 */
|
||||||
|
function wsError(ev: any) {
|
||||||
|
console.error('wsError', ev);
|
||||||
|
if (terminal.value != null) {
|
||||||
|
let message = 'disconnected';
|
||||||
|
terminal.value.write(`\x1b[31m${message}\x1b[m\r\n`);
|
||||||
|
} else if (terminalDom.value) {
|
||||||
|
terminalDom.value.style.background = '#000';
|
||||||
|
terminalDom.value.style.color = '#ff4d4f';
|
||||||
|
terminalDom.value.style.height = '60%';
|
||||||
|
terminalDom.value.innerText = 'disconnected';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**连接关闭后回调 */
|
||||||
|
function wsClose(code: number) {
|
||||||
|
// console.warn('wsClose', code);
|
||||||
|
if (terminal.value != null) {
|
||||||
|
let message = 'disconnected';
|
||||||
|
terminal.value.write(`\x1b[31m${message}\x1b[m\r\n`);
|
||||||
|
}
|
||||||
|
// 关闭事件
|
||||||
|
emit('close', {
|
||||||
|
code: code,
|
||||||
|
hostId: props.hostId,
|
||||||
|
id: props.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收消息后回调 */
|
||||||
|
function wsMessage(res: Record<string, any>) {
|
||||||
|
emit('message', res);
|
||||||
|
// console.log('wsMessage', res);
|
||||||
|
const { code, requestId, data } = res;
|
||||||
|
if (code === RESULT_CODE_ERROR) {
|
||||||
|
console.warn(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!requestId) return;
|
||||||
|
if (data.indexOf('is empty') > 0) return;
|
||||||
|
if (terminal.value != null) {
|
||||||
|
// terminal.value.write(data.trim().replace(/\n/g, "\r\n"));
|
||||||
|
// 是否n结尾
|
||||||
|
if (/[\r\n]$/.test(data)) {
|
||||||
|
terminal.value.writeln(data.trim().replace(/\n/g, '\r\n'));
|
||||||
|
} else {
|
||||||
|
terminal.value.write(data.replace(/\n/g, '\r\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.hostId) {
|
||||||
|
// 建立链接
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: '/ws/redis',
|
||||||
|
params: {
|
||||||
|
hostId: props.hostId,
|
||||||
|
},
|
||||||
|
onmessage: wsMessage,
|
||||||
|
onerror: wsError,
|
||||||
|
onopen: wsOpen,
|
||||||
|
onclose: wsClose,
|
||||||
|
};
|
||||||
|
ws.connect(options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
ws.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 给组件设置属性 ref="xxxTerminal"
|
||||||
|
// setup内使用 const xxxTerminal = ref();
|
||||||
|
defineExpose({
|
||||||
|
/**发送方法 */
|
||||||
|
send: (data: string) => {
|
||||||
|
ws.send({
|
||||||
|
requestId: `redis_${props.hostId}`,
|
||||||
|
type: 'redis',
|
||||||
|
data: `${data}\r\n`,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="terminal">
|
||||||
|
<div ref="terminalDom" :id="id" class="terminal"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.terminal {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -126,7 +126,7 @@ function handleRanderXterm(container: HTMLElement | undefined) {
|
|||||||
// console.log('尺寸', cols, rows);
|
// console.log('尺寸', cols, rows);
|
||||||
ws.send({
|
ws.send({
|
||||||
requestId: `ssh_resize_${props.hostId}`,
|
requestId: `ssh_resize_${props.hostId}`,
|
||||||
type: 'ssh_resize',
|
type: 'resize',
|
||||||
data: { cols, rows },
|
data: { cols, rows },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
264
src/components/TerminalSSHView/index.vue
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
<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';
|
||||||
|
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
|
||||||
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
|
const ws = new WS();
|
||||||
|
const emit = defineEmits(['connect', 'close', 'message']);
|
||||||
|
const props = defineProps({
|
||||||
|
/**终端ID,必传 */
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/**ws连接地址,必传 如/ws/view */
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/**网元类型,必传 */
|
||||||
|
neType: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/**网元ID,必传 */
|
||||||
|
neId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
/**窗口单行字符数 */
|
||||||
|
cols: {
|
||||||
|
type: Number,
|
||||||
|
default: 80,
|
||||||
|
},
|
||||||
|
/**窗口行数 */
|
||||||
|
rows: {
|
||||||
|
type: Number,
|
||||||
|
default: 40,
|
||||||
|
},
|
||||||
|
/**ws发送requestId前缀 如ssh_id */
|
||||||
|
prefix: {
|
||||||
|
type: String,
|
||||||
|
default: 'ssh',
|
||||||
|
},
|
||||||
|
/**消息处理函数 */
|
||||||
|
processMessages: {
|
||||||
|
type: Function,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**终端输入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: true, // 光标闪烁
|
||||||
|
cursorStyle: 'block',
|
||||||
|
scrollback: 1000, // 设置历史缓冲区大小为 1000 行
|
||||||
|
scrollSensitivity: 15,
|
||||||
|
tabStopWidth: 4,
|
||||||
|
disableStdin: true, // 禁止输入
|
||||||
|
});
|
||||||
|
// 挂载
|
||||||
|
xterm.open(container);
|
||||||
|
// 自适应尺寸
|
||||||
|
const fitAddon = new FitAddon();
|
||||||
|
xterm.loadAddon(fitAddon);
|
||||||
|
// 终端尺寸变化触发
|
||||||
|
xterm.onResize(({ cols, rows }) => {
|
||||||
|
// console.log('尺寸', cols, rows);
|
||||||
|
ws.send({
|
||||||
|
requestId: `resize_${props.id}`,
|
||||||
|
type: 'resize',
|
||||||
|
data: { cols, rows },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建 ResizeObserver 实例
|
||||||
|
var observer = new ResizeObserver(entries => {
|
||||||
|
fitAddon.fit();
|
||||||
|
});
|
||||||
|
// 监听元素大小变化
|
||||||
|
observer.observe(container);
|
||||||
|
|
||||||
|
terminal.value = xterm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**连接打开后回调 */
|
||||||
|
function wsOpen(ev: any) {
|
||||||
|
// console.info('wsOpen', ev);
|
||||||
|
nextTick(() => {
|
||||||
|
handleRanderXterm(terminalDom.value);
|
||||||
|
// 连接事件
|
||||||
|
emit('connect', {
|
||||||
|
timeStamp: ev.timeStamp,
|
||||||
|
cols: terminal.value.cols,
|
||||||
|
rows: terminal.value.rows,
|
||||||
|
neType: props.neType,
|
||||||
|
neId: props.neId,
|
||||||
|
id: props.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**连接错误后回调 */
|
||||||
|
function wsError(ev: any) {
|
||||||
|
console.error('wsError', ev);
|
||||||
|
if (terminal.value != null) {
|
||||||
|
let message = 'disconnected';
|
||||||
|
terminal.value.write(`\x1b[31m${message}\x1b[m\r\n`);
|
||||||
|
} else if (terminalDom.value) {
|
||||||
|
terminalDom.value.style.background = '#000';
|
||||||
|
terminalDom.value.style.color = '#ff4d4f';
|
||||||
|
terminalDom.value.style.height = '60%';
|
||||||
|
terminalDom.value.innerText = 'disconnected';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**连接关闭后回调 */
|
||||||
|
function wsClose(code: number) {
|
||||||
|
// console.warn('wsClose', code);
|
||||||
|
if (terminal.value != null) {
|
||||||
|
let message = 'disconnected ' + code;
|
||||||
|
terminal.value.write(`\x1b[31m${message}\x1b[m\r\n`);
|
||||||
|
}
|
||||||
|
// 关闭事件
|
||||||
|
emit('close', {
|
||||||
|
code: code,
|
||||||
|
neType: props.neType,
|
||||||
|
neId: props.neId,
|
||||||
|
id: props.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**接收消息后回调 */
|
||||||
|
function wsMessage(res: Record<string, any>) {
|
||||||
|
emit('message', res);
|
||||||
|
// console.log('wsMessage', res);
|
||||||
|
const { code, requestId, data } = res;
|
||||||
|
if (code === RESULT_CODE_ERROR) {
|
||||||
|
console.warn(res.msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!requestId) return;
|
||||||
|
if (terminal.value != null) {
|
||||||
|
let text = '';
|
||||||
|
// 处理消息
|
||||||
|
if (props.processMessages) {
|
||||||
|
text = props.processMessages(data);
|
||||||
|
}else{
|
||||||
|
text = processMessage(data);
|
||||||
|
}
|
||||||
|
// 无消息是则不输出
|
||||||
|
if (text === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
terminal.value.write(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**终端消息处理*/
|
||||||
|
function processMessage(data: string): string {
|
||||||
|
// 查找的开始输出标记
|
||||||
|
const parts: string[] = data.split('\u001b[?2004l\r');
|
||||||
|
if (parts.length > 0) {
|
||||||
|
if (parts[0].startsWith('^C') || parts[0].startsWith('\r')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
let text = parts[parts.length - 1];
|
||||||
|
// 找到最后输出标记
|
||||||
|
let lestIndex = text.lastIndexOf('\u001b[?2004h\u001b]0;');
|
||||||
|
if (lestIndex !== -1) {
|
||||||
|
text = text.substring(0, lestIndex);
|
||||||
|
}
|
||||||
|
if (text === '' || text === '\r\n' || text.startsWith('^C\r\n')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
// 是否还有最后输出标记
|
||||||
|
lestIndex = text.lastIndexOf('\u001b[?2004h');
|
||||||
|
if (lestIndex !== -1) {
|
||||||
|
text = text.substring(0, lestIndex);
|
||||||
|
}
|
||||||
|
// console.log({ parts, text });
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.neType && props.neId) {
|
||||||
|
// 建立链接
|
||||||
|
const options: OptionsType = {
|
||||||
|
url: props.url,
|
||||||
|
params: {
|
||||||
|
neType: props.neType,
|
||||||
|
neId: props.neId,
|
||||||
|
cols: props.cols,
|
||||||
|
rows: props.rows,
|
||||||
|
},
|
||||||
|
onmessage: wsMessage,
|
||||||
|
onerror: wsError,
|
||||||
|
onopen: wsOpen,
|
||||||
|
onclose: wsClose,
|
||||||
|
};
|
||||||
|
ws.connect(options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (ws.state() === WebSocket.OPEN) ws.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 给组件设置属性 ref="xxxTerminal"
|
||||||
|
// setup内使用 const xxxTerminal = ref();
|
||||||
|
defineExpose({
|
||||||
|
/**清除 */
|
||||||
|
clear: () => {
|
||||||
|
if (terminal.value != null) {
|
||||||
|
terminal.value.clear();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**发送命令 */
|
||||||
|
send: (type: string, data: Record<string, any>) => {
|
||||||
|
ws.send({
|
||||||
|
requestId: `${props.prefix}_${props.id}`,
|
||||||
|
type,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**模拟按下 Ctrl+C */
|
||||||
|
ctrlC: () => {
|
||||||
|
ws.send({
|
||||||
|
requestId: `${props.prefix}_${props.id}`,
|
||||||
|
type: 'ctrl-c',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div ref="terminalDom" :id="id" class="terminal"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.terminal {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||