Compare commits
60 Commits
nbi
...
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 |
@@ -1,5 +1,5 @@
|
||||
# 历史路径-哈希带井号标识
|
||||
VITE_HISTORY_HASH = true
|
||||
VITE_HISTORY_HASH = false
|
||||
|
||||
# 历史路径-前缀URL如:/h5
|
||||
VITE_HISTORY_BASE_URL = "/"
|
||||
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
||||
VITE_APP_CODE = "OMC"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "2.250412"
|
||||
VITE_APP_VERSION = "2.241123-fix"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
||||
VITE_APP_CODE = "OMC"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "2.250412"
|
||||
VITE_APP_VERSION = "2.241123-fix"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
80
package.json
80
package.json
@@ -8,54 +8,54 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "7.0.1",
|
||||
"@antv/g6": "4.8.24",
|
||||
"@codemirror/lang-javascript": "6.2.3",
|
||||
"@codemirror/lang-yaml": "6.1.2",
|
||||
"@codemirror/merge": "6.10.0",
|
||||
"@codemirror/theme-one-dark": "6.1.2",
|
||||
"@tato30/vue-pdf": "1.11.3",
|
||||
"@vueuse/core": "13.0.0",
|
||||
"@xterm/addon-fit": "0.10.0",
|
||||
"@xterm/xterm": "5.5.0",
|
||||
"ant-design-vue": "4.2.6",
|
||||
"antdv-pro-layout": "4.2.0",
|
||||
"antdv-pro-modal": "4.0.6",
|
||||
"codemirror": "6.0.1",
|
||||
"crypto-js": "4.2.0",
|
||||
"dayjs": "1.11.13",
|
||||
"echarts": "5.6.0",
|
||||
"file-saver": "2.0.5",
|
||||
"grid-layout-plus": "1.0.6",
|
||||
"intl-tel-input": "25.2.0",
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@antv/g6": "~4.8.24",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/lang-yaml": "^6.1.1",
|
||||
"@codemirror/merge": "^6.7.2",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@tato30/vue-pdf": "^1.11.2",
|
||||
"@vueuse/core": "11.2.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"ant-design-vue": "^4.2.5",
|
||||
"antdv-pro-layout": "^4.1.9",
|
||||
"antdv-pro-modal": "^4.0.5",
|
||||
"codemirror": "^6.0.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.11",
|
||||
"echarts": "~5.5.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"grid-layout-plus": "^1.0.5",
|
||||
"intl-tel-input": "^24.6.0",
|
||||
"js-base64": "^3.7.7",
|
||||
"js-cookie": "^3.0.5",
|
||||
"localforage": "^1.10.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"p-queue": "8.0.1",
|
||||
"pinia": "2.3.0",
|
||||
"vue": "3.5.13",
|
||||
"vue-i18n": "11.1.2",
|
||||
"vue-router": "4.5.0",
|
||||
"vue3-smooth-dnd": "0.0.6",
|
||||
"xlsx": "0.18.5"
|
||||
"p-queue": "~8.0.1",
|
||||
"pinia": "2.2.6",
|
||||
"vue": "^3.5.12",
|
||||
"vue-i18n": "^10.0.4",
|
||||
"vue-router": "^4.4.5",
|
||||
"vue3-smooth-dnd": "^0.0.6",
|
||||
"xlsx": "~0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/file-saver": "2.0.7",
|
||||
"@types/js-cookie": "3.0.6",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@vitejs/plugin-vue": "5.2.3",
|
||||
"less": "4.2.2",
|
||||
"typescript": "5.8.2",
|
||||
"unplugin-vue-components": "0.28.0",
|
||||
"vite": "6.2.2",
|
||||
"vite-plugin-compression": "0.5.1",
|
||||
"vue-tsc": "2.2.8"
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^22.7.7",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"less": "^4.2.0",
|
||||
"typescript": "^5.6.3",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "5.4.10",
|
||||
"vite-plugin-compression": "~0.5.1",
|
||||
"vue-tsc": "^2.1.8"
|
||||
}
|
||||
}
|
||||
|
||||
27
practical_training/README.md
Normal file
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
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
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
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
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/en-US.ts
Normal file
File diff suppressed because it is too large
Load Diff
2201
practical_training/i18n/locales/zh-CN.ts
Normal file
2201
practical_training/i18n/locales/zh-CN.ts
Normal file
File diff suppressed because it is too large
Load Diff
66
practical_training/plugins/auth-user.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
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
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>
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
addNeConfigData,
|
||||
delNeConfigData,
|
||||
editNeConfigData,
|
||||
} from '@/api/ne/neConfig';
|
||||
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';
|
||||
@@ -10,14 +10,12 @@ import { reactive, watch } from 'vue';
|
||||
|
||||
/**
|
||||
* 参数配置array类型
|
||||
* @param param 父级传入 { t, treeState, neTypeSelect, neIdSelect, fnActiveConfigNode, ruleVerification, modalState, fnModalCancel}
|
||||
* @param param 父级传入 { t, treeState, fnActiveConfigNode, ruleVerification, modalState, fnModalCancel}
|
||||
* @returns
|
||||
*/
|
||||
export default function useConfigArray({
|
||||
t,
|
||||
treeState,
|
||||
neTypeSelect,
|
||||
neIdSelect,
|
||||
fnActiveConfigNode,
|
||||
ruleVerification,
|
||||
modalState,
|
||||
@@ -68,7 +66,7 @@ export default function useConfigArray({
|
||||
}
|
||||
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(row, 'upfId')) {
|
||||
if (treeState.neType === 'SMF' && Reflect.has(row, 'upfId')) {
|
||||
const v = row.upfId.value;
|
||||
if (typeof v === 'string') {
|
||||
if (v === '') {
|
||||
@@ -105,7 +103,7 @@ export default function useConfigArray({
|
||||
const loc = `${from['index']['value']}`;
|
||||
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(from, 'upfId')) {
|
||||
if (treeState.neType === 'SMF' && Reflect.has(from, 'upfId')) {
|
||||
const v = from.upfId.value;
|
||||
if (Array.isArray(v)) {
|
||||
from.upfId.value = v.join(';');
|
||||
@@ -131,61 +129,28 @@ export default function useConfigArray({
|
||||
data[key] = from[key]['value'];
|
||||
}
|
||||
|
||||
// 请求
|
||||
const reqArr = [];
|
||||
if (neTypeSelect.value[1].startsWith('SYNC')) {
|
||||
for (const neId of neIdSelect.value) {
|
||||
reqArr.push(
|
||||
editNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neId,
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: data,
|
||||
loc: loc,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
reqArr.push(
|
||||
editNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: data,
|
||||
loc: loc,
|
||||
})
|
||||
);
|
||||
}
|
||||
// 无请求提示
|
||||
if (reqArr.length === 0) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.neIdSyncPleace'),
|
||||
duration: 3,
|
||||
});
|
||||
arrayEditClose();
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
Promise.allSettled(reqArr)
|
||||
.then(resArr => {
|
||||
const rejected = resArr.find(res => res.status === 'rejected');
|
||||
if (rejected) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.updateItemErr'),
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
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,
|
||||
});
|
||||
}
|
||||
const fulfilled = resArr.find(res => res.status === 'fulfilled');
|
||||
if (fulfilled) {
|
||||
fnActiveConfigNode('#');
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.updateItemErr'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -205,65 +170,27 @@ export default function useConfigArray({
|
||||
num: title,
|
||||
}),
|
||||
onOk() {
|
||||
// 请求
|
||||
const reqArr = [];
|
||||
if (neTypeSelect.value[1].startsWith('SYNC')) {
|
||||
for (const neId of neIdSelect.value) {
|
||||
reqArr.push(
|
||||
delNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neId,
|
||||
paramName: treeState.selectNode.paramName,
|
||||
loc: loc,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
reqArr.push(
|
||||
delNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
loc: loc,
|
||||
})
|
||||
);
|
||||
}
|
||||
// 无请求提示
|
||||
if (reqArr.length === 0) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.neIdSyncPleace'),
|
||||
duration: 3,
|
||||
});
|
||||
arrayEditClose();
|
||||
return;
|
||||
}
|
||||
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
Promise.allSettled(reqArr)
|
||||
.then(resArr => {
|
||||
const rejected = resArr.find(res => res.status === 'rejected');
|
||||
if (rejected) {
|
||||
message.error({
|
||||
content: `${rejected.reason}`,
|
||||
duration: 2,
|
||||
});
|
||||
} else {
|
||||
message.success({
|
||||
content: t('views.ne.neConfig.delItemOk', {
|
||||
num: title,
|
||||
}),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
const fulfilled = resArr.find(res => res.status === 'fulfilled');
|
||||
if (fulfilled) {
|
||||
fnActiveConfigNode('#');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
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,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -282,7 +209,7 @@ export default function useConfigArray({
|
||||
}
|
||||
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(row, 'upfId')) {
|
||||
if (treeState.neType === 'SMF' && Reflect.has(row, 'upfId')) {
|
||||
const v = row.upfId.value;
|
||||
if (typeof v === 'string') {
|
||||
if (v === '') {
|
||||
@@ -308,7 +235,7 @@ export default function useConfigArray({
|
||||
/**多列表新增单行确认 */
|
||||
function arrayAddOk(from: Record<string, any>) {
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(from, 'upfId')) {
|
||||
if (treeState.neType === 'SMF' && Reflect.has(from, 'upfId')) {
|
||||
const v = from.upfId.value;
|
||||
if (Array.isArray(v)) {
|
||||
from.upfId.value = v.join(';');
|
||||
@@ -334,61 +261,28 @@ export default function useConfigArray({
|
||||
data[key] = from[key]['value'];
|
||||
}
|
||||
|
||||
// 请求
|
||||
const reqArr = [];
|
||||
if (neTypeSelect.value[1].startsWith('SYNC')) {
|
||||
for (const neId of neIdSelect.value) {
|
||||
reqArr.push(
|
||||
addNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neId,
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: data,
|
||||
loc: `${from['index']['value']}`,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
reqArr.push(
|
||||
addNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: data,
|
||||
loc: `${from['index']['value']}`,
|
||||
})
|
||||
);
|
||||
}
|
||||
// 无请求提示
|
||||
if (reqArr.length === 0) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.neIdSyncPleace'),
|
||||
duration: 3,
|
||||
});
|
||||
arrayEditClose();
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
Promise.allSettled(reqArr)
|
||||
.then(resArr => {
|
||||
const rejected = resArr.find(res => res.status === 'rejected');
|
||||
if (rejected) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.addItemErr'),
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
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,
|
||||
});
|
||||
}
|
||||
const fulfilled = resArr.find(res => res.status === 'fulfilled');
|
||||
if (fulfilled) {
|
||||
fnActiveConfigNode('#');
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.addItemErr'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -473,7 +367,7 @@ export default function useConfigArray({
|
||||
}
|
||||
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && row.name === 'upfId') {
|
||||
if (treeState.neType === 'SMF' && row.name === 'upfId') {
|
||||
const v = row.value;
|
||||
if (typeof v === 'string') {
|
||||
if (v === '') {
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
addNeConfigData,
|
||||
editNeConfigData,
|
||||
delNeConfigData,
|
||||
} from '@/api/ne/neConfig';
|
||||
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';
|
||||
@@ -10,14 +10,12 @@ import { nextTick, reactive } from 'vue';
|
||||
|
||||
/**
|
||||
* 参数配置array类型的嵌套array
|
||||
* @param param 父级传入 { t, treeState, neTypeSelect, neIdSelect, fnActiveConfigNode, ruleVerification, modalState, arrayState, arrayInitEdit, arrayInitAdd, arrayEditClose}
|
||||
* @param param 父级传入 { t, treeState, fnActiveConfigNode, ruleVerification, modalState, arrayState, arrayInitEdit, arrayInitAdd, arrayEditClose}
|
||||
* @returns
|
||||
*/
|
||||
export default function useConfigArrayChild({
|
||||
t,
|
||||
treeState,
|
||||
neTypeSelect,
|
||||
neIdSelect,
|
||||
treeState,
|
||||
fnActiveConfigNode,
|
||||
ruleVerification,
|
||||
modalState,
|
||||
@@ -199,61 +197,28 @@ export default function useConfigArrayChild({
|
||||
data[key] = from[key]['value'];
|
||||
}
|
||||
|
||||
// 请求
|
||||
const reqArr = [];
|
||||
if (neTypeSelect.value[1].startsWith('SYNC')) {
|
||||
for (const neId of neIdSelect.value) {
|
||||
reqArr.push(
|
||||
editNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neId,
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: data,
|
||||
loc,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
reqArr.push(
|
||||
editNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: data,
|
||||
loc,
|
||||
})
|
||||
);
|
||||
}
|
||||
// 无请求提示
|
||||
if (reqArr.length === 0) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.neIdSyncPleace'),
|
||||
duration: 3,
|
||||
});
|
||||
arrayEditClose();
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
Promise.allSettled(reqArr)
|
||||
.then(resArr => {
|
||||
const rejected = resArr.find(res => res.status === 'rejected');
|
||||
if (rejected) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.updateItemErr'),
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
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,
|
||||
});
|
||||
}
|
||||
const fulfilled = resArr.find(res => res.status === 'fulfilled');
|
||||
if (fulfilled) {
|
||||
fnActiveConfigNode('#');
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.updateItemErr'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -274,65 +239,27 @@ export default function useConfigArrayChild({
|
||||
num: title,
|
||||
}),
|
||||
onOk() {
|
||||
// 请求
|
||||
const reqArr = [];
|
||||
if (neTypeSelect.value[1].startsWith('SYNC')) {
|
||||
for (const neId of neIdSelect.value) {
|
||||
reqArr.push(
|
||||
delNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neId,
|
||||
paramName: treeState.selectNode.paramName,
|
||||
loc,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
reqArr.push(
|
||||
delNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
loc,
|
||||
})
|
||||
);
|
||||
}
|
||||
// 无请求提示
|
||||
if (reqArr.length === 0) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.neIdSyncPleace'),
|
||||
duration: 3,
|
||||
});
|
||||
arrayEditClose();
|
||||
return;
|
||||
}
|
||||
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
Promise.allSettled(reqArr)
|
||||
.then(resArr => {
|
||||
const rejected = resArr.find(res => res.status === 'rejected');
|
||||
if (rejected) {
|
||||
message.error({
|
||||
content: `${rejected.reason}`,
|
||||
duration: 2,
|
||||
});
|
||||
} else {
|
||||
message.success({
|
||||
content: t('views.ne.neConfig.delItemOk', {
|
||||
num: title,
|
||||
}),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
const fulfilled = resArr.find(res => res.status === 'fulfilled');
|
||||
if (fulfilled) {
|
||||
fnActiveConfigNode('#');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
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,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -379,61 +306,28 @@ export default function useConfigArrayChild({
|
||||
data[key] = from[key]['value'];
|
||||
}
|
||||
|
||||
// 请求
|
||||
const reqArr = [];
|
||||
if (neTypeSelect.value[1].startsWith('SYNC')) {
|
||||
for (const neId of neIdSelect.value) {
|
||||
reqArr.push(
|
||||
addNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neId,
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: data,
|
||||
loc,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
reqArr.push(
|
||||
addNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: data,
|
||||
loc,
|
||||
})
|
||||
);
|
||||
}
|
||||
// 无请求提示
|
||||
if (reqArr.length === 0) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.neIdSyncPleace'),
|
||||
duration: 3,
|
||||
});
|
||||
arrayEditClose();
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
Promise.allSettled(reqArr)
|
||||
.then(resArr => {
|
||||
const rejected = resArr.find(res => res.status === 'rejected');
|
||||
if (rejected) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.addItemErr'),
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
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,
|
||||
});
|
||||
}
|
||||
const fulfilled = resArr.find(res => res.status === 'fulfilled');
|
||||
if (fulfilled) {
|
||||
fnActiveConfigNode('#');
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.addItemErr'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { editNeConfigData } from '@/api/ne/neConfig';
|
||||
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';
|
||||
@@ -6,16 +6,10 @@ import { reactive, toRaw } from 'vue';
|
||||
|
||||
/**
|
||||
* list类型参数处理
|
||||
* @param param 父级传入 {t, treeState, neTypeSelect, neIdSelect, ruleVerification}
|
||||
* @param param 父级传入 {t, treeState, ruleVerification}
|
||||
* @returns
|
||||
*/
|
||||
export default function useConfigList({
|
||||
t,
|
||||
treeState,
|
||||
neTypeSelect,
|
||||
neIdSelect,
|
||||
ruleVerification,
|
||||
}: any) {
|
||||
export default function useConfigList({ t, treeState, ruleVerification }: any) {
|
||||
/**单列表状态类型 */
|
||||
type ListStateType = {
|
||||
/**紧凑型 */
|
||||
@@ -84,64 +78,24 @@ export default function useConfigList({
|
||||
return;
|
||||
}
|
||||
|
||||
// 请求
|
||||
const reqArr = [];
|
||||
if (neTypeSelect.value[1].startsWith('SYNC')) {
|
||||
for (const neId of neIdSelect.value) {
|
||||
reqArr.push(
|
||||
editNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neId,
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: {
|
||||
[from['name']]: from['value'],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
reqArr.push(
|
||||
editNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neId: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: {
|
||||
[from['name']]: from['value'],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
// 无请求提示
|
||||
if (reqArr.length === 0) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.neIdSyncPleace'),
|
||||
duration: 3,
|
||||
});
|
||||
listState.confirmLoading = false;
|
||||
listState.editRecord = {};
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送
|
||||
listState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
Promise.allSettled(reqArr)
|
||||
.then(resArr => {
|
||||
const rejected = resArr.find(res => res.status === 'rejected');
|
||||
if (rejected) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.updateValueErr'),
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
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 fulfilled = resArr.find(res => res.status === 'fulfilled');
|
||||
if (fulfilled) {
|
||||
// 改变表格数据
|
||||
const item = listState.data.find(
|
||||
(item: Record<string, any>) => from['name'] === item['name']
|
||||
@@ -149,6 +103,11 @@ export default function useConfigList({
|
||||
if (item) {
|
||||
Object.assign(item, listState.editRecord);
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.updateValueErr'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -1,123 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw, watch, computed } from 'vue';
|
||||
import {
|
||||
reactive,
|
||||
ref,
|
||||
onMounted,
|
||||
toRaw,
|
||||
watch,
|
||||
defineAsyncComponent,
|
||||
} from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import { message, TreeSelect, type TreeSelectProps } from 'ant-design-vue/es';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import { DataNode } from 'ant-design-vue/es/tree';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import TableColumnsDnd from '@/components/TableColumnsDnd/index.vue';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import useOptions from './hooks/useOptions';
|
||||
import usePtOptions from './hooks/usePtOptions';
|
||||
import useConfigList from './hooks/useConfigList';
|
||||
import useConfigArray from './hooks/useConfigArray';
|
||||
import useConfigArrayChild from './hooks/useConfigArrayChild';
|
||||
import { getAllNeConfig, getNeConfigData } from '@/api/ne/neConfig';
|
||||
import { getAllNeConfig } from '@/api/ne/neConfig';
|
||||
import {
|
||||
getPtNeConfigData,
|
||||
ptContrastAsDefault,
|
||||
ptExport,
|
||||
ptExportAll,
|
||||
} from '@/api/pt/neConfig';
|
||||
import { isSystemAdmin, hasRoles } from '@/plugins/auth-user';
|
||||
const neInfoStore = useNeInfoStore();
|
||||
import saveAs from 'file-saver';
|
||||
const { t } = useI18n();
|
||||
const { ruleVerification, smfByUPFIdLoadData, smfByUPFIdOptions } = useOptions({
|
||||
t,
|
||||
});
|
||||
|
||||
/**网元类型 type,[](type,id) */
|
||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
/**网元类型 [](label,value,children) */
|
||||
let neSelectTreeDate = ref<TreeSelectProps['treeData']>([]);
|
||||
/**网元类型选择 type,id */
|
||||
let neTypeSelect = ref<string[]>(['', '']);
|
||||
/**网元类型选择 id */
|
||||
let neIdSelect = ref<string[]>([]);
|
||||
let neTypeSelectStatus = ref(true);
|
||||
/**网元类型neType选择 */
|
||||
async function fnSelectNeType(_: any, info: any) {
|
||||
if (!info) return;
|
||||
await fnGetNeConfig(info.value);
|
||||
if (treeState.data.length === 0) {
|
||||
message.warning({
|
||||
content: `${t('views.ne.neConfig.noConfigData')}`,
|
||||
duration: 3,
|
||||
});
|
||||
treeState.selectLoading = true;
|
||||
neIdSelect.value = [];
|
||||
neSelectTreeDate.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
neTypeSelect.value[0] = info.value;
|
||||
neTypeSelect.value[1] = 'SYNC';
|
||||
treeState.selectLoading = true;
|
||||
neTypeSelectStatus.value = true;
|
||||
neIdSelect.value = [];
|
||||
neSelectTreeDate.value = [];
|
||||
// 构建可选树形数据
|
||||
if (Array.isArray(info.children) && info.children.length > 0) {
|
||||
const neArr = info.children.concat();
|
||||
for (let index = 0; index < neArr.length; index++) {
|
||||
const v = neArr[index];
|
||||
const ne = {
|
||||
label: v.neName,
|
||||
value: v.neId,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
// 检查下级网元是否可用
|
||||
const res = await getNeConfigData({
|
||||
neType: v.neType,
|
||||
neId: v.neId,
|
||||
paramName: `${treeState.data[0].key}`,
|
||||
});
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
ne.disabled = !res.data.length;
|
||||
} else {
|
||||
ne.disabled = true;
|
||||
}
|
||||
|
||||
// 添加到树形数据
|
||||
const root = neSelectTreeDate.value?.find(s => s.label === v.province);
|
||||
if (root && Array.isArray(root.children)) {
|
||||
root.children.push(ne);
|
||||
} else {
|
||||
neSelectTreeDate.value?.push({
|
||||
label: v.province,
|
||||
value: 'SYNC_' + v.province,
|
||||
children: [ne],
|
||||
});
|
||||
}
|
||||
|
||||
const key = 'SYNC_' + v.province;
|
||||
// 初始区域
|
||||
if (neIdSelect.value.length === 0) {
|
||||
neTypeSelect.value[1] = key;
|
||||
}
|
||||
// 同区域内添加
|
||||
if (neTypeSelect.value[1] === key) {
|
||||
neIdSelect.value.push(v.neId);
|
||||
}
|
||||
}
|
||||
}
|
||||
fnActiveConfigNode(treeState.data[0].key);
|
||||
neTypeSelectStatus.value = false;
|
||||
}
|
||||
/**网元类型neId选择 */
|
||||
function fnSelectNeId(_: any, info: any) {
|
||||
if (info.children && Array.isArray(info.children)) {
|
||||
const okArr = info.children.filter((item: any) => !item.disabled);
|
||||
if (Array.isArray(okArr) && okArr.length === 0) {
|
||||
message.warning({
|
||||
content: `${t('views.ne.neConfig.noConfigdDisabled')}`,
|
||||
duration: 3,
|
||||
});
|
||||
neIdSelect.value = [];
|
||||
return;
|
||||
}
|
||||
neTypeSelect.value[1] = info.value;
|
||||
neIdSelect.value = okArr.map((item: any) => item.value);
|
||||
} else {
|
||||
neTypeSelect.value[1] = info.value;
|
||||
neIdSelect.value = [info.value];
|
||||
}
|
||||
fnActiveConfigNode(treeState.data[0].key);
|
||||
}
|
||||
/**网元参数 */
|
||||
let neSelectOptions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**左侧导航是否可收起 */
|
||||
let collapsible = ref<boolean>(true);
|
||||
@@ -131,6 +50,8 @@ function changeCollapsible() {
|
||||
type TreeStateType = {
|
||||
/**网元 loading */
|
||||
loading: boolean;
|
||||
/**网元类型,教学固定neId:001 */
|
||||
neType: string;
|
||||
/**网元配置 tree */
|
||||
data: DataNode[];
|
||||
/**选择对应Node一级 tree */
|
||||
@@ -143,7 +64,6 @@ type TreeStateType = {
|
||||
paramType: string;
|
||||
paramPerms: string[];
|
||||
paramData: Record<string, any>[];
|
||||
visible: string;
|
||||
};
|
||||
/**选择 loading */
|
||||
selectLoading: boolean;
|
||||
@@ -151,6 +71,7 @@ type TreeStateType = {
|
||||
|
||||
let treeState: TreeStateType = reactive({
|
||||
loading: true,
|
||||
neType: '',
|
||||
data: [],
|
||||
selectNode: {
|
||||
paramName: '',
|
||||
@@ -158,7 +79,6 @@ let treeState: TreeStateType = reactive({
|
||||
paramType: '',
|
||||
paramPerms: [],
|
||||
paramData: [],
|
||||
visible: 'public',
|
||||
// 树形节点需要有
|
||||
title: '',
|
||||
key: '',
|
||||
@@ -196,134 +116,30 @@ function fnActiveConfigNode(key: string | number) {
|
||||
}
|
||||
treeState.selectNode = JSON.parse(JSON.stringify(param));
|
||||
|
||||
let neId = neTypeSelect.value[1];
|
||||
// 无neId时取首个可连接的
|
||||
if (neId.startsWith('SYNC')) {
|
||||
const oneNeId = neIdSelect.value[0];
|
||||
if (oneNeId) {
|
||||
neId = oneNeId;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取网元端的配置数据
|
||||
fnGetNeConfigData(neTypeSelect.value[0], neId, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询配置可选属性值列表
|
||||
* neTypeSelect.value[0]
|
||||
*/
|
||||
async function fnGetNeConfig(neType: string) {
|
||||
if (!neType) {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.neTypePleace'),
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
const query: {
|
||||
neType: string;
|
||||
paramName: string | number;
|
||||
student: string | undefined;
|
||||
} = {
|
||||
neType: treeState.neType,
|
||||
paramName: key,
|
||||
student: undefined,
|
||||
};
|
||||
if (hasRoles(['teacher'])) {
|
||||
query.student = classState.student;
|
||||
}
|
||||
|
||||
treeState.loading = true;
|
||||
// 获取数据
|
||||
const res = await getAllNeConfig(neType);
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
const arr = [];
|
||||
for (const v of res.data) {
|
||||
const item = JSON.parse(JSON.stringify(v));
|
||||
// 规则项
|
||||
let paramData: Record<string, string>[] = [];
|
||||
for (let index = 0; index < item.paramData.length; index++) {
|
||||
const element = item.paramData[index];
|
||||
if (!element['visible']) {
|
||||
element['visible'] = 'public';
|
||||
}
|
||||
paramData.push(element);
|
||||
}
|
||||
// 权限控制
|
||||
let paramPerms: string[] = [];
|
||||
if (item.paramPerms) {
|
||||
paramPerms = item.paramPerms.split(',');
|
||||
} else {
|
||||
paramPerms = ['post', 'put', 'delete'];
|
||||
}
|
||||
arr.push({
|
||||
children: undefined,
|
||||
title: item.paramDisplay,
|
||||
key: item.paramName,
|
||||
paramName: item.paramName,
|
||||
paramDisplay: item.paramDisplay,
|
||||
paramType: item.paramType,
|
||||
paramPerms: paramPerms,
|
||||
paramData: paramData,
|
||||
visible: item.visible,
|
||||
});
|
||||
}
|
||||
treeState.data = arr;
|
||||
treeState.loading = false;
|
||||
} else {
|
||||
treeState.data = [];
|
||||
neTypeSelectStatus.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**过滤可见项 */
|
||||
const treeStateData = computed(() => {
|
||||
// 公共
|
||||
if (neTypeSelect.value[1].startsWith('SYNC')) {
|
||||
return treeState.data.filter(item => item.visible === 'public');
|
||||
}
|
||||
// 具体网元
|
||||
const arr: DataNode[] = [];
|
||||
for (const item of treeState.data) {
|
||||
if (item.visible === 'self') {
|
||||
arr.push(item);
|
||||
} else if (item.paramType === 'list') {
|
||||
for (let index = 0; index < item.paramData.length; index++) {
|
||||
const element = item.paramData[index];
|
||||
if (element['visible'] === 'self') {
|
||||
arr.push(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
});
|
||||
|
||||
/**
|
||||
* 查询配置属性值数据
|
||||
* paramName = treeState.data[0].key
|
||||
*/
|
||||
function fnGetNeConfigData(
|
||||
neType: string,
|
||||
neId: string,
|
||||
paramName: string | number
|
||||
) {
|
||||
const param = treeState.selectNode;
|
||||
// 获取网元端的配置数据
|
||||
getNeConfigData({ neType, neId, paramName }).then(res => {
|
||||
// 数据处理
|
||||
getPtNeConfigData(query).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
const ruleArr: Record<string, any>[] = JSON.parse(
|
||||
JSON.stringify(param.paramData)
|
||||
);
|
||||
const ruleArr = param.paramData;
|
||||
const dataArr = res.data;
|
||||
if (param.paramType === 'list') {
|
||||
// 过滤可见规则项
|
||||
let ruleArrFilter: Record<string, any>[] = [];
|
||||
if (neTypeSelect.value[1].startsWith('SYNC')) {
|
||||
ruleArrFilter = ruleArr.filter(item => item['visible'] === 'public');
|
||||
} else {
|
||||
ruleArrFilter = ruleArr.filter(item => item['visible'] === 'self');
|
||||
}
|
||||
// 列表项数据
|
||||
const dataList = [];
|
||||
for (const item of dataArr) {
|
||||
for (const key in item) {
|
||||
// 规则为准
|
||||
for (const rule of ruleArrFilter) {
|
||||
// 取到对应规则key设置值
|
||||
for (const rule of ruleArr) {
|
||||
if (rule['name'] === key) {
|
||||
const ruleItem = Object.assign(rule, {
|
||||
optional: 'true',
|
||||
@@ -398,19 +214,71 @@ function fnGetNeConfigData(
|
||||
tablePagination.current = 1;
|
||||
arrayEditClose();
|
||||
}
|
||||
// 有数据关闭loading
|
||||
setTimeout(() => {
|
||||
treeState.selectLoading = false;
|
||||
}, 300);
|
||||
} else {
|
||||
message.warning({
|
||||
content: `${param.paramDisplay} ${t('views.ne.neConfig.noConfigData')}`,
|
||||
content: `${param.paramDisplay} ${t(
|
||||
'views.configManage.configParamForm.noConfigData'
|
||||
)}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**查询配置可选属性值列表 */
|
||||
function fnGetNeConfig() {
|
||||
const neType = treeState.neType;
|
||||
if (!neType) {
|
||||
message.warning({
|
||||
content: t('views.configManage.configParamForm.neTypePleace'),
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
treeState.loading = true;
|
||||
// 获取数据
|
||||
getAllNeConfig(neType)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
const arr = [];
|
||||
for (const item of res.data) {
|
||||
let paramPerms: string[] = [];
|
||||
if (item.paramPerms) {
|
||||
paramPerms = item.paramPerms.split(',');
|
||||
} else {
|
||||
paramPerms = ['post', 'put', 'delete'];
|
||||
}
|
||||
arr.push({
|
||||
...item,
|
||||
children: undefined,
|
||||
title: item.paramDisplay,
|
||||
key: item.paramName,
|
||||
paramPerms,
|
||||
});
|
||||
}
|
||||
treeState.data = arr;
|
||||
treeState.loading = false;
|
||||
// 取首个tag
|
||||
if (res.data.length > 0) {
|
||||
const item = JSON.parse(JSON.stringify(treeState.data[0]));
|
||||
treeState.selectNode = item;
|
||||
treeState.selectLoading = false;
|
||||
fnActiveConfigNode(item.key);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
// 仅教师加载
|
||||
if (hasRoles(['teacher'])) {
|
||||
classStudents(neType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**添加框是否显示 */
|
||||
@@ -477,19 +345,26 @@ watch(
|
||||
() => modalState.open,
|
||||
val => {
|
||||
// SMF需要选择配置的UPF id
|
||||
if (val && neTypeSelect.value[0] === 'SMF') {
|
||||
let neId = neTypeSelect.value[1];
|
||||
// 无neId时取首个可连接的
|
||||
if (neId.startsWith('SYNC')) {
|
||||
neId = neIdSelect.value[0];
|
||||
}
|
||||
smfByUPFIdLoadData(neId);
|
||||
if (val && treeState.neType === 'SMF') {
|
||||
smfByUPFIdLoadData('001');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const {
|
||||
ptConfigState,
|
||||
ptConfigSave,
|
||||
ptConfigReset,
|
||||
ptConfigApply,
|
||||
classState,
|
||||
studentStatus,
|
||||
studentSearch,
|
||||
studentChange,
|
||||
classStudents,
|
||||
} = usePtOptions({ t, fnActiveConfigNode });
|
||||
|
||||
const { tablePagination, listState, listEdit, listEditClose, listEditOk } =
|
||||
useConfigList({ t, treeState, neTypeSelect, neIdSelect, ruleVerification });
|
||||
useConfigList({ t, treeState, ruleVerification });
|
||||
|
||||
const {
|
||||
arrayState,
|
||||
@@ -504,8 +379,6 @@ const {
|
||||
} = useConfigArray({
|
||||
t,
|
||||
treeState,
|
||||
neTypeSelect,
|
||||
neIdSelect,
|
||||
fnActiveConfigNode,
|
||||
ruleVerification,
|
||||
modalState,
|
||||
@@ -523,8 +396,6 @@ const {
|
||||
} = useConfigArrayChild({
|
||||
t,
|
||||
treeState,
|
||||
neTypeSelect,
|
||||
neIdSelect,
|
||||
fnActiveConfigNode,
|
||||
ruleVerification,
|
||||
modalState,
|
||||
@@ -534,18 +405,109 @@ const {
|
||||
arrayEditClose,
|
||||
});
|
||||
|
||||
// 异步加载组件
|
||||
const OpeateDrawer = defineAsyncComponent(
|
||||
() => import('./components/OpeateDrawer.vue')
|
||||
);
|
||||
const operateDrawer = ref<boolean>(false);
|
||||
/**打开历史 */
|
||||
function openOpeateDrawer() {
|
||||
operateDrawer.value = !operateDrawer.value;
|
||||
}
|
||||
|
||||
// 数据与示例比较
|
||||
const dataDiffState = reactive({
|
||||
open: false,
|
||||
title: t('views.configManage.configParamForm.ptDiff'),
|
||||
exampleData: '',
|
||||
data: '',
|
||||
});
|
||||
// 获取当前数据与示例数据
|
||||
function fnDataDiff() {
|
||||
ptContrastAsDefault({
|
||||
neType: treeState.neType,
|
||||
paramName: treeState.selectNode.paramName,
|
||||
student: classState.student,
|
||||
}).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
dataDiffState.data = JSON.stringify(res.data, null, 2);
|
||||
dataDiffState.exampleData = JSON.stringify(res.exampleData, null, 2);
|
||||
dataDiffState.title = treeState.selectNode.paramDisplay;
|
||||
dataDiffState.open = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据导出Excel
|
||||
function fnDataExport() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.configManage.configParamForm.ptExportTip'),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
ptExport(classState.student)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `config_data_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 数据导出Excel
|
||||
function fnDataExportAll() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.configManage.configParamForm.ptExportTip'),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
ptExportAll()
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `students_config_data_${Date.now()}.zip`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
neInfoStore.fnNelist().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
if (res.data.length > 0) {
|
||||
// 过滤不可用的网元
|
||||
neCascaderOptions.value = neInfoStore.getNeSelectOtions.filter(
|
||||
neSelectOptions.value = neInfoStore.getNeSelectOtions.filter(
|
||||
(item: any) => {
|
||||
return !['LMF', 'NEF'].includes(item.value);
|
||||
return !['OMC', 'LMF', 'NEF'].includes(item.value);
|
||||
}
|
||||
);
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
if (neSelectOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
@@ -553,16 +515,15 @@ onMounted(() => {
|
||||
return;
|
||||
}
|
||||
// 默认选择AMF
|
||||
const item = neCascaderOptions.value.find(s => s.value === 'AMF');
|
||||
const item = neSelectOptions.value.find(s => s.value === 'AMF');
|
||||
if (item && item.children) {
|
||||
fnSelectNeType(null, item);
|
||||
// const info = item.children[0];
|
||||
// neTypeSelect.value = [info.neType, info.neId];
|
||||
const info = item.children[0];
|
||||
treeState.neType = info.neType;
|
||||
} else {
|
||||
fnSelectNeType(null, neCascaderOptions.value[0]);
|
||||
// const info = neCascaderOptions.value[0].children[0];
|
||||
// neTypeSelect.value = [info.neType, info.neId];
|
||||
const info = neSelectOptions.value[0].children[0];
|
||||
treeState.neType = info.neType;
|
||||
}
|
||||
fnGetNeConfig();
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
@@ -576,6 +537,133 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-if="!isSystemAdmin()"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="8" :md="12" :xs="24" v-roles:has="['teacher']">
|
||||
<a-form-item
|
||||
:label="t('views.configManage.configParamForm.ptApplyShow')"
|
||||
name="student "
|
||||
>
|
||||
<a-select
|
||||
v-model:value="classState.student"
|
||||
show-search
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:default-active-first-option="false"
|
||||
:show-arrow="false"
|
||||
:allow-clear="true"
|
||||
:filter-option="false"
|
||||
:not-found-content="null"
|
||||
:options="classState.studentOptions"
|
||||
@search="(v:any)=> studentSearch(treeState.neType, v)"
|
||||
@change="studentChange"
|
||||
>
|
||||
<template #option="{ value, label, applyStatus }">
|
||||
<span>{{ label }} </span>
|
||||
<a-tag
|
||||
v-if="applyStatus === '0'"
|
||||
color="processing"
|
||||
style="position: absolute; right: 0"
|
||||
>
|
||||
{{
|
||||
applyStatus &&
|
||||
t('views.configManage.configParamForm.ptApply')
|
||||
}}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<!-- 教师查看学生 -->
|
||||
<template
|
||||
v-if="
|
||||
hasRoles(['teacher']) &&
|
||||
classState.student &&
|
||||
studentStatus === '0'
|
||||
"
|
||||
>
|
||||
<a-button
|
||||
@click="
|
||||
ptConfigApply(treeState.neType, '2', classState.student)
|
||||
"
|
||||
:loading="ptConfigState.applyLoading"
|
||||
>
|
||||
{{ t('views.configManage.configParamForm.ptApplyStuNE') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
@click="
|
||||
ptConfigApply(treeState.neType, '3', classState.student)
|
||||
"
|
||||
:loading="ptConfigState.applyLoading"
|
||||
>
|
||||
{{ t('views.configManage.configParamForm.ptApplyStuRack') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<a-button
|
||||
v-roles:has="['admin', 'teacher']"
|
||||
v-show="!classState.student"
|
||||
@click="ptConfigApply(treeState.neType, '2')"
|
||||
:loading="ptConfigState.applyLoading"
|
||||
>
|
||||
{{ t('views.configManage.configParamForm.ptApplyNE') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
@click="ptConfigSave(treeState.neType)"
|
||||
:loading="ptConfigState.saveLoading"
|
||||
v-roles:has="['admin']"
|
||||
>
|
||||
{{ t('views.configManage.configParamForm.ptLoad') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
@click="ptConfigReset(treeState.neType)"
|
||||
v-show="!classState.student"
|
||||
:loading="ptConfigState.restLoading"
|
||||
v-roles:has="['teacher']"
|
||||
>
|
||||
{{ t('views.configManage.configParamForm.ptReset') }}
|
||||
</a-button>
|
||||
<a-button @click="fnDataExport()" v-roles:has="['teacher']">
|
||||
{{ t('views.configManage.configParamForm.ptExport') }}
|
||||
</a-button>
|
||||
<a-button @click="fnDataExportAll()" v-roles:has="['teacher']">
|
||||
{{ t('views.configManage.configParamForm.ptExportAll') }}
|
||||
</a-button>
|
||||
|
||||
<!-- 学生 -->
|
||||
<a-button
|
||||
@click="ptConfigReset(treeState.neType)"
|
||||
:loading="ptConfigState.restLoading"
|
||||
v-roles:has="['student']"
|
||||
>
|
||||
{{ t('views.configManage.configParamForm.ptReset') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
@click="ptConfigApply(treeState.neType, '0')"
|
||||
:loading="ptConfigState.applyLoading"
|
||||
v-roles:has="['student']"
|
||||
>
|
||||
{{
|
||||
t('views.configManage.configParamForm.ptApplyStu', {
|
||||
ne: treeState.neType,
|
||||
})
|
||||
}}
|
||||
</a-button>
|
||||
<a-button @click="fnDataExport()" v-roles:has="['student']">
|
||||
{{ t('views.configManage.configParamForm.ptExport') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
:lg="6"
|
||||
@@ -587,43 +675,24 @@ onMounted(() => {
|
||||
<!-- 网元类型 -->
|
||||
<a-card size="small" :bordered="false" :loading="treeState.loading">
|
||||
<template #title>
|
||||
{{ t('views.ne.neConfig.treeTitle') }}
|
||||
{{ t('views.configManage.configParamForm.treeTitle') }}
|
||||
<span v-roles:has="['teacher']" class="student-name">
|
||||
{{ classState.student }}
|
||||
</span>
|
||||
</template>
|
||||
<a-form layout="vertical" autocomplete="off">
|
||||
<a-form-item name="neTypeSelect ">
|
||||
<a-input-group compact>
|
||||
<a-select
|
||||
:disabled="neTypeSelectStatus"
|
||||
:value="neTypeSelect[0]"
|
||||
:options="neCascaderOptions"
|
||||
:allow-clear="false"
|
||||
@change="fnSelectNeType"
|
||||
style="width: 40%"
|
||||
>
|
||||
</a-select>
|
||||
<a-tree-select
|
||||
v-model:value="neTypeSelect[1]"
|
||||
:status="neIdSelect.length === 0 ? 'warning' : ''"
|
||||
:disabled="treeState.selectLoading"
|
||||
:tree-data="neSelectTreeDate"
|
||||
:maxTagCount="1"
|
||||
:show-search="true"
|
||||
:allow-clear="false"
|
||||
:tree-default-expand-all="true"
|
||||
:tree-checkable="false"
|
||||
:show-checked-strategy="TreeSelect.SHOW_PARENT"
|
||||
placement="bottomRight"
|
||||
tree-node-filter-prop="label"
|
||||
style="width: 60%"
|
||||
@select="fnSelectNeId"
|
||||
>
|
||||
</a-tree-select>
|
||||
</a-input-group>
|
||||
<a-form-item name="neId ">
|
||||
<a-select
|
||||
v-model:value="treeState.neType"
|
||||
:options="neSelectOptions"
|
||||
:allow-clear="false"
|
||||
:disabled="ptConfigState.saveLoading"
|
||||
@change="fnGetNeConfig"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item name="treeStateData">
|
||||
<a-form-item name="listeningPort">
|
||||
<a-tree
|
||||
:disabled="neTypeSelectStatus"
|
||||
:tree-data="treeStateData"
|
||||
:tree-data="treeState.data"
|
||||
:selected-keys="[treeState.selectNode.paramName]"
|
||||
@select="fnSelectConfigNode"
|
||||
>
|
||||
@@ -655,12 +724,42 @@ onMounted(() => {
|
||||
{{ treeState.selectNode.paramDisplay }}
|
||||
</a-typography-text>
|
||||
<a-typography-text type="danger" v-else>
|
||||
{{ t('views.ne.neConfig.treeSelectTip') }}
|
||||
{{ t('views.configManage.configParamForm.treeSelectTip') }}
|
||||
</a-typography-text>
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center" v-show="!treeState.selectLoading">
|
||||
<a-tooltip placement="topRight">
|
||||
<span v-roles:has="['teacher', 'student']">
|
||||
<a-tooltip>
|
||||
<template #title>
|
||||
{{ t('views.configManage.configParamForm.ptDiff') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click.prevent="fnDataDiff()"
|
||||
>
|
||||
<template #icon>
|
||||
<BlockOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-tooltip>
|
||||
<template #title>
|
||||
{{ t('views.configManage.configParamForm.ptHistory') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click.prevent="openOpeateDrawer()"
|
||||
>
|
||||
<template #icon>
|
||||
<BarsOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button
|
||||
type="default"
|
||||
@@ -743,9 +842,10 @@ onMounted(() => {
|
||||
<template #title> {{ t('common.ok') }} </template>
|
||||
<a-popconfirm
|
||||
:title="
|
||||
t('views.ne.neConfig.editOkTip', {
|
||||
num: record['display'],
|
||||
})
|
||||
t(
|
||||
'views.configManage.configParamForm.editOkTip',
|
||||
{ num: record['display'] }
|
||||
)
|
||||
"
|
||||
placement="topRight"
|
||||
:disabled="listState.confirmLoading"
|
||||
@@ -791,8 +891,11 @@ onMounted(() => {
|
||||
@click="listEdit(record)"
|
||||
style="margin-left: 18px"
|
||||
v-if="
|
||||
!listState.confirmLoading &&
|
||||
!['read-only', 'read', 'ro'].includes(record.access)
|
||||
!['read-only', 'read', 'ro'].includes(
|
||||
record.access
|
||||
) &&
|
||||
!(hasRoles(['teacher']) && classState.student) &&
|
||||
!listState.confirmLoading
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
@@ -803,7 +906,7 @@ onMounted(() => {
|
||||
</a-table>
|
||||
|
||||
<!-- array类型 -->
|
||||
<template v-else-if="treeState.selectNode.paramType === 'array'">
|
||||
<template v-if="treeState.selectNode.paramType === 'array'">
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="index"
|
||||
@@ -824,7 +927,10 @@ onMounted(() => {
|
||||
type="primary"
|
||||
@click.prevent="arrayAdd()"
|
||||
size="small"
|
||||
v-if="treeState.selectNode.paramPerms.includes('post')"
|
||||
v-if="
|
||||
treeState.selectNode.paramPerms.includes('post') &&
|
||||
!(hasRoles(['teacher']) && classState.student)
|
||||
"
|
||||
>
|
||||
<template #icon> <PlusOutlined /> </template>
|
||||
{{ t('common.addText') }}
|
||||
@@ -832,7 +938,7 @@ onMounted(() => {
|
||||
<TableColumnsDnd
|
||||
type="ghost"
|
||||
:cache-id="treeState.selectNode.key"
|
||||
:columns="treeState.selectNode.paramPerms.includes('get') ? [...arrayState.columns.filter((s:any)=>s.key !== 'index')] : arrayState.columns"
|
||||
:columns="(treeState.selectNode.paramPerms.includes('get') || (hasRoles(['teacher']) && classState.student)) ? [...arrayState.columns.filter((s:any)=>s.key !== 'index')] : arrayState.columns"
|
||||
v-model:columns-dnd="arrayState.columnsDnd"
|
||||
></TableColumnsDnd>
|
||||
</a-space>
|
||||
@@ -843,7 +949,10 @@ onMounted(() => {
|
||||
<template v-if="column?.key === 'index'">
|
||||
<a-space :size="16" align="center">
|
||||
<a-tooltip
|
||||
v-if="treeState.selectNode.paramPerms.includes('put')"
|
||||
v-if="
|
||||
treeState.selectNode.paramPerms.includes('put') &&
|
||||
!(hasRoles(['teacher']) && classState.student)
|
||||
"
|
||||
>
|
||||
<template #title>{{ t('common.editText') }}</template>
|
||||
<a-button type="link" @click.prevent="arrayEdit(text)">
|
||||
@@ -853,11 +962,7 @@ onMounted(() => {
|
||||
<a-tooltip
|
||||
v-if="
|
||||
treeState.selectNode.paramPerms.includes('delete') &&
|
||||
!(
|
||||
neTypeSelect[0] === 'IMS' &&
|
||||
treeState.selectNode.paramName === 'plmn' &&
|
||||
text['value'] === 0
|
||||
)
|
||||
!(hasRoles(['teacher']) && classState.student)
|
||||
"
|
||||
>
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
@@ -882,7 +987,9 @@ onMounted(() => {
|
||||
"
|
||||
>
|
||||
<template #icon><BarsOutlined /></template>
|
||||
{{ t('views.ne.neConfig.arrayMore') }}
|
||||
{{
|
||||
t('views.configManage.configParamForm.arrayMore')
|
||||
}}
|
||||
</a-button>
|
||||
<!--特殊字段拓展显示-->
|
||||
<span
|
||||
@@ -983,7 +1090,11 @@ onMounted(() => {
|
||||
<template v-if="text.array">
|
||||
<a-button type="default" size="small">
|
||||
<template #icon><BarsOutlined /></template>
|
||||
{{ t('views.ne.neConfig.arrayMore') }}
|
||||
{{
|
||||
t(
|
||||
'views.configManage.configParamForm.arrayMore'
|
||||
)
|
||||
}}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
@@ -1003,10 +1114,6 @@ onMounted(() => {
|
||||
</template>
|
||||
</a-table>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<a-alert type="warning" show-icon message="No Data" />
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@@ -1056,7 +1163,7 @@ onMounted(() => {
|
||||
<!-- 特殊SMF-upfid选择 -->
|
||||
<a-select
|
||||
v-if="
|
||||
neTypeSelect[0] === 'SMF' &&
|
||||
treeState.neType === 'SMF' &&
|
||||
modalState.from[item.name]['name'] === 'upfId'
|
||||
"
|
||||
v-model:value="modalState.from[item.name]['value']"
|
||||
@@ -1112,6 +1219,44 @@ onMounted(() => {
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
|
||||
<!-- 与示例对比差异 -->
|
||||
<a-modal
|
||||
:width="800"
|
||||
:destroyOnClose="true"
|
||||
:mask-closable="false"
|
||||
v-model:open="dataDiffState.open"
|
||||
:title="dataDiffState.title"
|
||||
:footer="null"
|
||||
:body-style="{ padding: 0, maxHeight: '650px', 'overflow-y': 'auto' }"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
"
|
||||
>
|
||||
<div style="flex: 1; text-align: center">
|
||||
{{ t('views.configManage.configParamForm.ptDiffExample') }}
|
||||
</div>
|
||||
<div style="flex: 1; text-align: center">
|
||||
{{ t('views.configManage.configParamForm.ptDiffSelf') }}
|
||||
</div>
|
||||
</div>
|
||||
<CodemirrorEditeDiff
|
||||
:old-area="dataDiffState.exampleData"
|
||||
:new-area="dataDiffState.data"
|
||||
></CodemirrorEditeDiff>
|
||||
</a-modal>
|
||||
|
||||
<!-- 历史记录抽屉 -->
|
||||
<OpeateDrawer
|
||||
v-model:open="operateDrawer"
|
||||
:ne-type="treeState.neType"
|
||||
:param-name="treeState.selectNode.paramName"
|
||||
:student="classState.student"
|
||||
></OpeateDrawer>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
@@ -1140,4 +1285,11 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
// 学生账号
|
||||
.student-name {
|
||||
font-size: 18px;
|
||||
color: var(--ant-primary-color);
|
||||
margin-left: 12px;
|
||||
}
|
||||
</style>
|
||||
1275
practical_training/views/configManage/neManage/index.vue
Normal file
1275
practical_training/views/configManage/neManage/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
442
practical_training/views/configManage/neOverview/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
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
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
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
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
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
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
BIN
practical_training/views/dashboard/overview/images/border.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
practical_training/views/dashboard/overview/images/brand.png
Normal file
BIN
practical_training/views/dashboard/overview/images/brand.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
practical_training/views/dashboard/overview/images/line.png
Normal file
BIN
practical_training/views/dashboard/overview/images/line.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 237 B |
BIN
practical_training/views/dashboard/overview/images/rect.png
Normal file
BIN
practical_training/views/dashboard/overview/images/rect.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
493
practical_training/views/dashboard/overview/index.vue
Normal file
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>
|
||||
@@ -12,16 +12,14 @@ import {
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import {
|
||||
delSGWCDataCDR,
|
||||
exportSGWCDataCDR,
|
||||
listSGWCDataCDR,
|
||||
} from '@/api/neData/sgwc';
|
||||
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 { useClipboard } from '@vueuse/core';
|
||||
import dayjs, { type Dayjs } from 'dayjs';
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
import { hasRoles } from '@/plugins/auth-user';
|
||||
const { t } = useI18n();
|
||||
const ws = new WS();
|
||||
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||
@@ -30,39 +28,20 @@ const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||
let neOtions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**开始结束时间 */
|
||||
let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
|
||||
dayjs().startOf('hour'),
|
||||
dayjs().endOf('hour'),
|
||||
]);
|
||||
/**时间范围 */
|
||||
let rangePickerPresets = ref([
|
||||
{
|
||||
label: 'Now hour',
|
||||
value: [dayjs().startOf('hour'), dayjs().endOf('hour')],
|
||||
},
|
||||
{ label: 'Today', value: [dayjs().startOf('day'), dayjs().endOf('day')] },
|
||||
{
|
||||
label: 'Yesterday',
|
||||
value: [
|
||||
dayjs().subtract(1, 'day').startOf('day'),
|
||||
dayjs().subtract(1, 'day').endOf('day'),
|
||||
],
|
||||
},
|
||||
]);
|
||||
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neType: 'SGWC',
|
||||
neType: 'SMF',
|
||||
neId: '001',
|
||||
imsi: '',
|
||||
msisdn: '',
|
||||
subscriberID: '',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
/**开始时间 */
|
||||
startTime: undefined as undefined | number,
|
||||
startTime: '',
|
||||
/**结束时间 */
|
||||
endTime: undefined as undefined | number,
|
||||
endTime: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
@@ -72,14 +51,13 @@ let queryParams = reactive({
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
imsi: '',
|
||||
msisdn: '',
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
subscriberID: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
queryRangePicker.value = [dayjs().startOf('hour'), dayjs().endOf('hour')];
|
||||
queryRangePicker.value = ['', ''];
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
@@ -113,11 +91,11 @@ let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.chargingID'), // 计费ID
|
||||
title: t('views.dashboard.cdr.smfChargingID'), // 计费ID
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
@@ -127,71 +105,90 @@ let tableColumns: ColumnsType = [
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.sgwcServedIMSI'), // IMSI
|
||||
title: t('views.dashboard.cdr.smfSubscriptionIDType'), // 订阅 ID 类型
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return cdrJSON.servedIMSI;
|
||||
return cdrJSON.subscriberIdentifier?.subscriptionIDType;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.sgwcServedMSISDN'), // MSISDN
|
||||
title: t('views.dashboard.cdr.smfSubscriptionIDData'), // 订阅 ID 数据
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return cdrJSON.servedMSISDN;
|
||||
return cdrJSON.subscriberIdentifier?.subscriptionIDData;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.sgwcVolumeGPRSUplink'), // GPRS 上行链路
|
||||
title: t('views.dashboard.cdr.smfDataVolumeUplink'), // 数据量上行链路
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
const listOfTrafficVolumes = cdrJSON.listOfTrafficVolumes;
|
||||
const listOfMultipleUnitUsage = cdrJSON.listOfMultipleUnitUsage;
|
||||
if (
|
||||
!Array.isArray(listOfTrafficVolumes) ||
|
||||
listOfTrafficVolumes.length < 1
|
||||
!Array.isArray(listOfMultipleUnitUsage) ||
|
||||
listOfMultipleUnitUsage.length < 1
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
let dataVolumeGPRSUplink = 0;
|
||||
for (const used of listOfTrafficVolumes) {
|
||||
const v = +used.dataVolumeGPRSUplink;
|
||||
dataVolumeGPRSUplink += isNaN(v) ? 0 : v;
|
||||
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
|
||||
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
|
||||
return 0;
|
||||
}
|
||||
return dataVolumeGPRSUplink;
|
||||
return usedUnitContainer[0].dataVolumeUplink;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.sgwcVolumeGPRSDownlink'), // GPRS 下行链路
|
||||
title: t('views.dashboard.cdr.smfDataVolumeDownlink'), // 数据量下行链路
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 180,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
const listOfTrafficVolumes = cdrJSON.listOfTrafficVolumes;
|
||||
const listOfMultipleUnitUsage = cdrJSON.listOfMultipleUnitUsage;
|
||||
if (
|
||||
!Array.isArray(listOfTrafficVolumes) ||
|
||||
listOfTrafficVolumes.length < 1
|
||||
!Array.isArray(listOfMultipleUnitUsage) ||
|
||||
listOfMultipleUnitUsage.length < 1
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
let dataVolumeGPRSDownlink = 0;
|
||||
for (const used of listOfTrafficVolumes) {
|
||||
const v = +used.dataVolumeGPRSDownlink;
|
||||
dataVolumeGPRSDownlink += isNaN(v) ? 0 : v;
|
||||
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
|
||||
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
|
||||
return 0;
|
||||
}
|
||||
return dataVolumeGPRSDownlink;
|
||||
return usedUnitContainer[0].dataVolumeDownlink;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.durationTime'), // 持续时间
|
||||
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,
|
||||
@@ -201,13 +198,13 @@ let tableColumns: ColumnsType = [
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.invocationTime'), // 操作时间
|
||||
title: t('views.dashboard.cdr.smfInvocationTime'), // 调用时间
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return cdrJSON.recordOpeningTime;
|
||||
return cdrJSON.invocationTimestamp;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -287,7 +284,7 @@ function fnRecordDelete(id: string) {
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delSGWCDataCDR(id)
|
||||
delSMFDataCDR(id)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
@@ -310,18 +307,6 @@ function fnRecordDelete(id: string) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制CDR
|
||||
* @param jsonStr JSON字符串
|
||||
*/
|
||||
function fnRecordCopy(jsonStr: string) {
|
||||
if (!jsonStr) return;
|
||||
const text = JSON.stringify(jsonStr, null, 2);
|
||||
copy(text).then(() => {
|
||||
message.success(t('common.copyOk'), 3);
|
||||
});
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
@@ -329,18 +314,12 @@ function fnGetList(pageNum?: number) {
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
// 时间范围
|
||||
if (
|
||||
Array.isArray(queryRangePicker.value) &&
|
||||
queryRangePicker.value.length > 0
|
||||
) {
|
||||
queryParams.startTime = queryRangePicker.value[0].valueOf();
|
||||
queryParams.endTime = queryRangePicker.value[1].valueOf();
|
||||
} else {
|
||||
queryParams.startTime = undefined;
|
||||
queryParams.endTime = undefined;
|
||||
if (!queryRangePicker.value) {
|
||||
queryRangePicker.value = ['', ''];
|
||||
}
|
||||
listSGWCDataCDR(toRaw(queryParams)).then(res => {
|
||||
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) {
|
||||
@@ -384,14 +363,14 @@ function fnExportList() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const querys = toRaw(queryParams);
|
||||
querys.pageSize = 10000;
|
||||
exportSGWCDataCDR(querys)
|
||||
exportSMFDataCDR(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 3,
|
||||
});
|
||||
saveAs(res.data, `sgwc_cdr_event_export_${Date.now()}.xlsx`);
|
||||
saveAs(res.data, `smf_cdr_event_export_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
@@ -423,14 +402,12 @@ function fnRealTime() {
|
||||
params: {
|
||||
/**订阅通道组
|
||||
*
|
||||
* CDR会话事件-SGWC (GroupID:1008)
|
||||
* CDR会话事件-SMF (GroupID:1006)
|
||||
*/
|
||||
subGroupID: `1008_${queryParams.neId}`,
|
||||
subGroupID: `1006_${queryParams.neId}`,
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: (ev: any) => {
|
||||
console.error(ev);
|
||||
},
|
||||
onerror: wsError,
|
||||
};
|
||||
ws.connect(options);
|
||||
} else {
|
||||
@@ -440,6 +417,12 @@ function fnRealTime() {
|
||||
}
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsError(ev: any) {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
}
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
@@ -453,7 +436,7 @@ function wsMessage(res: Record<string, any>) {
|
||||
return;
|
||||
}
|
||||
// cdrEvent CDR会话事件
|
||||
if (data.groupId === `1008_${queryParams.neId}`) {
|
||||
if (data.groupId === `1006_${queryParams.neId}`) {
|
||||
const cdrEvent = data.data;
|
||||
queue.add(async () => {
|
||||
modalState.maxId += 1;
|
||||
@@ -483,7 +466,7 @@ onMounted(() => {
|
||||
if (res.data.length > 0) {
|
||||
let arr: Record<string, any>[] = [];
|
||||
res.data.forEach(i => {
|
||||
if (i.neType === 'SGWC') {
|
||||
if (i.neType === 'SMF') {
|
||||
arr.push({ value: i.neId, label: i.neName });
|
||||
}
|
||||
});
|
||||
@@ -523,39 +506,41 @@ onBeforeUnmount(() => {
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="SGWC" name="neId ">
|
||||
<a-form-item label="SMF" name="neId ">
|
||||
<a-select
|
||||
v-model:value="queryParams.neId"
|
||||
:options="neOtions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnQueryReset()"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.dashboard.cdr.sgwcServedIMSI')"
|
||||
name="imsi"
|
||||
:label="t('views.dashboard.cdr.smfSubscriptionIDData')"
|
||||
name="subscriberID"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.imsi"
|
||||
v-model:value="queryParams.subscriberID"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="40"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.dashboard.cdr.sgwcServedMSISDN')"
|
||||
name="msisdn"
|
||||
:label="t('views.dashboard.cdr.time')"
|
||||
name="queryRangePicker"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.msisdn"
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="40"
|
||||
></a-input>
|
||||
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">
|
||||
@@ -572,22 +557,6 @@ onBeforeUnmount(() => {
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.dashboard.cdr.time')"
|
||||
name="queryRangePicker"
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
:presets="rangePickerPresets"
|
||||
:bordered="true"
|
||||
:allow-clear="false"
|
||||
style="width: 100%"
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
@@ -623,7 +592,7 @@ onBeforeUnmount(() => {
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.confirmLoading"
|
||||
@click.prevent="fnRecordDelete('0')"
|
||||
v-perms:has="['cdr:ne:remove']"
|
||||
v-if="!hasRoles(['student'])"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('common.deleteText') }}
|
||||
@@ -686,7 +655,7 @@ onBeforeUnmount(() => {
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:columns="hasRoles(['student']) ? tableColumns.filter((s:any)=>s.key !== 'id'): tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
@@ -702,23 +671,11 @@ onBeforeUnmount(() => {
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.copyText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordCopy(record.cdrJSON)"
|
||||
>
|
||||
<template #icon>
|
||||
<CopyOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordDelete(record.id)"
|
||||
v-perms:has="['cdr:ne:remove']"
|
||||
>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
@@ -744,11 +701,15 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.time') }}: </span>
|
||||
<span>{{ record.cdrJSON.recordOpeningTime }}</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>
|
||||
@@ -765,74 +726,101 @@ onBeforeUnmount(() => {
|
||||
<span>Duration: </span>
|
||||
<span>{{ record.cdrJSON.duration }}</span>
|
||||
</div>
|
||||
<a-divider orientation="left"> Subscriber Identifier </a-divider>
|
||||
<div>
|
||||
<span>Record Access Point Name NI: </span>
|
||||
<span>{{ record.cdrJSON.accessPointNameNI }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Record Cause For Rec Closing: </span>
|
||||
<span>{{ record.cdrJSON.causeForRecClosing }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Record Sequence Number: </span>
|
||||
<span>{{ record.cdrJSON.recordSequenceNumber }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Local Record Sequence Number: </span>
|
||||
<span>{{ record.cdrJSON.localRecordSequenceNumber }}</span>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-divider orientation="left"> Server Information </a-divider>
|
||||
<div>
|
||||
<span>IMSI: </span>
|
||||
<span> {{ record.cdrJSON.servedIMSI }} </span>
|
||||
</div>
|
||||
<div>
|
||||
<span>MSISDN: </span>
|
||||
<span> {{ record.cdrJSON.servedMSISDN }} </span>
|
||||
</div>
|
||||
<div v-if="record.cdrJSON.pGWAddressUsed">
|
||||
<span>PGW Address: </span>
|
||||
<span> {{ record.cdrJSON.pGWAddressUsed }} </span>
|
||||
</div>
|
||||
<div v-if="record.cdrJSON.GGSNAddress">
|
||||
<span>GGSN Address: </span>
|
||||
<span> {{ record.cdrJSON.GGSNAddress }} </span>
|
||||
</div>
|
||||
<div v-if="record.cdrJSON.sGWAddress">
|
||||
<span>SGW Address: </span>
|
||||
<span> {{ record.cdrJSON.sGWAddress }} </span>
|
||||
</div>
|
||||
<div v-if="record.cdrJSON.SGSNAddress">
|
||||
<span>SGSN Address: </span>
|
||||
<span> {{ record.cdrJSON.SGSNAddress }} </span>
|
||||
</div>
|
||||
<div>
|
||||
<span>RAT Type: </span>
|
||||
<span> {{ record.cdrJSON.rATType }} </span>
|
||||
</div>
|
||||
<a-divider orientation="left"> PDPPD Information </a-divider>
|
||||
<div>
|
||||
<span>PDPPDN Type: </span>
|
||||
<span> {{ record.cdrJSON.pdpPDNType }} </span>
|
||||
</div>
|
||||
<div>
|
||||
<span>PDPPDN Address: </span>
|
||||
<span> {{ record.cdrJSON.servedPDPPDNAddress }} </span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Node Address: </span>
|
||||
<span>Subscription ID Type: </span>
|
||||
<span>
|
||||
{{ record.cdrJSON.servingNodeAddress?.join(', ') }}
|
||||
{{ record.cdrJSON.subscriberIdentifier?.subscriptionIDType }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Node Type: </span>
|
||||
<span>Subscription ID Data: </span>
|
||||
<span>
|
||||
<template v-for="item in record.cdrJSON.servingNodeType">
|
||||
{{ item.servingNodeType }}
|
||||
</template>
|
||||
{{ 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>
|
||||
770
practical_training/views/dashboard/smscCDR/index.vue
Normal file
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
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
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
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
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
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
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
1584
practical_training/views/system/user/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* =============== Configuration File Description ===============
|
||||
*
|
||||
*
|
||||
* - Nginx Deployment
|
||||
* Delete the file with the same name under the same level of loading.js, Nginx proxy address: /omc-api
|
||||
*
|
||||
@@ -10,19 +10,12 @@
|
||||
*
|
||||
*/
|
||||
(function () {
|
||||
// baseUrl = protocol://ip:port
|
||||
// baseUrl = 'http://192.168.8.100:33030';
|
||||
const protocol = window.location.protocol
|
||||
let wsprotocol = "ws:"
|
||||
const hostname = window.location.hostname
|
||||
let host = `${hostname}:33030`;
|
||||
if (protocol === 'https:') {
|
||||
host = `${hostname}:33443`;
|
||||
wsprotocol = "wss:"
|
||||
}
|
||||
|
||||
// host = ip:port
|
||||
// const host = '192.168.8.100:33030';
|
||||
const host = `${window.location.hostname}:33030`;
|
||||
|
||||
// Service Address
|
||||
sessionStorage.setItem('baseUrl', `${protocol}//${host}`);
|
||||
sessionStorage.setItem('baseUrl', `http://${host}`);
|
||||
// websocket Address
|
||||
sessionStorage.setItem('wsUrl', `${wsprotocol}//${host}`);
|
||||
sessionStorage.setItem('wsUrl', `ws://${host}`);
|
||||
})();
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,28 +0,0 @@
|
||||
<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
|
||||
|
||||
<g>
|
||||
<title>background</title>
|
||||
<rect fill="none" id="canvas_background" height="1026" width="1026" y="-1" x="-1"/>
|
||||
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
|
||||
<rect fill="url(#gridpattern)" stroke-width="0" y="1" x="1" height="768" width="1024"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<g stroke="null" id="svg_15">
|
||||
<path stroke="null" id="svg_4" fill="#B5D6FB" d="m512.094844,961.632039c-1.327621,0 -2.560412,-0.405439 -3.793202,-1.114958l-405.588164,-251.575028c-2.275921,-1.419037 -3.698372,-4.054392 -3.698372,-6.892467l0,-90.007504c0,-2.838074 1.422451,-5.473429 3.698372,-6.892467l405.588164,-255.426701c1.137961,-0.709519 2.465582,-1.114958 3.793202,-1.114958s2.655242,0.405439 3.793202,1.114958l405.493334,255.426701c2.275921,1.419037 3.698372,4.054392 3.698372,6.892467l0,90.007504c0,2.838074 -1.422451,5.473429 -3.698372,6.892467l-405.588164,251.575028c-1.137961,0.709519 -2.465582,1.114958 -3.698372,1.114958z"/>
|
||||
<path stroke="null" id="svg_5" fill="#0276F7" d="m512.094844,356.615382l405.398504,255.426701l0,90.007504l-66.096551,40.94936l-339.301952,210.625668l-339.491613,-210.625668l-66.096551,-40.94936l0,-90.007504l405.588164,-255.426701m0,-16.014849c-2.655242,0 -5.215653,0.709519 -7.586405,2.229916l-405.588164,255.426701c-4.551843,2.838074 -7.396745,8.108784 -7.396745,13.784933l0,90.007504c0,5.676149 2.844902,10.946859 7.491575,13.886293l66.096551,41.05072l339.491613,210.625668c2.275921,1.419037 4.931163,2.128556 7.491575,2.128556s5.215653,-0.709519 7.491575,-2.128556l339.301952,-210.625668l66.096551,-40.94936c4.646673,-2.838074 7.491575,-8.108784 7.491575,-13.886293l0,-90.007504c0,-5.676149 -2.844902,-10.946859 -7.396745,-13.784933l-405.398504,-255.426701c-2.370751,-1.520397 -5.025993,-2.331275 -7.586405,-2.331275z"/>
|
||||
<path stroke="null" id="svg_6" fill="#FFFFFF" d="m106.50668,612.042083l405.493334,253.298145l405.493334,-253.298145l-405.398504,-255.426701l-405.588164,255.426701z"/>
|
||||
<path stroke="null" id="svg_7" fill="#D4E4FC" d="m501.473877,64.192353l-254.9032,498.487506l263.343075,161.162085l266.662127,-162.074323l-275.102002,-497.575268z"/>
|
||||
<path stroke="null" id="svg_8" fill="#0276F7" d="m229.975417,602.311542c-1.232791,0 -2.465582,-0.304079 -3.698372,-1.013598c-3.603542,-2.128556 -4.931163,-6.993826 -2.844902,-10.845499l279.653845,-532.13896c1.327621,-2.533995 3.793202,-4.054392 6.543274,-4.054392c2.655242,0 5.120823,1.520397 6.543274,4.054392l284.395348,532.13896c2.086261,3.851672 0.75864,8.716943 -2.750072,10.946859c-3.603542,2.128556 -8.155385,0.810878 -10.241646,-2.939434l-277.852074,-519.874424l-273.205401,519.671704c-1.422451,2.635355 -3.982862,4.054392 -6.543274,4.054392z"/>
|
||||
<path stroke="null" id="svg_9" fill="#0276F7" d="m509.913752,755.567562c-4.172523,0 -7.491575,-3.547593 -7.491575,-8.007424l0,-666.744777c0,-4.459831 3.319052,-8.007424 7.491575,-8.007424s7.491575,3.547593 7.491575,8.007424l0,666.846137c0,4.358471 -3.413882,7.906065 -7.491575,7.906065z"/>
|
||||
<path stroke="null" id="svg_10" fill="#0276F7" d="m509.913752,731.849369c-1.327621,0 -2.560412,-0.405439 -3.698372,-1.013598l-263.343075,-161.162085c-3.603542,-2.229916 -4.836333,-7.095186 -2.750072,-10.946859c2.086261,-3.851672 6.638104,-5.16935 10.241646,-2.939434l259.549873,158.83081l262.963755,-159.844408c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.946859l-266.662127,162.074323c-1.137961,0.709519 -2.465582,1.013598 -3.698372,1.013598z"/>
|
||||
<path stroke="null" id="svg_11" fill="#0276F7" d="m509.913752,579.708306c-1.327621,0 -2.560412,-0.405439 -3.793202,-1.114958l-201.988026,-125.686154c-3.603542,-2.229916 -4.741503,-7.095186 -2.750072,-10.946859c2.086261,-3.851672 6.638104,-5.16935 10.241646,-2.838074l198.289654,123.354879l201.798366,-122.138561c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.845499l-205.496739,124.469837c-1.137961,0.709519 -2.465582,1.013598 -3.698372,1.013598zm-2.465582,-157.513132c-1.232791,0 -2.370751,-0.304079 -3.508712,-0.912238l-140.917468,-79.668804c-3.698372,-2.128556 -5.025993,-6.892467 -3.129392,-10.845499c1.896601,-3.953032 6.448444,-5.37207 10.146816,-3.344873l137.503586,77.742968l143.00373,-79.871524c3.698372,-2.027196 8.155385,-0.506799 10.146816,3.344873c1.896601,3.953032 0.47415,8.716943 -3.129392,10.845499l-146.512442,81.79736c-1.232791,0.608159 -2.370751,0.912238 -3.603542,0.912238zm2.465582,-148.49211c-1.232791,0 -2.465582,-0.304079 -3.508712,-0.912238l-82.312492,-47.436387c-3.603542,-2.128556 -5.025993,-6.993826 -3.034562,-10.845499c1.991431,-3.953032 6.543274,-5.27071 10.146816,-3.243514l78.708949,45.409191l78.329629,-47.537747c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.946859l-81.933171,49.666303c-1.232791,0.608159 -2.560412,0.912238 -3.793202,0.912238z"/>
|
||||
<path stroke="null" id="svg_12" fill="#0276F7" d="m509.913752,579.708306l-0.28449,0l-263.248245,-9.021022c-4.172523,-0.10136 -7.396745,-3.851672 -7.207085,-8.210144c0.09483,-4.459831 4.077693,-7.703345 7.681235,-7.703345l263.343075,9.021022c4.172523,0.10136 7.396745,3.851672 7.207085,8.210144c-0.18966,4.257112 -3.508712,7.703345 -7.491575,7.703345zm0,152.141063c-1.612111,0 -3.224222,-0.608159 -4.646673,-1.723117c-3.224222,-2.736715 -3.793202,-7.804705 -1.232791,-11.250938l205.496739,-276.610899c2.560412,-3.446233 7.301915,-4.054392 10.526137,-1.317677c3.224222,2.736715 3.793202,7.804705 1.232791,11.250938l-205.496739,276.610899c-1.517281,2.027196 -3.698372,3.040794 -5.879464,3.040794z"/>
|
||||
<path stroke="null" id="svg_13" fill="#0276F7" d="m509.913752,579.708306c-1.422451,0 -2.750072,-0.405439 -4.077693,-1.216318c-3.508712,-2.432635 -4.457013,-7.297906 -2.275921,-11.048218l144.14169,-239.310492c2.275921,-3.750313 6.922594,-4.763911 10.336476,-2.432635c3.508712,2.432635 4.457013,7.297906 2.275921,11.048218l-144.14169,239.310492c-1.422451,2.331275 -3.793202,3.648953 -6.258784,3.648953zm-2.465582,-157.513132c-1.043131,0 -2.086261,-0.20272 -3.129392,-0.709519c-3.793202,-1.824476 -5.405313,-6.588387 -3.698372,-10.642779l84.398753,-198.158413c1.706941,-4.054392 6.069124,-5.777509 9.957156,-3.953032c3.793202,1.824476 5.405313,6.588387 3.698372,10.642779l-84.303923,198.158413c-1.327621,2.939434 -4.077693,4.662551 -6.922594,4.662551z"/>
|
||||
<path stroke="null" id="svg_14" fill="#0276F7" d="m591.846924,375.062866c-2.750072,0 -5.405313,-1.621757 -6.732934,-4.459831c-1.801771,-3.953032 -0.28449,-8.716943 3.413882,-10.642779l129.253371,-67.302908l-365.759539,-178.089172l20.862613,208.091673l133.994874,-64.262114c3.698372,-1.824476 8.155385,0 9.862326,4.054392c1.706941,4.054392 0,8.716943 -3.793202,10.541419l-143.38305,68.823305c-2.181091,1.013598 -4.646673,0.912238 -6.827764,-0.405439c-2.086261,-1.317677 -3.413882,-3.547593 -3.698372,-6.081588l-23.328195,-233.026185c-0.28449,-2.838074 0.853471,-5.676149 3.034562,-7.297906c2.181091,-1.621757 5.025993,-2.027196 7.491575,-0.810878l392.217126,190.961867c2.655242,1.317677 4.362183,4.054392 4.362183,7.196546c0,3.142154 -1.612111,5.980228 -4.172523,7.297906l-143.57271,74.600814c-1.043131,0.608159 -2.181091,0.810878 -3.224222,0.810878zm-283.921198,78.959286c-3.603542,0 -6.827764,-2.838074 -7.396745,-6.791107c-0.56898,-4.358471 2.181091,-8.412864 6.258784,-9.122382l199.617275,-31.826978c4.077693,-0.608159 7.870895,2.331275 8.534705,6.689747c0.56898,4.358471 -2.181091,8.412864 -6.258784,9.122382l-199.617275,31.826978c-0.47415,0.10136 -0.853471,0.10136 -1.137961,0.10136z"/>
|
||||
</g>
|
||||
<text stroke="null" font-style="italic" transform="matrix(6.577099502228161,0,0,7.449448263868419,-1073.2057632249744,-908.8606073938396) " xml:space="preserve" text-anchor="start" font-family="Arvo, sans-serif" font-size="24" id="svg_16" y="177.898525" x="178.621382" stroke-width="0" fill="#B5D6FB">4G</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.9 KiB |
@@ -1,28 +0,0 @@
|
||||
<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
|
||||
|
||||
<g>
|
||||
<title>background</title>
|
||||
<rect fill="none" id="canvas_background" height="1026" width="1026" y="-1" x="-1"/>
|
||||
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
|
||||
<rect fill="url(#gridpattern)" stroke-width="0" y="1" x="1" height="768" width="1024"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<g stroke="null" id="svg_15">
|
||||
<path stroke="null" id="svg_4" fill="#B5D6FB" d="m512.094844,961.632039c-1.327621,0 -2.560412,-0.405439 -3.793202,-1.114958l-405.588164,-251.575028c-2.275921,-1.419037 -3.698372,-4.054392 -3.698372,-6.892467l0,-90.007504c0,-2.838074 1.422451,-5.473429 3.698372,-6.892467l405.588164,-255.426701c1.137961,-0.709519 2.465582,-1.114958 3.793202,-1.114958s2.655242,0.405439 3.793202,1.114958l405.493334,255.426701c2.275921,1.419037 3.698372,4.054392 3.698372,6.892467l0,90.007504c0,2.838074 -1.422451,5.473429 -3.698372,6.892467l-405.588164,251.575028c-1.137961,0.709519 -2.465582,1.114958 -3.698372,1.114958z"/>
|
||||
<path stroke="null" id="svg_5" fill="#0276F7" d="m512.094844,356.615382l405.398504,255.426701l0,90.007504l-66.096551,40.94936l-339.301952,210.625668l-339.491613,-210.625668l-66.096551,-40.94936l0,-90.007504l405.588164,-255.426701m0,-16.014849c-2.655242,0 -5.215653,0.709519 -7.586405,2.229916l-405.588164,255.426701c-4.551843,2.838074 -7.396745,8.108784 -7.396745,13.784933l0,90.007504c0,5.676149 2.844902,10.946859 7.491575,13.886293l66.096551,41.05072l339.491613,210.625668c2.275921,1.419037 4.931163,2.128556 7.491575,2.128556s5.215653,-0.709519 7.491575,-2.128556l339.301952,-210.625668l66.096551,-40.94936c4.646673,-2.838074 7.491575,-8.108784 7.491575,-13.886293l0,-90.007504c0,-5.676149 -2.844902,-10.946859 -7.396745,-13.784933l-405.398504,-255.426701c-2.370751,-1.520397 -5.025993,-2.331275 -7.586405,-2.331275z"/>
|
||||
<path stroke="null" id="svg_6" fill="#FFFFFF" d="m106.50668,612.042083l405.493334,253.298145l405.493334,-253.298145l-405.398504,-255.426701l-405.588164,255.426701z"/>
|
||||
<path stroke="null" id="svg_7" fill="#D4E4FC" d="m501.473877,64.192353l-254.9032,498.487506l263.343075,161.162085l266.662127,-162.074323l-275.102002,-497.575268z"/>
|
||||
<path stroke="null" id="svg_8" fill="#0276F7" d="m229.975417,602.311542c-1.232791,0 -2.465582,-0.304079 -3.698372,-1.013598c-3.603542,-2.128556 -4.931163,-6.993826 -2.844902,-10.845499l279.653845,-532.13896c1.327621,-2.533995 3.793202,-4.054392 6.543274,-4.054392c2.655242,0 5.120823,1.520397 6.543274,4.054392l284.395348,532.13896c2.086261,3.851672 0.75864,8.716943 -2.750072,10.946859c-3.603542,2.128556 -8.155385,0.810878 -10.241646,-2.939434l-277.852074,-519.874424l-273.205401,519.671704c-1.422451,2.635355 -3.982862,4.054392 -6.543274,4.054392z"/>
|
||||
<path stroke="null" id="svg_9" fill="#0276F7" d="m509.913752,755.567562c-4.172523,0 -7.491575,-3.547593 -7.491575,-8.007424l0,-666.744777c0,-4.459831 3.319052,-8.007424 7.491575,-8.007424s7.491575,3.547593 7.491575,8.007424l0,666.846137c0,4.358471 -3.413882,7.906065 -7.491575,7.906065z"/>
|
||||
<path stroke="null" id="svg_10" fill="#0276F7" d="m509.913752,731.849369c-1.327621,0 -2.560412,-0.405439 -3.698372,-1.013598l-263.343075,-161.162085c-3.603542,-2.229916 -4.836333,-7.095186 -2.750072,-10.946859c2.086261,-3.851672 6.638104,-5.16935 10.241646,-2.939434l259.549873,158.83081l262.963755,-159.844408c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.946859l-266.662127,162.074323c-1.137961,0.709519 -2.465582,1.013598 -3.698372,1.013598z"/>
|
||||
<path stroke="null" id="svg_11" fill="#0276F7" d="m509.913752,579.708306c-1.327621,0 -2.560412,-0.405439 -3.793202,-1.114958l-201.988026,-125.686154c-3.603542,-2.229916 -4.741503,-7.095186 -2.750072,-10.946859c2.086261,-3.851672 6.638104,-5.16935 10.241646,-2.838074l198.289654,123.354879l201.798366,-122.138561c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.845499l-205.496739,124.469837c-1.137961,0.709519 -2.465582,1.013598 -3.698372,1.013598zm-2.465582,-157.513132c-1.232791,0 -2.370751,-0.304079 -3.508712,-0.912238l-140.917468,-79.668804c-3.698372,-2.128556 -5.025993,-6.892467 -3.129392,-10.845499c1.896601,-3.953032 6.448444,-5.37207 10.146816,-3.344873l137.503586,77.742968l143.00373,-79.871524c3.698372,-2.027196 8.155385,-0.506799 10.146816,3.344873c1.896601,3.953032 0.47415,8.716943 -3.129392,10.845499l-146.512442,81.79736c-1.232791,0.608159 -2.370751,0.912238 -3.603542,0.912238zm2.465582,-148.49211c-1.232791,0 -2.465582,-0.304079 -3.508712,-0.912238l-82.312492,-47.436387c-3.603542,-2.128556 -5.025993,-6.993826 -3.034562,-10.845499c1.991431,-3.953032 6.543274,-5.27071 10.146816,-3.243514l78.708949,45.409191l78.329629,-47.537747c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.946859l-81.933171,49.666303c-1.232791,0.608159 -2.560412,0.912238 -3.793202,0.912238z"/>
|
||||
<path stroke="null" id="svg_12" fill="#0276F7" d="m509.913752,579.708306l-0.28449,0l-263.248245,-9.021022c-4.172523,-0.10136 -7.396745,-3.851672 -7.207085,-8.210144c0.09483,-4.459831 4.077693,-7.703345 7.681235,-7.703345l263.343075,9.021022c4.172523,0.10136 7.396745,3.851672 7.207085,8.210144c-0.18966,4.257112 -3.508712,7.703345 -7.491575,7.703345zm0,152.141063c-1.612111,0 -3.224222,-0.608159 -4.646673,-1.723117c-3.224222,-2.736715 -3.793202,-7.804705 -1.232791,-11.250938l205.496739,-276.610899c2.560412,-3.446233 7.301915,-4.054392 10.526137,-1.317677c3.224222,2.736715 3.793202,7.804705 1.232791,11.250938l-205.496739,276.610899c-1.517281,2.027196 -3.698372,3.040794 -5.879464,3.040794z"/>
|
||||
<path stroke="null" id="svg_13" fill="#0276F7" d="m509.913752,579.708306c-1.422451,0 -2.750072,-0.405439 -4.077693,-1.216318c-3.508712,-2.432635 -4.457013,-7.297906 -2.275921,-11.048218l144.14169,-239.310492c2.275921,-3.750313 6.922594,-4.763911 10.336476,-2.432635c3.508712,2.432635 4.457013,7.297906 2.275921,11.048218l-144.14169,239.310492c-1.422451,2.331275 -3.793202,3.648953 -6.258784,3.648953zm-2.465582,-157.513132c-1.043131,0 -2.086261,-0.20272 -3.129392,-0.709519c-3.793202,-1.824476 -5.405313,-6.588387 -3.698372,-10.642779l84.398753,-198.158413c1.706941,-4.054392 6.069124,-5.777509 9.957156,-3.953032c3.793202,1.824476 5.405313,6.588387 3.698372,10.642779l-84.303923,198.158413c-1.327621,2.939434 -4.077693,4.662551 -6.922594,4.662551z"/>
|
||||
<path stroke="null" id="svg_14" fill="#0276F7" d="m591.846924,375.062866c-2.750072,0 -5.405313,-1.621757 -6.732934,-4.459831c-1.801771,-3.953032 -0.28449,-8.716943 3.413882,-10.642779l129.253371,-67.302908l-365.759539,-178.089172l20.862613,208.091673l133.994874,-64.262114c3.698372,-1.824476 8.155385,0 9.862326,4.054392c1.706941,4.054392 0,8.716943 -3.793202,10.541419l-143.38305,68.823305c-2.181091,1.013598 -4.646673,0.912238 -6.827764,-0.405439c-2.086261,-1.317677 -3.413882,-3.547593 -3.698372,-6.081588l-23.328195,-233.026185c-0.28449,-2.838074 0.853471,-5.676149 3.034562,-7.297906c2.181091,-1.621757 5.025993,-2.027196 7.491575,-0.810878l392.217126,190.961867c2.655242,1.317677 4.362183,4.054392 4.362183,7.196546c0,3.142154 -1.612111,5.980228 -4.172523,7.297906l-143.57271,74.600814c-1.043131,0.608159 -2.181091,0.810878 -3.224222,0.810878zm-283.921198,78.959286c-3.603542,0 -6.827764,-2.838074 -7.396745,-6.791107c-0.56898,-4.358471 2.181091,-8.412864 6.258784,-9.122382l199.617275,-31.826978c4.077693,-0.608159 7.870895,2.331275 8.534705,6.689747c0.56898,4.358471 -2.181091,8.412864 -6.258784,9.122382l-199.617275,31.826978c-0.47415,0.10136 -0.853471,0.10136 -1.137961,0.10136z"/>
|
||||
</g>
|
||||
<text stroke="null" font-style="italic" transform="matrix(6.577099502228161,0,0,7.449448263868419,-1073.2057632249744,-908.8606073938396) " xml:space="preserve" text-anchor="start" font-family="Arvo, sans-serif" font-size="24" id="svg_16" y="177.898525" x="178.621382" stroke-width="0" fill="#D4E4FC">5G</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.9 KiB |
@@ -1,7 +1,7 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
import { parseDateToStr, YYYY_MM_DD_HH_MM_SS } from '@/utils/date-utils';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
|
||||
/**
|
||||
@@ -122,7 +122,7 @@ export function updateConfirm(data: Record<string, any>) {
|
||||
const userName = useUserStore().userName;
|
||||
let finalData = {
|
||||
alarm: {
|
||||
ack_time: parseDateToStr(time, YYYY_MM_DD_HH_MM_SS),
|
||||
ack_time: parseDateToStr(time),
|
||||
ack_user: userName,
|
||||
ack_state: '1',
|
||||
},
|
||||
@@ -145,7 +145,7 @@ export function cancelConfirm(data: (string | number)[]) {
|
||||
const userName = useUserStore().userName;
|
||||
let finalData = {
|
||||
alarm: {
|
||||
ack_time: parseDateToStr(time, YYYY_MM_DD_HH_MM_SS),
|
||||
ack_time: parseDateToStr(time),
|
||||
ack_user: '',
|
||||
ack_state: '0',
|
||||
},
|
||||
@@ -216,7 +216,7 @@ export function clearAlarm(data: Record<string, any>) {
|
||||
const userName = useUserStore().userName;
|
||||
let finalData = {
|
||||
data: {
|
||||
clear_time: parseDateToStr(time, YYYY_MM_DD_HH_MM_SS),
|
||||
clear_time: parseDateToStr(time),
|
||||
clear_type: '2',
|
||||
alarm_status: '0',
|
||||
clear_user: userName,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
|
||||
@@ -63,11 +63,7 @@ export async function getAlarmSet() {
|
||||
}
|
||||
}
|
||||
if (Object.keys(resultData).length === 0) {
|
||||
return {
|
||||
code: RESULT_CODE_ERROR,
|
||||
msg: RESULT_MSG_ERROR[language],
|
||||
data: {},
|
||||
};
|
||||
return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_ERROR[language], data: {} };
|
||||
}
|
||||
return {
|
||||
code: RESULT_CODE_SUCCESS,
|
||||
@@ -121,11 +117,7 @@ export async function updateAlarmSet(data: Record<string, any>) {
|
||||
}
|
||||
// 无变更时
|
||||
if (resultNum === 0) {
|
||||
return {
|
||||
code: RESULT_CODE_ERROR,
|
||||
msg: RESULT_MSG_ERROR[language],
|
||||
data: 0,
|
||||
};
|
||||
return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_ERROR[language], data: 0 };
|
||||
}
|
||||
return {
|
||||
code: RESULT_CODE_SUCCESS,
|
||||
@@ -174,10 +166,11 @@ export async function getForwardSet() {
|
||||
*/
|
||||
export async function updateForwardSet(data: Record<string, any>) {
|
||||
// return false;
|
||||
let obj: any = [
|
||||
{ interface: 'Email', to_user: data.emailObj },
|
||||
{ interface: 'SMS', to_user: data.smsObj },
|
||||
];
|
||||
console.log(data)
|
||||
let obj:any=[
|
||||
{interface:"Email",to_user:data.emailObj},
|
||||
{interface:"SMS",to_user:data.smsObj}
|
||||
]
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/config?WHERE=config_tag='forwardAlarm'`,
|
||||
method: 'put',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
import { parseDateToStr, YYYY_MM_DD_HH_MM_SS } from '@/utils/date-utils';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
|
||||
/**
|
||||
@@ -79,7 +79,7 @@ export function updateConfirm(data: Record<string, any>) {
|
||||
const userName = useUserStore().userName;
|
||||
let finalData = {
|
||||
alarm: {
|
||||
ack_time: parseDateToStr(time, YYYY_MM_DD_HH_MM_SS),
|
||||
ack_time: parseDateToStr(time),
|
||||
ack_user: userName,
|
||||
ack_state: '1',
|
||||
},
|
||||
@@ -101,7 +101,7 @@ export function cancelConfirm(data: (string | number)[]) {
|
||||
var time = new Date();
|
||||
let finalData = {
|
||||
alarm: {
|
||||
ack_time: parseDateToStr(time, YYYY_MM_DD_HH_MM_SS),
|
||||
ack_time: parseDateToStr(time),
|
||||
ack_user: '',
|
||||
ack_state: '0',
|
||||
},
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
|
||||
import { sessionGet } from '@/utils/cache-session-utils';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
@@ -53,43 +51,3 @@ export function delFile(query: Record<string, any>) {
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新FTP信息
|
||||
* @param data 数据
|
||||
* @returns object
|
||||
*/
|
||||
export function updateFTPInfo(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/lm/table/ftp`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取FTP信息
|
||||
* @param data 数据
|
||||
* @returns object
|
||||
*/
|
||||
export function getFTPInfo() {
|
||||
return request({
|
||||
url: `/lm/table/ftp`,
|
||||
method: 'get',
|
||||
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送FTP文件
|
||||
* @param data 数据
|
||||
* @returns object
|
||||
*/
|
||||
export function putFTPInfo(filePath: string, fileName: string) {
|
||||
return request({
|
||||
url: `/lm/table/ftp`,
|
||||
method: 'put',
|
||||
data: { filePath, fileName },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
|
||||
import { sessionGet } from '@/utils/cache-session-utils';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
// 登录方法
|
||||
@@ -9,7 +7,7 @@ export function login(data: Record<string, string>) {
|
||||
method: 'post',
|
||||
data: data,
|
||||
whithToken: false,
|
||||
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
|
||||
crypto: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,7 +22,7 @@ export function register(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data: data,
|
||||
whithToken: false,
|
||||
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
|
||||
crypto: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
|
||||
import { sessionGet } from '@/utils/cache-session-utils';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
@@ -38,7 +36,7 @@ export function addNeInfo(data: Record<string, any>) {
|
||||
url: `/ne/info`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
|
||||
crypto: true,
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
@@ -53,7 +51,7 @@ export function updateNeInfo(data: Record<string, any>) {
|
||||
url: `/ne/info`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
|
||||
crypto: true,
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ export function listAMFDataUE(query: Record<string, any>) {
|
||||
url: '/neData/amf/ue/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,90 +40,3 @@ export function exportAMFDataUE(data: Record<string, any>) {
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* AMF-接入基站信息列表
|
||||
* @param query 查询参数 neId=001&id=1
|
||||
* @returns object
|
||||
*/
|
||||
export function listAMFNblist(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/amf/nb/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* AMF-接入基站状态信息列表
|
||||
* @param query 查询参数 neId=001&state=1
|
||||
* @returns object
|
||||
*/
|
||||
export function listAMFNbStatelist(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/amf/nb/list-cfg',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* AMF-接入基站状态信息新增
|
||||
* @param neId 网元ID
|
||||
* @param data 数据 { "index": 1, "name": "Gnb", "address": "192.168.8.1", "position": "Area-B" }
|
||||
* @returns object
|
||||
*/
|
||||
export function addAMFNbState(neId: string, data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/config/data`,
|
||||
method: 'post',
|
||||
data: {
|
||||
neType: 'AMF',
|
||||
neId: neId,
|
||||
paramName: 'gnbList',
|
||||
paramData: data,
|
||||
loc: `${data.index}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* AMF-接入基站状态信息修改
|
||||
* @param neId 网元ID
|
||||
* @param data 数据 { "index": 1, "name": "Gnb", "address": "192.168.8.1", "position": "Area-B" }
|
||||
* @returns object
|
||||
*/
|
||||
export function editAMFNbState(neId: string, data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/config/data`,
|
||||
method: 'put',
|
||||
data: {
|
||||
neType: 'AMF',
|
||||
neId: neId,
|
||||
paramName: 'gnbList',
|
||||
paramData: data,
|
||||
loc: `${data.index}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* AMF-接入基站状态信息删除
|
||||
* @param neId 网元ID
|
||||
* @param index 数据index
|
||||
* @returns object
|
||||
*/
|
||||
export function delAMFNbState(neId: string, index: string | number) {
|
||||
return request({
|
||||
url: `/ne/config/data`,
|
||||
method: 'delete',
|
||||
params: {
|
||||
neType: 'AMF',
|
||||
neId: neId,
|
||||
paramName: 'gnbList',
|
||||
loc: `${index}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ export function listIMSDataCDR(query: Record<string, any>) {
|
||||
url: '/neData/ims/cdr/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ export function listMMEDataUE(query: Record<string, any>) {
|
||||
url: '/neData/mme/ue/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -41,90 +40,3 @@ export function exportMMEDataUE(data: Record<string, any>) {
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MME-接入基站信息列表
|
||||
* @param query 查询参数 neId=001&id=1
|
||||
* @returns object
|
||||
*/
|
||||
export function listMMENblist(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/mme/nb/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MME-接入基站状态信息列表
|
||||
* @param query 查询参数 neId=001&state=1
|
||||
* @returns object
|
||||
*/
|
||||
export function listMMENbStatelist(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/mme/nb/list-cfg',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MME-接入基站状态信息新增
|
||||
* @param neId 网元ID
|
||||
* @param data 数据 { "index": 1, "name": "Enb", "address": "192.168.8.1", "position": "Area-B" }
|
||||
* @returns object
|
||||
*/
|
||||
export function addMMENbState(neId: string, data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/config/data`,
|
||||
method: 'post',
|
||||
data: {
|
||||
neType: 'MME',
|
||||
neId: neId,
|
||||
paramName: 'enbList',
|
||||
paramData: data,
|
||||
loc: `${data.index}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MME-接入基站状态信息修改
|
||||
* @param neId 网元ID
|
||||
* @param data 数据 { "index": 1, "name": "Enb", "address": "192.168.8.1", "position": "Area-B" }
|
||||
* @returns object
|
||||
*/
|
||||
export function editMMENbState(neId: string, data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ne/config/data`,
|
||||
method: 'put',
|
||||
data: {
|
||||
neType: 'MME',
|
||||
neId: neId,
|
||||
paramName: 'enbList',
|
||||
paramData: data,
|
||||
loc: `${data.index}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* MME-接入基站状态信息删除
|
||||
* @param neId 网元ID
|
||||
* @param index 数据index
|
||||
* @returns object
|
||||
*/
|
||||
export function delMMENbState(neId: string, index: string | number) {
|
||||
return request({
|
||||
url: `/ne/config/data`,
|
||||
method: 'delete',
|
||||
params: {
|
||||
neType: 'MME',
|
||||
neId: neId,
|
||||
paramName: 'enbList',
|
||||
loc: `${index}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 历史记录列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listNBState(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/nb-state/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 历史记录列表导出
|
||||
* @param data 查询列表条件
|
||||
* @returns object
|
||||
*/
|
||||
export function exportNBState(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/nb-state/export',
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询SGWC-CDR会话事件
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listSGWCDataCDR(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/sgwc/cdr/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SGWC-CDR会话删除
|
||||
* @param id 信息ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delSGWCDataCDR(cdrIds: string | number) {
|
||||
return request({
|
||||
url: `/neData/sgwc/cdr/${cdrIds}`,
|
||||
method: 'delete',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SGWC-CDR会话列表导出
|
||||
* @param data 查询列表条件
|
||||
* @returns object
|
||||
*/
|
||||
export function exportSGWCDataCDR(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/sgwc/cdr/export',
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
@@ -10,7 +10,6 @@ export function listSMFDataCDR(query: Record<string, any>) {
|
||||
url: '/neData/smf/cdr/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,27 +41,14 @@ export function exportSMFDataCDR(data: Record<string, any>) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SMF-在线订阅用户数量
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listSMFSubNum(neId: string) {
|
||||
return request({
|
||||
url: '/neData/smf/sub/num',
|
||||
method: 'get',
|
||||
params: { neId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SMF-在线订阅用户列表信息
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listSMFSubList(query: Record<string, any>) {
|
||||
export function listSMFSubscribers(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/smf/sub/list',
|
||||
url: '/neData/smf/subscribers',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ export function listSMSCDataCDR(query: Record<string, any>) {
|
||||
url: '/neData/smsc/cdr/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
|
||||
import { sessionGet } from '@/utils/cache-session-utils';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
/**
|
||||
* 获取下拉框数据
|
||||
* @returns object
|
||||
*/
|
||||
export function getBakFile() {
|
||||
return request({
|
||||
url: '/ue/table/list',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取对应类型的文件列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function getBakFileList(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ue/file/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 下载远端文件
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function downFile(query: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ue/file/${query.fileName}`,
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 删除远端获取文件
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function delFile(query: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ue/file/${query.fileName}`,
|
||||
method: 'delete',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
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';
|
||||
|
||||
/**
|
||||
* 新 查询自定义指标数据
|
||||
@@ -11,7 +14,6 @@ export async function listCustomData(query: Record<string, any>) {
|
||||
url: `/pm/kpiC/report`,
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
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';
|
||||
|
||||
/**
|
||||
* 查询自定义指标
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
|
||||
/**
|
||||
* 查询任务列表
|
||||
|
||||
120
src/api/pt/neConfig.ts
Normal file
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
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
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
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',
|
||||
});
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export function getNeFile(query: Record<string, any>) {
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob',
|
||||
timeout: 600_000,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export function getNeDirZip(data: Record<string, any>) {
|
||||
method: 'get',
|
||||
params: data,
|
||||
responseType: 'blob',
|
||||
timeout: 600_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
71
src/api/trace/analysis.ts
Normal file
71
src/api/trace/analysis.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
|
||||
/**
|
||||
* 查询信令列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function listTraceData(query: Record<string, any>) {
|
||||
let totalSQL = 'select count(*) as total from trace_data where 1=1 ';
|
||||
let rowsSQL = 'select * from trace_data where 1=1 ';
|
||||
|
||||
// 查询
|
||||
let querySQL = '';
|
||||
if (query.imsi) {
|
||||
querySQL += ` and imsi like '%${query.imsi}%' `;
|
||||
}
|
||||
if (query.msisdn) {
|
||||
querySQL += ` and msisdn like '%${query.msisdn}%' `;
|
||||
}
|
||||
|
||||
// 分页
|
||||
const pageNum = (query.pageNum - 1) * query.pageSize;
|
||||
const limtSql = ` limit ${pageNum},${query.pageSize} `;
|
||||
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/omc_db/trace_data`,
|
||||
method: 'get',
|
||||
params: {
|
||||
totalSQL: totalSQL + querySQL,
|
||||
rowsSQL: rowsSQL + querySQL + limtSql,
|
||||
},
|
||||
});
|
||||
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
const data: DataList = {
|
||||
total: 0,
|
||||
rows: [],
|
||||
code: result.code,
|
||||
msg: result.msg,
|
||||
};
|
||||
result.data.data.forEach((item: any) => {
|
||||
const itemData = item['trace_data'];
|
||||
if (Array.isArray(itemData)) {
|
||||
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
|
||||
data.total = itemData[0]['total'];
|
||||
} else {
|
||||
data.rows = itemData.map(v => parseObjLineToHump(v));
|
||||
}
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 信令数据解析HTML
|
||||
* @param id 任务ID
|
||||
* @returns
|
||||
*/
|
||||
export function getTraceRawInfo(id: Record<string, string>) {
|
||||
return request({
|
||||
url: `/api/rest/traceManagement/v1/decMessage/${id}`,
|
||||
method: 'get',
|
||||
responseType: 'text',
|
||||
});
|
||||
}
|
||||
@@ -62,18 +62,3 @@ export function packetKeep(taskNo: string, duration: number = 120) {
|
||||
data: { taskNo, duration },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 信令跟踪文件
|
||||
* @param taskNo 对象
|
||||
* @returns object
|
||||
*/
|
||||
export function packetPCAPFile(taskNo: string) {
|
||||
return request({
|
||||
url: '/trace/packet/filePull',
|
||||
method: 'get',
|
||||
params: { taskNo },
|
||||
responseType: 'blob',
|
||||
timeout: 680_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
|
||||
/**
|
||||
* 查询跟踪任务列表
|
||||
@@ -74,31 +76,29 @@ export function filePullTask(traceId: string) {
|
||||
method: 'get',
|
||||
params: { traceId },
|
||||
responseType: 'blob',
|
||||
timeout: 600_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟踪任务数据列表
|
||||
* @param query 查询参数
|
||||
* 获取网元跟踪接口列表
|
||||
* @returns object
|
||||
*/
|
||||
export async function listTraceData(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/trace/data/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询跟踪任务数据信息
|
||||
* @param id ID
|
||||
* @returns object
|
||||
*/
|
||||
export async function getTraceData(id: string | number) {
|
||||
return request({
|
||||
url: `/trace/data/${id}`,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -78,6 +78,6 @@ export function filePullTaskHLR(query: Record<string, any>) {
|
||||
method: 'get',
|
||||
params: query,
|
||||
responseType: 'blob',
|
||||
timeout: 600_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { ref, reactive, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
@@ -32,91 +31,8 @@ const terminalDom = ref<HTMLElement | undefined>(undefined);
|
||||
/**终端输入实例对象 */
|
||||
const terminal = ref<any>(null);
|
||||
|
||||
/**终端输入文字状态 */
|
||||
const terminalState = reactive<{
|
||||
/**输入值 */
|
||||
text: string;
|
||||
/**历史 */
|
||||
history: {
|
||||
label?: string;
|
||||
value: string;
|
||||
}[];
|
||||
}>({
|
||||
text: '',
|
||||
history: [
|
||||
{
|
||||
value: 'info server',
|
||||
},
|
||||
{
|
||||
value: 'info replication',
|
||||
},
|
||||
{
|
||||
value: 'keys ausf:*',
|
||||
},
|
||||
{
|
||||
value: 'quit',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/**自动完成根据输入项进行筛选 */
|
||||
function fnAutoCompleteFilter(input: string, option: any) {
|
||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
}
|
||||
|
||||
/**自动完成按键触发 */
|
||||
function fnAutoCompleteKeydown(evt: KeyboardEvent) {
|
||||
if (evt.key === 'Enter') {
|
||||
// 阻止默认的换行行为
|
||||
evt.preventDefault();
|
||||
// 按下 Shift + Enter 键时换行
|
||||
if (evt.shiftKey && evt.target) {
|
||||
// 插入换行符
|
||||
const textarea = evt.target as HTMLInputElement;
|
||||
const start = textarea.selectionStart || 0;
|
||||
const end = textarea.selectionEnd || 0;
|
||||
const text = textarea.value;
|
||||
textarea.value = text.substring(0, start) + '\n' + text.substring(end);
|
||||
terminalState.text = textarea.value;
|
||||
// 更新光标位置
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 1;
|
||||
} else {
|
||||
// ws未连接
|
||||
if (ws.state() !== WebSocket.OPEN) {
|
||||
message.error('disconnected');
|
||||
return;
|
||||
}
|
||||
|
||||
// 输入历史
|
||||
const cmdStr = terminalState.text.trim().replace(/\n/g, '\r\n');
|
||||
const hisIndex = terminalState.history.findIndex(
|
||||
item => item.value === cmdStr
|
||||
);
|
||||
if (hisIndex === -1) {
|
||||
terminalState.history.push({
|
||||
value: cmdStr,
|
||||
});
|
||||
}
|
||||
|
||||
// 发送文本
|
||||
terminal.value.scrollToBottom();
|
||||
terminal.value.writeln('\r\n> ' + cmdStr);
|
||||
ws.send({
|
||||
requestId: `redis_${props.hostId}`,
|
||||
type: 'redis',
|
||||
data: `${cmdStr}\r\n`,
|
||||
});
|
||||
terminalState.text = '';
|
||||
|
||||
// 退出登录
|
||||
if (['q', 'quit', 'exit'].includes(cmdStr)) {
|
||||
setTimeout(() => {
|
||||
ws.close();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**终端输入命令 */
|
||||
const terminalCmd = ref<string>('');
|
||||
|
||||
/**终端输入渲染 */
|
||||
function handleRanderXterm(container: HTMLElement | undefined) {
|
||||
@@ -133,13 +49,47 @@ function handleRanderXterm(container: HTMLElement | undefined) {
|
||||
scrollback: 1000,
|
||||
scrollSensitivity: 15,
|
||||
tabStopWidth: 4,
|
||||
disableStdin: true, // 禁止输入
|
||||
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();
|
||||
@@ -263,21 +213,7 @@ defineExpose({
|
||||
|
||||
<template>
|
||||
<div class="terminal">
|
||||
<div ref="terminalDom" style="height: calc(100% - 36px)" :id="id"></div>
|
||||
<a-auto-complete
|
||||
v-model:value="terminalState.text"
|
||||
:dropdown-match-select-width="500"
|
||||
style="width: 100%"
|
||||
:options="terminalState.history"
|
||||
:filter-option="fnAutoCompleteFilter"
|
||||
:defaultActiveFirstOption="false"
|
||||
>
|
||||
<a-textarea
|
||||
:auto-size="{ minRows: 1, maxRows: 6 }"
|
||||
placeholder="Execute command. Shift+Enter to line feed, Enter to send"
|
||||
@keypress="fnAutoCompleteKeydown"
|
||||
/>
|
||||
</a-auto-complete>
|
||||
<div ref="terminalDom" :id="id" class="terminal"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -80,16 +80,16 @@ function fnAutoCompleteFilter(input: string, option: any) {
|
||||
}
|
||||
|
||||
/**自动完成按键触发 */
|
||||
function fnAutoCompleteKeydown(evt: KeyboardEvent) {
|
||||
function fnAutoCompleteKeydown(evt: any) {
|
||||
if (evt.key === 'Enter') {
|
||||
// 阻止默认的换行行为
|
||||
evt.preventDefault();
|
||||
// 按下 Shift + Enter 键时换行
|
||||
if (evt.shiftKey && evt.target) {
|
||||
if (evt.shiftKey) {
|
||||
// 插入换行符
|
||||
const textarea = evt.target as HTMLInputElement;
|
||||
const start = textarea.selectionStart || 0;
|
||||
const end = textarea.selectionEnd || 0;
|
||||
const textarea = evt.target;
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const text = textarea.value;
|
||||
textarea.value = text.substring(0, start) + '\n' + text.substring(end);
|
||||
terminalState.text = textarea.value;
|
||||
@@ -121,7 +121,7 @@ function fnAutoCompleteKeydown(evt: KeyboardEvent) {
|
||||
type: 'telnet',
|
||||
data: `${cmdStr}\r\n`,
|
||||
});
|
||||
terminalState.text = '';
|
||||
terminalState.text = ' ';
|
||||
|
||||
// 退出登录
|
||||
if (['q', 'quit', 'exit'].includes(cmdStr)) {
|
||||
@@ -296,12 +296,11 @@ defineExpose({
|
||||
style="width: 100%"
|
||||
:options="terminalState.history"
|
||||
:filter-option="fnAutoCompleteFilter"
|
||||
:defaultActiveFirstOption="false"
|
||||
@keydown="fnAutoCompleteKeydown"
|
||||
>
|
||||
<a-textarea
|
||||
:auto-size="{ minRows: 1, maxRows: 6 }"
|
||||
placeholder="Execute command. Shift+Enter to line feed, Enter to send"
|
||||
@keypress="fnAutoCompleteKeydown"
|
||||
/>
|
||||
</a-auto-complete>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/**会话缓存-接口加密 */
|
||||
export const CACHE_SESSION_CRYPTO_API = 'cache:session:cryptoApi';
|
||||
|
||||
/**会话缓存-网络请求 */
|
||||
export const CACHE_SESSION_FATCH = 'cache:session:fatch';
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ export const NE_TYPE_LIST = [
|
||||
'IMS',
|
||||
'AMF',
|
||||
'AUSF',
|
||||
'UDR',
|
||||
'UDM',
|
||||
'SMF',
|
||||
'PCF',
|
||||
@@ -20,8 +19,6 @@ export const NE_TYPE_LIST = [
|
||||
'SMSF',
|
||||
'CBC',
|
||||
'CHF',
|
||||
'HLR',
|
||||
'SGWC',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,6 +16,7 @@ export default {
|
||||
errorFields: 'Please fill in the required information in {num} correctly!',
|
||||
tablePaginationTotal: 'Total {total} items',
|
||||
noData: "No Data",
|
||||
zebra:'Tabular zebra pattern',
|
||||
ok: 'Ok',
|
||||
cancel: 'Cancel',
|
||||
close: 'Close',
|
||||
@@ -130,7 +131,7 @@ export default {
|
||||
},
|
||||
LockScreen: {
|
||||
inputPlacePwd:'Lock Screen Password',
|
||||
enter:'Enter',
|
||||
validSucc:'Validation Passed',
|
||||
validError:'Validation Failure',
|
||||
backLogin:'Logout to Relogin',
|
||||
backReload:'Restarting now, please wait...',
|
||||
@@ -221,11 +222,12 @@ export default {
|
||||
capability: 'Capability',
|
||||
serialNum: 'Serial Number',
|
||||
expiryDate: 'Expiry Date',
|
||||
neStatus: 'Status Abnormal',
|
||||
runStatus: 'Runing Status',
|
||||
mark:'Information',
|
||||
neStatus: 'NE status is abnormal',
|
||||
runStatus:'Running Status',
|
||||
mark:'Brief Information',
|
||||
object:'Object',
|
||||
versionNum:'Version',
|
||||
systemStatus:'Status',
|
||||
realNeStatus:'Status',
|
||||
reloadTime:'Refresh Time',
|
||||
Critical:'Critical',
|
||||
@@ -339,7 +341,7 @@ export default {
|
||||
profile: {
|
||||
phonenumber: "Phone",
|
||||
email: "Email",
|
||||
deptName: "Department",
|
||||
deptName: "Class",
|
||||
postGroup: "Possession of posts",
|
||||
roleGroup: "Ownership",
|
||||
loginIp: "Log in",
|
||||
@@ -349,6 +351,184 @@ export default {
|
||||
description: "No data yet, try refreshing",
|
||||
},
|
||||
},
|
||||
configManage: {
|
||||
neManage: {
|
||||
addNe:'Add Network Element',
|
||||
delSure:'Confirm deleting the data item with network element name {msg}',
|
||||
editNe:'Edit Network Element',
|
||||
exportSure:'Confirm exporting the configuration information with the network element name {msg}',
|
||||
exportTip:'Export successful, please go to backup management for download',
|
||||
getInfo:'Failed to get network element information',
|
||||
neType:'NE Type',
|
||||
neTypePlease: 'Select network element type',
|
||||
neId:'NE ID',
|
||||
neName:'NE Name',
|
||||
neTypeTip:'Fill in the type of network element created, such as:SMF',
|
||||
uid:'RM UID',
|
||||
uidTip:'Please enter a unique resource identifier',
|
||||
ip:'IP Address',
|
||||
mac:'NE MAC address',
|
||||
macTip:'Able to locate the physical address (MAC) of the network element',
|
||||
port:'Port',
|
||||
portTip:'Maximum range 0~65535',
|
||||
pvflag:'PV Flag',
|
||||
pnf:'Physical Network Element',
|
||||
vnf:'Virtual Network Element',
|
||||
province:'Region',
|
||||
vendorName:'Vendor Name',
|
||||
dn:'Network Identification',
|
||||
reload: 'Reload',
|
||||
restart: 'Restart',
|
||||
totalSure:'Confirm the network element with {operator} network element name {msg}',
|
||||
stop: 'Stop',
|
||||
start: 'Start',
|
||||
log: 'Logs',
|
||||
export: 'Export',
|
||||
import: 'Import',
|
||||
fileForm:'File Source',
|
||||
selectPlease:'Please select the source of the import file',
|
||||
server:'Server File',
|
||||
local:'Local File',
|
||||
fileSelect:'Please select the current import file',
|
||||
sync:'Synchronize to NE',
|
||||
open:'Open',
|
||||
close:'Close',
|
||||
addFail:'Add failed',
|
||||
operFail:'Operation Failed'
|
||||
},
|
||||
backupManage: {
|
||||
setBackupTask: 'Set automatic backup time',
|
||||
neTypePlease: 'Query network element type',
|
||||
neType: 'NE Type',
|
||||
neID: 'NE ID',
|
||||
fileName: 'File Name',
|
||||
createAt: 'Create at',
|
||||
remark:'Remark',
|
||||
edit:'Edit Backup File',
|
||||
totalSure:'Confirm that {oper} records item number {id}?',
|
||||
},
|
||||
softwareManage: {
|
||||
sendBtn: 'Distribute',
|
||||
runBtn: 'Activate',
|
||||
backBtn: 'Rollback',
|
||||
historyBtn: 'Distribution Record',
|
||||
neTypePlease: 'Select network element type',
|
||||
neType: 'NE Type',
|
||||
fileName: 'File Name',
|
||||
version: 'Version',
|
||||
versionPlease: 'Version number cannot be empty',
|
||||
updateTime: 'Uploaded Time',
|
||||
description: 'Description',
|
||||
deleteTip: 'Are you sure to delete the data item with software [{fileName}]?',
|
||||
downloadTip: 'Are you sure to download the data item with software [{fileName}]?',
|
||||
updateComment: 'Comment',
|
||||
updateCommentPlease: 'Please enter the software description',
|
||||
updateFile: 'Software File',
|
||||
updateFilePlease: 'Please upload the updated software file',
|
||||
verifyFile: 'Verify File',
|
||||
selectFile: 'SELECT FILE',
|
||||
sendTitle: 'Distribute software version',
|
||||
sendContent: 'Are you sure to send the file with the software package [{fileName}] to the corresponding network element?',
|
||||
runTitle: 'Activate software version',
|
||||
runContent: 'Are you sure to activate the software version of [{fileName}] that has been issued to the corresponding network element?',
|
||||
backTitle: 'Fallback software version',
|
||||
backContent: 'Confirm that the software version of [{fileName}] has been issued for the corresponding network element rollback?',
|
||||
neId: 'Corresponding network element',
|
||||
neIdPlease: 'Please select the corresponding network element',
|
||||
versions:'Version',
|
||||
upVersions:'Version before upgrade',
|
||||
backVersions:'Version before rollback',
|
||||
status:'Status',
|
||||
letUpTime:'Activation time',
|
||||
createTime:'Creation time',
|
||||
onlyAble:'Only upload file format {fileText} is supported',
|
||||
nullVersion:'There is no rollback version for the current network element.',
|
||||
},
|
||||
license: {
|
||||
neTypePlease: 'Select network element type',
|
||||
neType: 'NE Type',
|
||||
serialNum: 'Serial Num',
|
||||
createTime: 'Time',
|
||||
comment: 'Description',
|
||||
updateComment: 'License Description',
|
||||
updateCommentPlease: 'Please enter a license description',
|
||||
updateFile: 'License File',
|
||||
updateFilePlease: 'Please upload and update the License file',
|
||||
selectFile: 'SELECT FILE',
|
||||
neId: 'NE ID',
|
||||
neIdPlease: 'Please select the corresponding network element',
|
||||
},
|
||||
configParam:{
|
||||
dataNull:'No configuration item data yet',
|
||||
editSuss:'Modification successful',
|
||||
editFail:'Edit failed',
|
||||
Unable:'Illegal operation of attribute value',
|
||||
delSure:'Confirm to delete the data item with Index [{value}]?',
|
||||
addSuss:'Add successfully',
|
||||
addFail:'Add failed',
|
||||
delArraySure:'Confirm to delete the data item with {arrayChildTitle} Index as [{value}]?',
|
||||
parUnable:'The parameter value is not within the reasonable range',
|
||||
ipv4Tip:'Not a legal IPV4 address',
|
||||
ipv6Tip:'Not a legal IPV6 address',
|
||||
enumTip:'Not a reasonable enumeration value',
|
||||
boolTip:'Not a reasonable Boolean value',
|
||||
default:'The input value is of unknown type',
|
||||
reloadSuss:'Network element reloading completed',
|
||||
reloadFail:'Network element reloading failed',
|
||||
neNUll:'No network element list data yet',
|
||||
reload:'Reload',
|
||||
post:'Submit',
|
||||
editSure:'Are you sure you want to update this attribute value? ',
|
||||
arraryEdit:'Are you sure to submit the record whose updated Index is [{value}]? ',
|
||||
addSure:'Are you sure to submit the new record of Index: [{value}]? '
|
||||
},
|
||||
configParamForm: {
|
||||
treeTitle: "Navigation Configuration",
|
||||
treeSelectTip: "Select configuration item information in the left configuration navigation!",
|
||||
neType: 'NE Type',
|
||||
neTypePleace: "Please select the network element type",
|
||||
noConfigData: "No data on configuration items",
|
||||
updateValue: "[ {num} ] parameter value modified successfully.",
|
||||
updateValueErr: "Attribute value modification failure",
|
||||
updateItem: "Modify Index to {num}.",
|
||||
updateItemErr: "Record modification failure",
|
||||
delItemOk: "Deleting Index as {num} succeeded",
|
||||
addItemOk: "Add Index as {num} Record Succeeded",
|
||||
addItemErr: "Record addition failure",
|
||||
requireUn: "[ {display} ] input value is of unknown type",
|
||||
requireString: "[ {display} ] parameter value is invalid.",
|
||||
requireInt: "[ {display} ] parameter value not in reasonable range {filter}",
|
||||
requireIpv4: "[ {display} ] not a legitimate IPV4 address",
|
||||
requireIpv6: "[ {display} ] not a legitimate IPV6 address.",
|
||||
requireEnum: "[ {display} ] is not a reasonable enumeration value.",
|
||||
requireBool: "[ {display} ] is not a reasonable boolean value.",
|
||||
editOkTip: "Confirm updating the value of this [ {num} ] attribute?",
|
||||
updateItemTip: "Confirm updating the data item with Index [{num}]?",
|
||||
delItemTip: "Confirm deleting the data item with Index [{num}]?",
|
||||
arrayMore: "Expand",
|
||||
ptDiff: 'Comparison Example',
|
||||
ptDiffExample: 'Example Configuration',
|
||||
ptDiffSelf: 'Current Individuals',
|
||||
ptDiffLoad: 'Load More',
|
||||
ptDiffMerge: 'Comparative Differences',
|
||||
ptDiffRest: 'Restore this version',
|
||||
ptHistory: 'History',
|
||||
ptReset: 'Reset To Example',
|
||||
ptResetTip: 'Confirmed to reset to the sample configuration?',
|
||||
ptLoad: 'Load Current Configuration',
|
||||
ptLoadTip: 'Confirm that you want to load the current network element configuration?',
|
||||
ptExport: "Export Excel",
|
||||
ptExportTip: "Confirm that you want to export the network element configuration data to an Excel file?",
|
||||
ptExportAll: "导出所有学生配置",
|
||||
ptApplyShow: 'View Student',
|
||||
ptApply: 'request',
|
||||
ptApplyNE: 'Application To NE',
|
||||
ptApplyStu: 'Application To {ne}',
|
||||
ptApplyStuTip: 'Confirm that you want to initiate a Configure Application to {ne} request to the teacher?',
|
||||
ptApplyStuRack: 'Return Request',
|
||||
ptApplyStuNE: 'Application Request',
|
||||
},
|
||||
},
|
||||
dashboard: {
|
||||
overview:{
|
||||
title: "Core Network Dashboard",
|
||||
@@ -420,18 +600,14 @@ export default {
|
||||
resultFail: "Fail",
|
||||
delTip: "Confirm deletion of the data item numbered [{msg}]?",
|
||||
exportTip: "Do you confirm to export the current query conditions of the CDR data? (Maximum 10,000 items can be exported.)",
|
||||
chargingID: 'Charging ID',
|
||||
smfChargingID: 'Charging ID',
|
||||
smfSubscriptionIDData: 'Subscription ID Data',
|
||||
smfSubscriptionIDType: 'Subscription ID Type',
|
||||
smfDataVolumeUplink: 'Data Volume Uplink',
|
||||
smfDataVolumeDownlink: 'Data Volume Downlink',
|
||||
smfDataTotalVolume: 'Data Total Volume',
|
||||
durationTime: 'Duration',
|
||||
invocationTime: 'Invocation Time',
|
||||
sgwcServedIMSI: 'IMSI',
|
||||
sgwcServedMSISDN: 'MSISDN',
|
||||
sgwcVolumeGPRSUplink: 'GPRS Uplink',
|
||||
sgwcVolumeGPRSDownlink: 'GPRS Downlink',
|
||||
smfDuration: 'Duration',
|
||||
smfInvocationTime: 'Invocation Time',
|
||||
},
|
||||
ue: {
|
||||
eventType: "Event Type",
|
||||
@@ -503,7 +679,7 @@ export default {
|
||||
delTip: 'Confirm deletion of network element information data items?',
|
||||
oam: {
|
||||
title: 'OAM Configuration',
|
||||
restart: 'Restart NE',
|
||||
sync: 'Sync to NE',
|
||||
oamEnable: 'Service',
|
||||
oamPort: 'Port',
|
||||
snmpEnable: 'Service',
|
||||
@@ -606,7 +782,6 @@ export default {
|
||||
upgradeDone: 'Update complete, service being reloaded',
|
||||
upgradeFail: 'The update fails, please check whether the software file exists and whether the service terminal environment is available!',
|
||||
upgradeModal: 'Network Element Version Updates',
|
||||
noPath: 'Package File Not Found',
|
||||
},
|
||||
neLicense: {
|
||||
status: "License Status",
|
||||
@@ -630,9 +805,7 @@ export default {
|
||||
treeSelectTip: "Select configuration item information in the left configuration navigation!",
|
||||
neType: 'NE Type',
|
||||
neTypePleace: "Please select the network element type",
|
||||
neIdSyncPleace: "Please select the synchronized network element",
|
||||
noConfigData: "No data on configuration items",
|
||||
noConfigdDisabled: "The configuration item is not normal",
|
||||
updateValue: "[ {num} ] parameter value modified successfully.",
|
||||
updateValueErr: "Attribute value modification failure",
|
||||
updateItem: "Modify Index to {num}.",
|
||||
@@ -693,7 +866,7 @@ export default {
|
||||
installSourceUpload: 'New Upload',
|
||||
installSelect: 'Select Record',
|
||||
installUpload: 'Upload File',
|
||||
installText: 'Install',
|
||||
installText: 'Installed',
|
||||
licenseTitle: "Licenses",
|
||||
licenseDesc: "Network element service authorization certification",
|
||||
licenseResultTitle: "Whether to authorize activation immediately",
|
||||
@@ -706,30 +879,6 @@ export default {
|
||||
licenseTip2: '2. Clicking [Finish] will end the installation process.',
|
||||
},
|
||||
},
|
||||
neData: {
|
||||
baseStation: {
|
||||
list: "List",
|
||||
topology: "Topology",
|
||||
nbName: "RanNodeName",
|
||||
ueNum: "UE Number",
|
||||
topologyTitle: "Radio State Graph",
|
||||
name: "Name",
|
||||
namePlease: "text content length 0~64",
|
||||
position: "Position",
|
||||
positionPlease: "location description. Prohibition of spaces, length of text content 0-64",
|
||||
address: "IP Address",
|
||||
addressPlease: "text content length 0~64",
|
||||
state: "State",
|
||||
online: "Online",
|
||||
offline: "Offline",
|
||||
time: "Change Time",
|
||||
addRadio: "Add Radio Info",
|
||||
editRadio: "Edit Radio Info",
|
||||
history: "Status History",
|
||||
exportTip: "Confirm exporting xlsx table files based on search criteria?",
|
||||
importDataEmpty: "Imported data is empty",
|
||||
},
|
||||
},
|
||||
neUser: {
|
||||
auth: {
|
||||
authInfo:' Authentication Info',
|
||||
@@ -740,7 +889,6 @@ export default {
|
||||
checkExport : 'Check Export',
|
||||
checkExportConfirm: 'Confirm exporting the checked authenticated user data?',
|
||||
import: 'Import',
|
||||
importFail: 'Failure Record',
|
||||
loadDataConfirm: 'Are you sure you want to reload the data?',
|
||||
loadData: 'Load Data',
|
||||
loadDataTip: 'Successfully fetched loaded data: {num} items, the system is internally updating the data, it will take about {timer} seconds, please wait!!!!!.',
|
||||
@@ -769,7 +917,6 @@ export default {
|
||||
checkExport : 'Check Export',
|
||||
checkExportConfirm: 'Are you sure to export the data of the checked subscribers?',
|
||||
import: 'Import',
|
||||
importFail: 'Failure Record',
|
||||
loadDataConfirm: 'Are you sure you want to reload the data?',
|
||||
loadData: 'Load Data',
|
||||
loadDataTip: 'Successfully fetched loaded data: {num} items, the system is internally updating the data, it will take about {timer} seconds, please wait!!!!!.',
|
||||
@@ -964,7 +1111,6 @@ export default {
|
||||
expressionModal:'Expression Modal',
|
||||
expressionErrorTip:'Please check the expression, the wrong indicator is {kpiId}',
|
||||
expressionNoIdTip:'Please check the expression, no valid indicator is found',
|
||||
unitSelect:'To better display the image, the same unit needs to be selected. The current unit is:',
|
||||
},
|
||||
kpiKeyTarget:{
|
||||
"time":"Time",
|
||||
@@ -983,14 +1129,8 @@ export default {
|
||||
},
|
||||
kpiOverView:{
|
||||
"kpiName":"NE Metrics Name",
|
||||
"maxValue":"Max",
|
||||
"maxValueTip":"The max value of metric data within the user's filtered time range.",
|
||||
"minValue":"Min",
|
||||
"minValueTip":"The min value of metric data within the user's filtered time range.",
|
||||
"avgValue":"Avg",
|
||||
"avgValueTip":"The average value of metric data within the user's filtered time range.",
|
||||
"totalValue":"Sum",
|
||||
"totalValueTip":"The sum value of metric data within the user's filtered time range.",
|
||||
"maxValue":"Max Value",
|
||||
"minValue":"Min Value",
|
||||
"kpiChartTitle":"Overview of NE metrics",
|
||||
"changeLine":"Change to Line Charts",
|
||||
"changeBar":"Change to Bar Charts",
|
||||
@@ -999,6 +1139,25 @@ export default {
|
||||
},
|
||||
},
|
||||
traceManage: {
|
||||
analysis: {
|
||||
imsi: 'IMSI',
|
||||
imsiPlease: 'Please enter IMSI',
|
||||
msisdn: 'MSISDN',
|
||||
msisdnPlease: 'Please enter MSISDN',
|
||||
trackTaskId: 'Task ID',
|
||||
srcIp: 'Source IP Address',
|
||||
dstIp: 'Destination IP Address',
|
||||
signalType: 'Signaling Type',
|
||||
msgDirect: 'Message Direction',
|
||||
msgType: 'Message Type',
|
||||
rowTime: 'Record Time',
|
||||
signalData: 'Signaling Data',
|
||||
signalDetail: 'Signaling Details',
|
||||
noData: 'No information content',
|
||||
taskTitle: 'Task {num}',
|
||||
taskDownText: 'Download HTML',
|
||||
taskDownTip: 'Confirm downloading the signaling details HTML file?',
|
||||
},
|
||||
pcap: {
|
||||
capArgPlease: 'Please enter tcpdump -i any support parameter',
|
||||
cmd: 'Command',
|
||||
@@ -1049,35 +1208,30 @@ export default {
|
||||
imsiTip: 'Mobile communication IMSI number',
|
||||
srcIp: 'Source IP Address',
|
||||
srcIpPlease: 'Please enter the IP address',
|
||||
srcIpTip: 'sending IPv4 address',
|
||||
srcIpTip: 'Current sender IPv4 address',
|
||||
dstIp: 'Destination IP Address',
|
||||
dstIpPlease: 'Please enter the IP address',
|
||||
dstIpTip: 'receiving end IPv4 address',
|
||||
dstIpTip: 'IPv4 address of the receiving end of the other party',
|
||||
interfaces: 'Signaling Interface',
|
||||
interfacesPlease: 'Please enter the signaling interface',
|
||||
rangePicker: 'Task Time',
|
||||
signalPort: 'Signal Port',
|
||||
signalPortPlease: 'Please enter the signaling port',
|
||||
signalPortTip: 'Port of the side corresponding to the destination IP address or source IP address',
|
||||
rangePicker: 'Start/End Time',
|
||||
rangePickerPlease: 'Please select the start and end time of the task',
|
||||
remark: 'Remark',
|
||||
remarkPlease: 'Task description can be entered',
|
||||
addTask: 'Add Task',
|
||||
editTask: 'Modify Task',
|
||||
viewTask: 'View Task',
|
||||
errorTaskInfo: 'Failed to obtain task information',
|
||||
delTaskTip: 'Are you sure to delete the data item with record ID {id} ?',
|
||||
stopTask: 'Successful cessation of tasks {id}',
|
||||
stopTaskTip: 'Confirm stopping the task with record ID {id} ?',
|
||||
pcapView: "Track Data Analysis",
|
||||
traceFile: "Track File",
|
||||
pcapView: "Tracking Data Analysis",
|
||||
traceFile: "Tracking File",
|
||||
errMsg: "Error Message",
|
||||
imsiORmsisdn: "imsi or msisdn is null, cannot start task",
|
||||
dataView: "Track Data",
|
||||
protocolOrInterface: "Protocol/Interface",
|
||||
msgNe: 'Network Element',
|
||||
msgEvent: 'Event',
|
||||
msgType: 'Type',
|
||||
msgDirect: 'Direction',
|
||||
msgLen: 'Length',
|
||||
rowTime: 'Time',
|
||||
taskInfo: 'Task information',
|
||||
},
|
||||
},
|
||||
faultManage: {
|
||||
@@ -1187,13 +1341,12 @@ export default {
|
||||
size: "Size",
|
||||
modifiedTime: "Modified Time",
|
||||
fileName: "File Name",
|
||||
downTipZip: "Confirm downloading the directory [{fileName}] as a ZIP file?",
|
||||
downTip: "Confirm the download file name is [{fileName}] File?",
|
||||
downTipErr: "Failed to get file",
|
||||
dirCd: "Enter Dir",
|
||||
viewAs: 'View Action',
|
||||
reload: "Reload",
|
||||
follow: 'Enable Instant Update',
|
||||
follow: 'Monitoring Content',
|
||||
tailChar: 'End Characters',
|
||||
tailLines: 'End Lines',
|
||||
},
|
||||
@@ -1576,7 +1729,7 @@ export default {
|
||||
account: 'Account',
|
||||
userName: 'Nick Name',
|
||||
permission: 'Role',
|
||||
className: 'Department',
|
||||
className: 'Class',
|
||||
loginIp: 'Login Address',
|
||||
loginTime: 'Login Time',
|
||||
status: 'Status',
|
||||
@@ -1601,7 +1754,7 @@ export default {
|
||||
userTop:'User profile',
|
||||
sex:'User Gender',
|
||||
email:'E-mail',
|
||||
fromClass:'Department',
|
||||
fromClass:'Class',
|
||||
userWork:'User position',
|
||||
userWorkPlease: 'Please select user post',
|
||||
userTip:'User Description',
|
||||
@@ -1702,8 +1855,8 @@ export default {
|
||||
role:{
|
||||
allScopeOptions:'All data permissions',
|
||||
byMyselfScopeOptions:'Custom data permissions',
|
||||
onlyClassScopeOptions:'Data permissions of this department',
|
||||
classAllScopeOptions:'Data permissions for this department and the following',
|
||||
onlyClassScopeOptions:'Data permissions of this class',
|
||||
classAllScopeOptions:'Data permissions for this class and the following',
|
||||
myselfScopeOptions:'Only personal data permissions',
|
||||
roleId:'Role Number',
|
||||
roleName:'Role Name',
|
||||
@@ -1744,20 +1897,20 @@ export default {
|
||||
batchCancel:'Batch cancellation of authorization',
|
||||
},
|
||||
dept:{
|
||||
classInfo:' Department Information',
|
||||
className:'Department Name',
|
||||
classId:'Department Number',
|
||||
classSort:'Department Sorting',
|
||||
status:'Department Status',
|
||||
classInfo:' Class Information',
|
||||
className:'Class Name',
|
||||
classId:'Class Number',
|
||||
classSort:'Class Sorting',
|
||||
status:'Class Status',
|
||||
createTime:'Creation Time',
|
||||
highClass:'Higher Office',
|
||||
emailTip:'Please input the correct email address',
|
||||
phoneTip:'Please enter the correct phone number',
|
||||
node:'Root Node',
|
||||
delSure:'Are you sure to delete the data item with department number [{deptId}]?',
|
||||
delSure:'Are you sure to delete the data item with class number [{deptId}]?',
|
||||
open:'Exhibition',
|
||||
close:'Fold',
|
||||
addClass:'Add new sub-department',
|
||||
addClass:'Add new sub-class',
|
||||
admin:'Principal',
|
||||
phone:'Contact Number',
|
||||
email:'Mail',
|
||||
|
||||
@@ -16,6 +16,7 @@ export default {
|
||||
errorFields: '请正确填写 {num} 处必填信息!',
|
||||
tablePaginationTotal: '总共 {total} 条',
|
||||
noData: "暂无数据",
|
||||
zebra:'表格斑马纹',
|
||||
ok: '确定',
|
||||
cancel: '取消',
|
||||
close: '关闭',
|
||||
@@ -130,7 +131,7 @@ export default {
|
||||
},
|
||||
LockScreen: {
|
||||
inputPlacePwd:'请输入锁屏密码',
|
||||
enter:'进入',
|
||||
validSucc:'校验通过',
|
||||
validError:'校验失败',
|
||||
backLogin:'退出并重新登录',
|
||||
backReload:'正在重启,请稍等...',
|
||||
@@ -221,12 +222,13 @@ export default {
|
||||
capability: '用户容量',
|
||||
serialNum: '序列号',
|
||||
expiryDate: '许可证到期日期',
|
||||
neStatus:'状态异常',
|
||||
neStatus:'网元状态异常',
|
||||
runStatus:'运行状态',
|
||||
mark:'信息',
|
||||
mark:'简略信息',
|
||||
object:'对象',
|
||||
versionNum:'版本号',
|
||||
realNeStatus:'状态',
|
||||
systemStatus:'系统状态',
|
||||
realNeStatus:'网元状态',
|
||||
reloadTime:'刷新时间',
|
||||
Critical:'严重告警',
|
||||
Major:'主要告警',
|
||||
@@ -339,8 +341,8 @@ export default {
|
||||
profile: {
|
||||
phonenumber: "手机号码",
|
||||
email: "用户邮箱",
|
||||
deptName: "所属部门",
|
||||
postGroup: "拥有岗位",
|
||||
deptName: "所属班级",
|
||||
postGroup: "拥有职位",
|
||||
roleGroup: "拥有角色",
|
||||
loginIp: "登录地址",
|
||||
loginDate: "登录时间",
|
||||
@@ -349,6 +351,184 @@ export default {
|
||||
description: "暂无数据,尝试刷新看看",
|
||||
},
|
||||
},
|
||||
configManage: {
|
||||
neManage: {
|
||||
addNe:'添加网元',
|
||||
delSure:'确认删除网元名称为{msg}的数据项 ',
|
||||
editNe:'修改网元',
|
||||
exportSure:'确认导出网元名称为 {msg} 的配置信息',
|
||||
exportTip:'导出成功,请到备份管理进行下载',
|
||||
getInfo:'获取网元信息失败',
|
||||
neType:'网元类型',
|
||||
neTypePlease: '请输入网元类型',
|
||||
neId:'网元内部标识',
|
||||
neName:'网元名称',
|
||||
neTypeTip:'填写创建的网元类型,如:SMF',
|
||||
uid:'资源唯一标识',
|
||||
uidTip:'请输入资源唯一标识',
|
||||
ip:'IP地址',
|
||||
mac:'网元物理地址',
|
||||
macTip:'能够定位网元的物理地址(MAC)',
|
||||
port:'端口',
|
||||
portTip:'最大范围0~65535',
|
||||
pvflag:'网元虚拟化标识',
|
||||
pnf:'物理网元',
|
||||
vnf:'虚拟网元',
|
||||
province:'网元服务省份',
|
||||
vendorName:'厂商名称',
|
||||
dn:'网络标识',
|
||||
reload: '重载',
|
||||
restart: '重启',
|
||||
totalSure:'确认{oper}网元名称为 {msg} 的网元',
|
||||
stop: '停止',
|
||||
start: '启动',
|
||||
log: '日志',
|
||||
export: '导出',
|
||||
import: '导入',
|
||||
fileForm:'文件来源',
|
||||
selectPlease:'请选择导入文件来源',
|
||||
server:'服务器文件',
|
||||
local:'本地文件',
|
||||
fileSelect:'请选择当前导入文件',
|
||||
sync:'同步到网元',
|
||||
open:'开',
|
||||
close:'关',
|
||||
addFail:'新增失败',
|
||||
operFail:'操作失败'
|
||||
},
|
||||
backupManage: {
|
||||
setBackupTask: '设置自动备份时间',
|
||||
neTypePlease: '查询网元类型',
|
||||
neType: '网元类型',
|
||||
neID: '网元内部标识',
|
||||
fileName: '文件名',
|
||||
createAt: '创建时间',
|
||||
remark:'备份说明',
|
||||
edit:'编辑备份文件',
|
||||
totalSure:'确认{oper}记录编号为 {id} 的数据项?',
|
||||
},
|
||||
softwareManage: {
|
||||
sendBtn: '下发',
|
||||
runBtn: '激活',
|
||||
backBtn: '回退',
|
||||
historyBtn: '下发记录',
|
||||
neTypePlease: '选择网元类型',
|
||||
neType: '网元类型',
|
||||
fileName: '文件名',
|
||||
version: '版本号',
|
||||
versionPlease: '版本号不能为空',
|
||||
updateTime: '上传时间',
|
||||
description: '功能描述',
|
||||
deleteTip: '确认删除 【{fileName}】 的软件数据项?',
|
||||
downloadTip: '确认下载 【{fileName}】 的软件数据项?',
|
||||
updateComment: '软件说明',
|
||||
updateCommentPlease: '请输入软件说明',
|
||||
updateFile: '软件文件',
|
||||
updateFilePlease: '请上传更新软件文件',
|
||||
verifyFile: '校验文件',
|
||||
selectFile: '选择文件',
|
||||
sendTitle: '下发软件版本',
|
||||
sendContent: '确认下发软件包为【{fileName}】的文件到对应网元?',
|
||||
runTitle: '激活软件版本',
|
||||
runContent: '确认在对应网元激活已下发【{fileName}】的软件版本?',
|
||||
backTitle: '回退软件版本',
|
||||
backContent: '确认在对应网元回退已下发【{fileName}】的软件版本?',
|
||||
neId: '对应网元',
|
||||
neIdPlease: '请选择对应网元',
|
||||
versions:'版本',
|
||||
upVersions:'升级前版本',
|
||||
backVersions:'回退前版本',
|
||||
status:'状态',
|
||||
letUpTime:'激活时间',
|
||||
createTime:'创建时间',
|
||||
onlyAble:'只支持上传文件格式 {fileText}',
|
||||
nullVersion:'当前网元无可回退版本',
|
||||
},
|
||||
license: {
|
||||
neTypePlease: '选择网元类型',
|
||||
neType: '网元类型',
|
||||
serialNum: '序列号',
|
||||
createTime: '时间',
|
||||
comment: '说明',
|
||||
updateComment: 'License说明',
|
||||
updateCommentPlease: '请输入License说明',
|
||||
updateFile: 'License文件',
|
||||
updateFilePlease: '请上传更新License文件',
|
||||
selectFile: '选择文件',
|
||||
neId: '网元内部标识',
|
||||
neIdPlease: '请选择对应网元',
|
||||
},
|
||||
configParam:{
|
||||
dataNull:'暂无配置项数据',
|
||||
editSuss:'修改成功',
|
||||
editFail:'修改失败',
|
||||
unable:'非法操作属性值',
|
||||
delSure:'确认删除Index为 【{value}】 的数据项?',
|
||||
addSuss:'新增成功',
|
||||
addFail:'新增失败',
|
||||
delArraySure:'确认删除{arrayChildTitle} Index 为 【{value}】 的数据项?',
|
||||
parUnable:'参数值不在合理范围',
|
||||
ipv4Tip:'不是合法的IPV4地址',
|
||||
ipv6Tip:'不是合法的IPV6地址',
|
||||
enumTip:'不是合理的枚举值',
|
||||
boolTip:'不是合理的布尔类型的值',
|
||||
default:'输入值是未知类型',
|
||||
reloadSuss:'网元重新加载完成',
|
||||
reloadFail:'网元重新加载失败',
|
||||
neNUll:'暂无网元列表数据',
|
||||
reload:'重载',
|
||||
post:'提交',
|
||||
editSure:'确认更新该属性值吗?',
|
||||
arraryEdit:'确认提交更新 Index 为 【{value}】 的记录吗?',
|
||||
addSure:'确认提交新增 Index :【{value}】 的记录吗?',
|
||||
},
|
||||
configParamForm: {
|
||||
treeTitle: "配置导航",
|
||||
treeSelectTip: "左侧配置导航中选择配置项信息!",
|
||||
neType: "网元类型",
|
||||
neTypePleace: "请选择网元类型",
|
||||
noConfigData: "暂无配置项数据",
|
||||
updateValue: "【 {num} 】 属性值修改成功",
|
||||
updateValueErr: "属性值修改失败",
|
||||
updateItem: "修改 Index 为 {num} 记录成功",
|
||||
updateItemErr: "记录修改失败",
|
||||
delItemOk: "删除 Index 为 {num} 记录成功",
|
||||
addItemOk: "新增 Index 为 {num} 记录成功",
|
||||
addItemErr: "记录新增失败",
|
||||
requireUn: "【 {display} 】输入值是未知类型",
|
||||
requireString: "【 {display} 】参数值不合理",
|
||||
requireInt: "【 {display} 】参数值不在合理范围 {filter}",
|
||||
requireIpv4: "【 {display} 】不是合法的IPV4地址",
|
||||
requireIpv6: "【 {display} 】不是合法的IPV6地址",
|
||||
requireEnum: "【 {display} 】不是合理的枚举值",
|
||||
requireBool: "【 {display} 】不是合理的布尔类型的值",
|
||||
editOkTip: "确认更新该【 {num} 】属性值吗?",
|
||||
updateItemTip: "确认更新Index为 【{num}】 的数据项?",
|
||||
delItemTip: "确认删除Index为 【{num}】 的数据项?",
|
||||
arrayMore: "展开",
|
||||
ptDiff: '对比示例',
|
||||
ptDiffExample: '示例配置',
|
||||
ptDiffSelf: '当前个人',
|
||||
ptDiffLoad: '加载更多',
|
||||
ptDiffMerge: '差异对比',
|
||||
ptDiffRest: '还原此版本',
|
||||
ptHistory: '历史记录',
|
||||
ptReset: '重置为示例',
|
||||
ptResetTip: '确认要重置为示例配置吗?',
|
||||
ptLoad: '载入当前网元配置',
|
||||
ptLoadTip: '确认要载入当前网元配置吗?',
|
||||
ptExport: "导出Excel",
|
||||
ptExportTip: "确认要导出网元配置数据到Excel文件中吗?",
|
||||
ptExportAll: "导出所有学生配置",
|
||||
ptApplyShow: '查看学生',
|
||||
ptApply: '申请',
|
||||
ptApplyNE: '应用配置到网元',
|
||||
ptApplyStu: '申请配置应用到 {ne}',
|
||||
ptApplyStuTip: '确认要向教师发起配置应用到 {ne} 的申请吗?',
|
||||
ptApplyStuRack: '退回该学生配置',
|
||||
ptApplyStuNE: '应用该学生配置',
|
||||
},
|
||||
},
|
||||
dashboard: {
|
||||
overview:{
|
||||
title: "核心网系统看板",
|
||||
@@ -420,18 +600,14 @@ export default {
|
||||
resultFail: "失败",
|
||||
delTip: "确认删除编号为【{msg}】的数据项?",
|
||||
exportTip: "确认导出当前查询条件的话单数据吗?(导出最大支持一万条)",
|
||||
chargingID: '计费ID',
|
||||
smfChargingID: '计费ID',
|
||||
smfSubscriptionIDData: '订阅 ID 数据',
|
||||
smfSubscriptionIDType: '订阅 ID 类型',
|
||||
smfDataVolumeUplink: '数据量上行链路',
|
||||
smfDataVolumeDownlink: '数据量下行链路',
|
||||
smfDataTotalVolume: '数据总量',
|
||||
durationTime: '持续时间',
|
||||
invocationTime: '调用时间',
|
||||
sgwcServedIMSI: 'IMSI',
|
||||
sgwcServedMSISDN: 'MSISDN',
|
||||
sgwcVolumeGPRSUplink: 'GPRS 上行链路',
|
||||
sgwcVolumeGPRSDownlink: 'GPRS 下行链路',
|
||||
smfDuration: '持续时间',
|
||||
smfInvocationTime: '调用时间',
|
||||
},
|
||||
ue: {
|
||||
eventType: "事件类型",
|
||||
@@ -503,7 +679,7 @@ export default {
|
||||
delTip: '确认删除网元信息数据项吗?',
|
||||
oam: {
|
||||
title: 'OAM配置',
|
||||
restart: '下发后重启网元',
|
||||
sync: '同步到网元',
|
||||
oamEnable: '服务',
|
||||
oamPort: '端口',
|
||||
snmpEnable: '服务',
|
||||
@@ -606,7 +782,6 @@ export default {
|
||||
upgradeDone: '更新完成,服务正在重载',
|
||||
upgradeFail: '更新失败,请检查软件文件是否存在且服务终端环境是否可用!',
|
||||
upgradeModal: '网元版本更新',
|
||||
noPath: '软件包文件未发现',
|
||||
},
|
||||
neLicense: {
|
||||
status: "许可证状态",
|
||||
@@ -630,9 +805,7 @@ export default {
|
||||
treeSelectTip: "左侧配置导航中选择配置项信息!",
|
||||
neType: "网元类型",
|
||||
neTypePleace: "请选择网元类型",
|
||||
neIdSyncPleace: "请选择同步网元",
|
||||
noConfigData: "暂无配置项数据",
|
||||
noConfigdDisabled: "配置项网元未正常服务",
|
||||
updateValue: "【 {num} 】 属性值修改成功",
|
||||
updateValueErr: "属性值修改失败",
|
||||
updateItem: "修改 Index 为 {num} 记录成功",
|
||||
@@ -706,30 +879,6 @@ export default {
|
||||
licenseTip2: '2. 点击【结束】将结束安装过程',
|
||||
},
|
||||
},
|
||||
neData: {
|
||||
baseStation: {
|
||||
list: "列表",
|
||||
topology: "拓扑图",
|
||||
nbName: "设备名称",
|
||||
ueNum: "在线用户数",
|
||||
topologyTitle: "基站状态关系图",
|
||||
name: "基站名称",
|
||||
namePlease: "文本内容长度0~64",
|
||||
position: "基站位置",
|
||||
positionPlease: "位置描述。禁止空格,文本内容长度0-64",
|
||||
address: "IP地址",
|
||||
addressPlease: "文本内容长度0~64",
|
||||
state: "基站状态",
|
||||
online: "在线",
|
||||
offline: "离线",
|
||||
time: "变更时间",
|
||||
addRadio: "添加基站信息",
|
||||
editRadio: "更新基站信息",
|
||||
history: "历史记录",
|
||||
exportTip: "确认根据搜索条件导出xlsx表格文件吗?",
|
||||
importDataEmpty: "导入数据为空",
|
||||
},
|
||||
},
|
||||
neUser: {
|
||||
auth: {
|
||||
authInfo:'鉴权信息',
|
||||
@@ -740,7 +889,6 @@ export default {
|
||||
checkExport : '勾选导出',
|
||||
checkExportConfirm: '确认导出已勾选的鉴权用户数据吗?',
|
||||
import: '导入',
|
||||
importFail: '失败记录',
|
||||
loadDataConfirm: '确认要重新加载数据吗?',
|
||||
loadData: '加载数据',
|
||||
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!',
|
||||
@@ -769,7 +917,6 @@ export default {
|
||||
checkExport : '勾选导出',
|
||||
checkExportConfirm: '确认导出已勾选的签约用户数据吗?',
|
||||
import: '导入',
|
||||
importFail: '失败记录',
|
||||
loadDataConfirm: '确认要重新加载数据吗?',
|
||||
loadData: '加载数据',
|
||||
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!',
|
||||
@@ -964,7 +1111,6 @@ export default {
|
||||
expressionModal:'表达式模块',
|
||||
expressionErrorTip:'请检查表达式,错误的指标为{kpiId}',
|
||||
expressionNoIdTip:'请检查表达式,没有找到任何有效的指标',
|
||||
unitSelect:'为更好展示图需选择相同单位,当前单位为:',
|
||||
},
|
||||
kpiKeyTarget:{
|
||||
"time":"时间",
|
||||
@@ -984,13 +1130,7 @@ export default {
|
||||
kpiOverView:{
|
||||
"kpiName":"指标名",
|
||||
"maxValue":"最大值",
|
||||
"maxValueTip":"用户筛选时间范围内度量数据的最大值。",
|
||||
"minValue":"最小值",
|
||||
"minValueTip":"用户筛选时间范围内度量数据的最小值。",
|
||||
"avgValue":"平均值",
|
||||
"avgValueTip":"用户筛选时间范围内度量数据的平均值。",
|
||||
"totalValue":"总值",
|
||||
"totalValueTip":"用户筛选时间范围内度量数据的总值。",
|
||||
"kpiChartTitle":"网元指标概览",
|
||||
"changeLine":"切换为折线图",
|
||||
"changeBar":"切换为柱状图",
|
||||
@@ -999,6 +1139,25 @@ export default {
|
||||
},
|
||||
},
|
||||
traceManage: {
|
||||
analysis: {
|
||||
imsi: 'IMSI',
|
||||
imsiPlease: '请输入IMSI',
|
||||
msisdn: 'MSISDN',
|
||||
msisdnPlease: '请输入MSISDN',
|
||||
trackTaskId: '跟踪任务标记',
|
||||
srcIp: '源IP地址',
|
||||
dstIp: '目标IP地址',
|
||||
signalType: '信令类型',
|
||||
msgDirect: '消息元',
|
||||
msgType: '消息类型',
|
||||
rowTime: '记录时间',
|
||||
signalData: '信令数据',
|
||||
signalDetail: '信令详情',
|
||||
noData: '无信息内容',
|
||||
taskTitle: '任务 {num}',
|
||||
taskDownText: '下载HTML',
|
||||
taskDownTip: '确认下载信令详情HTML文件?',
|
||||
},
|
||||
pcap: {
|
||||
capArgPlease: '请输入tcpdump -i any支持参数',
|
||||
cmd: '命令',
|
||||
@@ -1049,17 +1208,21 @@ export default {
|
||||
imsiTip: '移动通信IMSI编号',
|
||||
srcIp: '源IP地址',
|
||||
srcIpPlease: '请输入源IP地址',
|
||||
srcIpTip: '发送端IPv4地址',
|
||||
srcIpTip: '当前发送端IPv4地址',
|
||||
dstIp: '目标IP地址',
|
||||
dstIpPlease: '请输入目标IP地址',
|
||||
dstIpTip: '接收端IPv4地址',
|
||||
dstIpTip: '对方接收端IPv4地址',
|
||||
interfaces: '信令接口',
|
||||
interfacesPlease: '请输入信令接口',
|
||||
rangePicker: '任务时间',
|
||||
signalPort: '信令端口',
|
||||
signalPortPlease: '请输入信令端口',
|
||||
signalPortTip: '目标IP地址或源IP地址对应一方的端口',
|
||||
rangePicker: '开始结束时间',
|
||||
rangePickerPlease: '请选择任务时间开始结束时间',
|
||||
remark: '说明',
|
||||
remarkPlease: '可输入任务说明',
|
||||
addTask: '添加任务',
|
||||
editTask: '修改任务',
|
||||
viewTask: '查看任务',
|
||||
errorTaskInfo: '获取任务信息失败',
|
||||
delTaskTip: '确认删除记录编号为 {id} 的数据项?',
|
||||
@@ -1069,15 +1232,6 @@ export default {
|
||||
traceFile: "跟踪文件",
|
||||
errMsg: "错误信息",
|
||||
imsiORmsisdn: "imsi 或 msisdn 是空值,不能开始任务",
|
||||
dataView: "跟踪数据",
|
||||
protocolOrInterface: "协议/接口",
|
||||
msgNe: '消息网元',
|
||||
msgEvent: '消息事件',
|
||||
msgType: '消息类型',
|
||||
msgDirect: '消息方向',
|
||||
msgLen: '消息长度',
|
||||
rowTime: '消息时间',
|
||||
taskInfo: '任务信息',
|
||||
},
|
||||
},
|
||||
faultManage: {
|
||||
@@ -1163,7 +1317,7 @@ export default {
|
||||
type:'网元类型',
|
||||
neId:'网元唯一标识',
|
||||
MML:'MML',
|
||||
logTime:'log Time'
|
||||
logTime:'记录时间',
|
||||
},
|
||||
forwarding:{
|
||||
type:'网元类型',
|
||||
@@ -1187,7 +1341,6 @@ export default {
|
||||
size: "文件大小",
|
||||
modifiedTime: "修改时间",
|
||||
fileName: "文件名称",
|
||||
downTipZip: "确认将目录 【{fileName}】 下载为ZIP文件?",
|
||||
downTip: "确认下载文件名为 【{fileName}】 文件?",
|
||||
downTipErr: "文件获取失败",
|
||||
dirCd: "进入目录",
|
||||
@@ -1576,7 +1729,7 @@ export default {
|
||||
account: '登录账号',
|
||||
userName: '用户昵称',
|
||||
permission: '用户权限',
|
||||
className: '部门名称',
|
||||
className: '班级名称',
|
||||
loginIp: '登录地址',
|
||||
loginTime: '登录时间',
|
||||
status: '用户状态',
|
||||
@@ -1601,9 +1754,9 @@ export default {
|
||||
userTop:'用户头像',
|
||||
sex:'用户性别',
|
||||
email:'电子邮箱',
|
||||
fromClass:'所属部门',
|
||||
userWork:'用户岗位',
|
||||
userWorkPlease: '请选择用户岗位',
|
||||
fromClass:'所属班级',
|
||||
userWork:'用户职位',
|
||||
userWorkPlease: '请选择用户职位',
|
||||
userTip:'用户说明',
|
||||
loginPwd:'登录密码',
|
||||
updateSure:'是否更新已经存在的数据',
|
||||
@@ -1702,8 +1855,8 @@ export default {
|
||||
role:{
|
||||
allScopeOptions:'全部数据权限',
|
||||
byMyselfScopeOptions:'自定数据权限',
|
||||
onlyClassScopeOptions:'本部门数据权限',
|
||||
classAllScopeOptions:'本部门及以下数据权限',
|
||||
onlyClassScopeOptions:'本班级数据权限',
|
||||
classAllScopeOptions:'本班级及以下数据权限',
|
||||
myselfScopeOptions:'仅本人数据权限',
|
||||
roleId:'角色编号',
|
||||
roleName:'角色名称',
|
||||
@@ -1744,36 +1897,36 @@ export default {
|
||||
batchCancel:'批量取消授权',
|
||||
},
|
||||
dept:{
|
||||
classInfo:'部门信息',
|
||||
className:'部门名称',
|
||||
classId:'部门编号',
|
||||
classSort:'部门排序',
|
||||
status:'部门状态',
|
||||
classInfo:'班级信息',
|
||||
className:'班级名称',
|
||||
classId:'班级编号',
|
||||
classSort:'班级排序',
|
||||
status:'班级状态',
|
||||
createTime:'创建时间',
|
||||
highClass:'上级部门',
|
||||
highClass:'上级班级',
|
||||
emailTip:'请输入正确的邮箱地址',
|
||||
phoneTip:'请输入正确的手机号码',
|
||||
node:'根节点',
|
||||
delSure:'确认删除部门编号为 【{deptId}】 的数据项?',
|
||||
delSure:'确认删除班级编号为 【{deptId}】 的数据项?',
|
||||
open:'展',
|
||||
close:'折',
|
||||
addClass:'新增子部门',
|
||||
addClass:'新增子班级',
|
||||
admin:'负责人',
|
||||
phone:'联系电话',
|
||||
email:'邮箱',
|
||||
},
|
||||
post:{
|
||||
positionInfo:'岗位信息',
|
||||
positionId:'岗位编号',
|
||||
positionCode:'岗位编码',
|
||||
positionName:'岗位名称',
|
||||
positionSort:'岗位排序',
|
||||
positionStatus:'岗位状态',
|
||||
positionMark:'岗位说明',
|
||||
positionInfo:'职位信息',
|
||||
positionId:'职位编号',
|
||||
positionCode:'职位编码',
|
||||
positionName:'职位名称',
|
||||
positionSort:'职位排序',
|
||||
positionStatus:'职位状态',
|
||||
positionMark:'职位说明',
|
||||
createTime:'创建时间',
|
||||
codeTip:'请正确输入岗位编码',
|
||||
nameTip:'请正确输入岗位名称',
|
||||
delSure:'确认删除岗位编号为 【{postId}】 的数据项?',
|
||||
codeTip:'请正确输入职位编码',
|
||||
nameTip:'请正确输入职位名称',
|
||||
delSure:'确认删除职位编号为 【{postId}】 的数据项?',
|
||||
},
|
||||
log:{
|
||||
operate:{
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
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 权限字符数组
|
||||
|
||||
@@ -198,7 +198,7 @@ function beforeRequest(options: OptionsType): OptionsType | Promise<any> {
|
||||
if (options.method === 'get') return options;
|
||||
|
||||
// 非get参数提交
|
||||
let body = options.data;
|
||||
let body = options.data
|
||||
if (body instanceof FormData) {
|
||||
options.body = body;
|
||||
} else if (body) {
|
||||
|
||||
@@ -119,15 +119,17 @@ export class WS {
|
||||
};
|
||||
// 用于指定当从服务器接受到信息时的回调函数。
|
||||
ws.onmessage = ev => {
|
||||
if (ev.type !== 'message') return;
|
||||
// 解析文本消息
|
||||
try {
|
||||
const jsonData = JSON.parse(ev.data);
|
||||
if (typeof options.onmessage === 'function') {
|
||||
options.onmessage(jsonData);
|
||||
if (ev.type === 'message') {
|
||||
const data = ev.data;
|
||||
try {
|
||||
const jsonData = JSON.parse(data);
|
||||
if (typeof options.onmessage === 'function') {
|
||||
options.onmessage(jsonData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('websocket message formatting error', error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('websocket message formatting error', error);
|
||||
}
|
||||
};
|
||||
// 用于指定连接关闭后的回调函数。
|
||||
@@ -219,7 +221,7 @@ export class WS {
|
||||
this.heartInterval = window.setInterval(() => {
|
||||
this.send({
|
||||
requestId: `${Date.now()}`,
|
||||
type: 'PING',
|
||||
type: 'ping',
|
||||
});
|
||||
}, heartTimer);
|
||||
}
|
||||
|
||||
@@ -182,12 +182,7 @@ router.beforeEach(async (to, from, next) => {
|
||||
next({ name: 'Index' });
|
||||
}
|
||||
|
||||
let token = getToken();
|
||||
|
||||
// 免用户登录认证
|
||||
if (!appStore.loginAuth) {
|
||||
token = '== Not Login Auth ==';
|
||||
}
|
||||
const token = getToken();
|
||||
|
||||
// 没有token
|
||||
if (!token) {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { getSysConf } from '@/api';
|
||||
import { CACHE_LOCAL_I18N, CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
|
||||
import { CACHE_LOCAL_I18N } from '@/constants/cache-keys-constants';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { removeToken } from '@/plugins/auth-token';
|
||||
import { parseUrlPath } from '@/plugins/file-static-url';
|
||||
import { localGet, localSet } from '@/utils/cache-local-utils';
|
||||
import { sessionSet } from '@/utils/cache-session-utils';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
/**应用参数类型 */
|
||||
@@ -21,10 +20,6 @@ type AppStore = {
|
||||
buildTime: string;
|
||||
/**系统引导使用 */
|
||||
bootloader: boolean;
|
||||
// 用户登录认证
|
||||
loginAuth: boolean;
|
||||
// 用户接口加密
|
||||
cryptoApi: boolean;
|
||||
// 序列号
|
||||
serialNum: string;
|
||||
/**应用版权声明 */
|
||||
@@ -57,8 +52,6 @@ const useAppStore = defineStore('app', {
|
||||
version: `-`,
|
||||
buildTime: `-`,
|
||||
bootloader: false,
|
||||
loginAuth: true,
|
||||
cryptoApi: true,
|
||||
serialNum: `-`,
|
||||
copyright: `Copyright ©2023 For ${import.meta.env.VITE_APP_NAME}`,
|
||||
logoType: 'icon',
|
||||
@@ -92,9 +85,6 @@ const useAppStore = defineStore('app', {
|
||||
if (this.bootloader) {
|
||||
removeToken();
|
||||
}
|
||||
this.loginAuth = res.data.loginAuth !== 'false';
|
||||
this.cryptoApi = res.data.cryptoApi !== 'false';
|
||||
sessionSet(CACHE_SESSION_CRYPTO_API, res.data.cryptoApi);
|
||||
this.serialNum = res.data.serialNum;
|
||||
this.appName = res.data.title;
|
||||
this.copyright = res.data.copyright;
|
||||
|
||||
@@ -20,7 +20,7 @@ type MaskStateType = {
|
||||
const useMaskStore = defineStore('mask', {
|
||||
state: (): MaskStateType => ({
|
||||
type: (localGet(CACHE_LOCAL_MASK) || 'none') as MaskStateType['type'],
|
||||
lockPasswd: atob(localGet(CACHE_LOCAL_LOCK_PASSWD) || ''),
|
||||
lockPasswd: localGet(CACHE_LOCAL_LOCK_PASSWD) || '',
|
||||
lockTimeout: 0,
|
||||
}),
|
||||
getters: {},
|
||||
@@ -59,7 +59,7 @@ const useMaskStore = defineStore('mask', {
|
||||
}, 5_000);
|
||||
}
|
||||
if (type === 'lock') {
|
||||
localSet(CACHE_LOCAL_LOCK_PASSWD, btoa(this.lockPasswd));
|
||||
localSet(CACHE_LOCAL_LOCK_PASSWD, this.lockPasswd);
|
||||
} else {
|
||||
localRemove(CACHE_LOCAL_LOCK_PASSWD);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { defineStore } from 'pinia';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { parseDataToOptions } from '@/utils/parse-tree-utils';
|
||||
import { getNeTraceInterfaceAll } from '@/api/trace/task';
|
||||
import { getNePerformanceList } from '@/api/perfManage/taskManage';
|
||||
|
||||
/**网元信息类型 */
|
||||
@@ -12,6 +13,8 @@ type NeInfo = {
|
||||
neCascaderOptions: Record<string, any>[];
|
||||
/**选择器单级父类型 */
|
||||
neSelectOtions: Record<string, any>[];
|
||||
/**跟踪接口列表 */
|
||||
traceInterfaceList: Record<string, any>[];
|
||||
/**性能测量数据集 */
|
||||
perMeasurementList: Record<string, any>[];
|
||||
};
|
||||
@@ -21,6 +24,7 @@ const useNeInfoStore = defineStore('neinfo', {
|
||||
neList: [],
|
||||
neCascaderOptions: [],
|
||||
neSelectOtions: [],
|
||||
traceInterfaceList: [],
|
||||
perMeasurementList: [],
|
||||
}),
|
||||
getters: {
|
||||
@@ -57,7 +61,7 @@ const useNeInfoStore = defineStore('neinfo', {
|
||||
const res = await listAllNeInfo({
|
||||
bandStatus: false,
|
||||
});
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
// 原始列表
|
||||
this.neList = JSON.parse(JSON.stringify(res.data));
|
||||
|
||||
@@ -75,6 +79,24 @@ const useNeInfoStore = defineStore('neinfo', {
|
||||
}
|
||||
return res;
|
||||
},
|
||||
// 刷新跟踪接口列表
|
||||
async fnRefreshNeTraceInterface() {
|
||||
this.traceInterfaceList = [];
|
||||
const res = await this.fnNeTraceInterface();
|
||||
return res;
|
||||
},
|
||||
// 获取跟踪接口列表
|
||||
async fnNeTraceInterface() {
|
||||
// 有数据不请求
|
||||
if (this.traceInterfaceList.length > 0) {
|
||||
return { code: 1, data: this.traceInterfaceList, msg: 'success' };
|
||||
}
|
||||
const res = await getNeTraceInterfaceAll();
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
this.traceInterfaceList = res.data;
|
||||
}
|
||||
return res;
|
||||
},
|
||||
// 获取性能测量数据集列表
|
||||
async fnNeTaskPerformance() {
|
||||
// 有数据不请求
|
||||
@@ -82,7 +104,7 @@ const useNeInfoStore = defineStore('neinfo', {
|
||||
return { code: 1, data: this.perMeasurementList, msg: 'success' };
|
||||
}
|
||||
const res = await getNePerformanceList();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
this.perMeasurementList = res.data;
|
||||
}
|
||||
return res;
|
||||
|
||||
@@ -133,12 +133,18 @@ const useUserStore = defineStore('user', {
|
||||
}
|
||||
|
||||
// 水印文字信息=用户昵称 手机号
|
||||
// let waterMarkContent = this.userName;
|
||||
// if (this.phonenumber) {
|
||||
// waterMarkContent = `${this.userName} ${this.phonenumber}`;
|
||||
// }
|
||||
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) {
|
||||
|
||||
2
src/typings/vite-env.d.ts
vendored
2
src/typings/vite-env.d.ts
vendored
@@ -7,10 +7,8 @@ declare module '*.vue' {
|
||||
export default component;
|
||||
}
|
||||
|
||||
// "vue3-smooth-dnd": "^0.0.6"
|
||||
declare module 'vue3-smooth-dnd';
|
||||
|
||||
// "intl-tel-input": "^25.2.0"
|
||||
declare module 'intl-tel-input/intlTelInputWithUtils' {
|
||||
import intlTelInput from 'intl-tel-input';
|
||||
export default intlTelInput;
|
||||
|
||||
@@ -20,9 +20,6 @@ export const YYYYMMDDHHMMSS = 'YYYYMMDDHHmmss';
|
||||
/**年-月-日 时:分:秒 列如:2022-12-30 01:01:59 */
|
||||
export const YYYY_MM_DD_HH_MM_SS = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
/**年-月-日 时:分:秒 列如:2022-12-30T01:01:59+08:00 */
|
||||
export const YYYY_MM_DD_HH_MM_SSZ = 'YYYY-MM-DDTHH:mm:ssZZ';
|
||||
|
||||
/**
|
||||
* 格式时间字符串
|
||||
* @param dateStr 时间字符串
|
||||
@@ -39,12 +36,12 @@ export function parseStrToDate(
|
||||
/**
|
||||
* 格式时间
|
||||
* @param date 可转的Date对象
|
||||
* @param formatStr 时间格式 默认YYYY-MM-DD HH:mm:ssZZ
|
||||
* @param formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss
|
||||
* @returns 时间格式字符串
|
||||
*/
|
||||
export function parseDateToStr(
|
||||
date: string | number | Date,
|
||||
formatStr: string = YYYY_MM_DD_HH_MM_SSZ
|
||||
formatStr: string = YYYY_MM_DD_HH_MM_SS
|
||||
): string {
|
||||
return dayjs(date).format(formatStr);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function readLoalXlsx(
|
||||
/**
|
||||
* 读取表格数据 工作表
|
||||
* @param fileBolb 文件对象
|
||||
* @param index SheetName索引
|
||||
* @param index 文件保存路径
|
||||
* @return 表格对象列表
|
||||
*/
|
||||
export async function readSheet(
|
||||
|
||||
@@ -13,41 +13,13 @@ export function generateColorHEX(): string {
|
||||
* @returns rgb(24 144 255) / rgba(0,0,0,.85)
|
||||
*/
|
||||
export function generateColorRGBA(hasAlpha: boolean = false) {
|
||||
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
||||
|
||||
let red: number;
|
||||
let green: number;
|
||||
let blue: number;
|
||||
|
||||
if (isDark) {
|
||||
// 暗色模式下生成较亮的颜色
|
||||
red = Math.floor(Math.random() * 156) + 100; // 100-255
|
||||
green = Math.floor(Math.random() * 156) + 100; // 100-255
|
||||
blue = Math.floor(Math.random() * 156) + 100; // 100-255
|
||||
|
||||
// 确保至少有一个通道的值较高,使颜色更明亮
|
||||
const brightChannel = Math.floor(Math.random() * 3);
|
||||
switch (brightChannel) {
|
||||
case 0:
|
||||
red = Math.min(255, red + 50);
|
||||
break;
|
||||
case 1:
|
||||
green = Math.min(255, green + 50);
|
||||
break;
|
||||
case 2:
|
||||
blue = Math.min(255, blue + 50);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// 亮色模式下生成正常的颜色
|
||||
red = Math.floor(Math.random() * 256); // 0-255
|
||||
green = Math.floor(Math.random() * 256); // 0-255
|
||||
blue = Math.floor(Math.random() * 256); // 0-255
|
||||
}
|
||||
const red = Math.floor(Math.random() * 256);
|
||||
const green = Math.floor(Math.random() * 256);
|
||||
const blue = Math.floor(Math.random() * 256);
|
||||
|
||||
if (hasAlpha) {
|
||||
const alpha = Math.floor(Math.random() * 100);
|
||||
return `rgba(${red}, ${green}, ${blue}, 0.${alpha})`;
|
||||
return `rgb(${red}, ${green}, ${blue}, 0.${alpha})`;
|
||||
}
|
||||
|
||||
return `rgb(${red}, ${green}, ${blue})`;
|
||||
|
||||
@@ -126,7 +126,7 @@ export function parseDataToTreeExclude(
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析树结构数据转出所有一维id数组
|
||||
* 解析树结构数据转出一维id数组
|
||||
*
|
||||
* @param data 数组数据
|
||||
* @param fieldId 读取节点字段 默认 'id'
|
||||
@@ -158,7 +158,7 @@ export function parseTreeKeys(
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析树结构数据转出根节点的一维id数组
|
||||
* 解析树结构数据转出含子节点的一维id数组
|
||||
*
|
||||
* @param data 数组数据
|
||||
* @param fieldId 读取节点字段 默认 'id'
|
||||
@@ -221,43 +221,3 @@ export function parseDataToOptions(
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析树结构数据转出子节点关联根节点的一维id数组
|
||||
*
|
||||
* @param data 数组数据
|
||||
* @param checkedKeys 子节点数组数据
|
||||
* @param fieldId 读取节点字段 默认 'id'
|
||||
* @param fieldChildren 读取子节点字段 默认 'children'
|
||||
* @returns 层级数组
|
||||
*/
|
||||
export function parseTreeNodeKeysByChecked(
|
||||
data: Record<string, any>[],
|
||||
checkedKeys: (string | number)[],
|
||||
fieldId: string = 'id',
|
||||
fieldChildren: string = 'children'
|
||||
) {
|
||||
// 节点id
|
||||
let treeIds: (string | number)[] = [];
|
||||
componet(data);
|
||||
/**闭包递归函数 */
|
||||
function componet(data: Record<string, any>[]) {
|
||||
if (data.length <= 0) return false;
|
||||
let hasKey = false;
|
||||
for (const iterator of data) {
|
||||
const key = iterator[fieldId];
|
||||
if (checkedKeys.includes(key)) {
|
||||
hasKey = true;
|
||||
}
|
||||
let nodes = iterator[fieldChildren];
|
||||
if (Array.isArray(nodes) && nodes.length > 0) {
|
||||
if (componet(nodes)) {
|
||||
treeIds.push(key);
|
||||
hasKey = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasKey;
|
||||
}
|
||||
return treeIds;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user