Compare commits
67 Commits
multi-tena
...
psap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5637af9798 | ||
|
|
acdf580ba5 | ||
|
|
886fb7a7c0 | ||
|
|
0ac4e1b7ff | ||
|
|
8137808d44 | ||
|
|
d4b0f9f343 | ||
|
|
b7ae3c0cdb | ||
|
|
5e21c8d53d | ||
|
|
6598f7c67b | ||
|
|
c6b5d9861c | ||
|
|
b1776f66a0 | ||
|
|
03082ba11f | ||
|
|
3e67ebbe8a | ||
|
|
3372d9929b | ||
|
|
bcca03d983 | ||
|
|
7b91ffa19d | ||
|
|
772593e6da | ||
|
|
5a4f12687e | ||
|
|
f5684d867f | ||
|
|
14df112884 | ||
|
|
1a46b1948b | ||
|
|
db20ced6f3 | ||
|
|
50543a241c | ||
|
|
eb809f4f3d | ||
|
|
4cba38f0c0 | ||
|
|
34cbcb808a | ||
|
|
e53328176c | ||
|
|
c1fe8cd4c5 | ||
|
|
42c4e19f84 | ||
|
|
270f9915dc | ||
|
|
af15418695 | ||
|
|
331315ab84 | ||
|
|
43034fd082 | ||
|
|
7e790d90b8 | ||
|
|
0a19aaa8df | ||
|
|
bfa80e10f5 | ||
|
|
edd399ac5c | ||
|
|
9a47e34dfc | ||
|
|
ab1532c2f5 | ||
|
|
eed90b24ef | ||
|
|
4e63395383 | ||
|
|
956cbfc3a3 | ||
|
|
f5b843d9a8 | ||
|
|
1246308a3d | ||
|
|
e1343ce228 | ||
|
|
0cb7158f57 | ||
|
|
63c7ae2538 | ||
|
|
48ddafaec9 | ||
|
|
eeeae3dd12 | ||
|
|
f0a5da681c | ||
|
|
35c7b86865 | ||
|
|
cef90a49f9 | ||
|
|
c9a0fd7818 | ||
|
|
7e35dca9d8 | ||
|
|
860e06e7b0 | ||
|
|
b352533523 | ||
|
|
a8a5c0a31e | ||
|
|
26686f88db | ||
|
|
fb3f1daecf | ||
|
|
3680da64c1 | ||
|
|
f87fcb73b9 | ||
|
|
c11227d747 | ||
|
|
8a612e0760 | ||
|
|
29f5e41976 | ||
|
|
3ab0b47095 | ||
|
|
aa04abdbb4 | ||
|
|
db95099934 |
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
||||
VITE_APP_CODE = "OMC"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "local-dev"
|
||||
VITE_APP_VERSION = "2.250412"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
||||
VITE_APP_CODE = "OMC"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "local-prod"
|
||||
VITE_APP_VERSION = "2.250412"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
38
CHANGELOG.md
@@ -1,43 +1,5 @@
|
||||
# 版本发布日志
|
||||
|
||||
## 2.2510.4-20251024
|
||||
|
||||
- 修复 AMF配置WhiteList Content导入index0无效问题
|
||||
- 新增 更新背景图片固定BA的
|
||||
- 新增 基站状态补充randId列
|
||||
- 新增 Roaming CDR自定义导出功能
|
||||
- 优化 基站名称和位置信息改为非必填
|
||||
- 修复 网元信息更新响应修复
|
||||
- 新增 UDM鉴权cnFlag添加5G/4G接入选择
|
||||
- 新增 更新getAllNeConfig函数,支持传入neId参数
|
||||
- 修复 更新根网管节点处理逻辑,支持无OMC情况
|
||||
- 优化 移除ID列显示
|
||||
- 优化 操作日志信息json结构格式美化
|
||||
- 优化 备份网元日志文件数据查看
|
||||
- 优化 悬浮标签修改
|
||||
|
||||
## 2.2510.2-20251011
|
||||
|
||||
- 新增 UDM-auth数据导出按钮权限定义
|
||||
- 新增 SMSC-CDR添加结果原因说明
|
||||
- 优化 移除CDR/UE/网元版本/授权列表显示ID列
|
||||
- 修复 MML回车undefined问题,关闭搜索匹配
|
||||
|
||||
## 2.2408.1-20250815
|
||||
|
||||
- 修复 给config.js加随机数避免缓存
|
||||
- 优化 UDM鉴权用户显示创建时间
|
||||
- 修复 系统用户登录主页重复刷新问题
|
||||
- 新增 kpi自定义仪表盘需求更改
|
||||
- 修复 租户主页暗色模式显示不一致
|
||||
- 修复 租户和系统用户的主页显示差异
|
||||
- 修复 图片上传取值fileName失败
|
||||
- 优化 第三方用户不可删除和修改密码
|
||||
- 修复 恢复显示快速PLMN修改窗口
|
||||
- 新增 第三方登录认证功能和管理页
|
||||
- 修复 回退看板,修复切换upf的流量统计显示
|
||||
|
||||
|
||||
## 2.2404.1-20240402
|
||||
|
||||
- 新增 网元安装流程相关页面与操作相关接口联调
|
||||
|
||||
28
README.md
@@ -46,31 +46,5 @@ export NODE_OPTIONS=--max-old-space-size=50000
|
||||
|
||||
```text
|
||||
https://192.168.5.23/
|
||||
admin
|
||||
admin
|
||||
admin / admin
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 多租户涉及页面
|
||||
ems_frontend_vue3\src\views\dashboard\smfCDR\index.vue
|
||||
ems_frontend_vue3\src\views\dashboard\imsCDR\index.vue
|
||||
ems_frontend_vue3\src\views\dashboard\amfUE\index.vue
|
||||
ems_frontend_vue3\src\views\dashboard\mmeUE\index.vue
|
||||
ems_frontend_vue3\src\views\system\user\index.vue
|
||||
ems_frontend_vue3\src\views\system\tenant\index.vue
|
||||
ems_frontend_vue3\src\views\system\log\operate\index.vue
|
||||
ems_frontend_vue3\src\views\neUser\sub\index.vue
|
||||
ems_frontend_vue3\src\views\neUser\ims\index.vue
|
||||
ems_frontend_vue3\src\views\neUser\ue\index.vue
|
||||
ems_frontend_vue3\src\views\neUser\base5G\index.vue
|
||||
|
||||
ems_frontend_vue3\src\views\tenant\amfUE\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\base5G\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\ims\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\imsCDR\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\mmeUE\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\operate\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\smfCDR\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\sub\index.vue
|
||||
ems_frontend_vue3\src\views\tenant\ue\index.vue
|
||||
12
index.html
@@ -2,18 +2,14 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="google" content="notranslate" />
|
||||
<meta name="google" content="notranslate">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>loading...</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="preload" href="/loading.js" as="script"/>
|
||||
<link rel="preload" href="/loading.js" as="script">
|
||||
<script async src="/loading.js"></script>
|
||||
<script>
|
||||
var script = document.createElement('script');
|
||||
script.src = '/config.js?nocache=' + new Date().getTime();
|
||||
script.async = true;
|
||||
document.head.appendChild(script);
|
||||
</script>
|
||||
<link rel="preload" href="/config.js" as="script">
|
||||
<script async src="/config.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
77
package.json
@@ -8,55 +8,54 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build": "vue-tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "7.0.1",
|
||||
"@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",
|
||||
"echarts-liquidfill": "^3.1.0",
|
||||
"file-saver": "2.0.5",
|
||||
"grid-layout-plus": "1.0.6",
|
||||
"intl-tel-input": "25.2.0",
|
||||
"@codemirror/lang-javascript": "^6.2.2",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/merge": "^6.8.0",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@tato30/vue-pdf": "^1.11.3",
|
||||
"@vueuse/core": "^12.5.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.11",
|
||||
"echarts": "~5.6.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"grid-layout-plus": "^1.0.6",
|
||||
"intl-tel-input": "~25.2.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.3.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-i18n": "^11.1.0",
|
||||
"vue-router": "^4.5.0",
|
||||
"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/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/nprogress": "^0.2.3",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"less": "^4.2.1",
|
||||
"typescript": "~5.6.3",
|
||||
"unplugin-vue-components": "^0.28.0",
|
||||
"vite": "^6.1.0",
|
||||
"vite-plugin-compression": "~0.5.1",
|
||||
"vue-tsc": "^2.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 216 KiB |
@@ -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
|
||||
*
|
||||
@@ -20,7 +20,7 @@
|
||||
host = `${hostname}:33443`;
|
||||
wsprotocol = "wss:"
|
||||
}
|
||||
|
||||
|
||||
// Service Address
|
||||
sessionStorage.setItem('baseUrl', `${protocol}//${host}`);
|
||||
// websocket Address
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
001012082101039,1234,internet|ims_sig,internet|ims_sig,321321,255,321312,32131,32131
|
||||
@@ -1,2 +0,0 @@
|
||||
001011100001157,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111
|
||||
001011100001158,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111
|
||||
@@ -1,2 +0,0 @@
|
||||
001011100001157,62357000583,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&internet&ims,1,64,24,65,def_eps,1,010200000000,-
|
||||
001011100001158,62357000585,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&internet&ims,1,64,24,65,def_eps,1,010200000000,-
|
||||
@@ -1,2 +0,0 @@
|
||||
62357000580,123456
|
||||
62357000581,123456
|
||||
@@ -1,2 +0,0 @@
|
||||
001012082101039,62357000580,1,ims.mnc001.mcc001.3gppnetwork.org
|
||||
62357000581,62357000581,0,ims.mnc001.mcc001.3gppnetwork.org
|
||||
@@ -9,8 +9,6 @@ import advancedFormat from 'dayjs/plugin/advancedFormat';
|
||||
import useLayoutStore from '@/store/modules/layout';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { CACHE_LOCAL_PROCONFIG } from './constants/cache-keys-constants';
|
||||
import { localRemove } from './utils/cache-local-utils';
|
||||
const { t, currentLocale } = useI18n();
|
||||
const { themeConfig, initPrimaryColor, changeConf } = useLayoutStore();
|
||||
dayjs.extend(advancedFormat);
|
||||
@@ -36,7 +34,6 @@ onBeforeMount(() => {
|
||||
maxCount: 15,
|
||||
});
|
||||
initPrimaryColor();
|
||||
localRemove(CACHE_LOCAL_PROCONFIG);
|
||||
|
||||
// 输出应用版本号
|
||||
const appStore = useAppStore();
|
||||
|
||||
27
src/api/agentManage/callback.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
|
||||
/**
|
||||
* 查询工单列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listCallBack(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/psap/v1/mf/ticket/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换进行中状态
|
||||
* @param
|
||||
* @returns bolb
|
||||
*/
|
||||
export function updateStatus(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `psap/v1/mf/ticket/${data.ticketId}/status`,
|
||||
method: 'PATCH',
|
||||
});
|
||||
}
|
||||
16
src/api/agentManage/callings.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
|
||||
/**
|
||||
* 查询定时任务调度列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listCallings(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/psap/v1/mf/callings/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
75
src/api/cbc/cbe.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* CBC列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listCBC(query: Record<string, any>) {
|
||||
return request({
|
||||
url: `/psap/v1/cbc/${query.neId}/message/list`,
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* CBC签约用户新增
|
||||
* @param data 签约对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addCBC(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/psap/v1/cbc/${data.neId}/message`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export function updateCBCStatus(data:any) {
|
||||
return request({
|
||||
url: `/psap/v1/cbc/${data.neId}/message/${data.id}/${data.status}`,
|
||||
method: 'put',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function updateCBC(data:any) {
|
||||
return request({
|
||||
url: `/psap/v1/cbc/${data.neId}/message/${data.id}`,
|
||||
method: 'put',
|
||||
data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* CBC删除
|
||||
* @param data 签约对象
|
||||
* @returns object
|
||||
*/
|
||||
export function delCBC(neId: string, id: string) {
|
||||
return request({
|
||||
url: `/psap/v1/cbc/${neId}/message/${id}`,
|
||||
method: 'delete',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ export function getPass() {
|
||||
* @param data 鉴权对象
|
||||
* @returns object
|
||||
*/
|
||||
export function clearAlarm2(data: Record<string, any>) {
|
||||
export function clearAlarm(data: Record<string, any>) {
|
||||
var time = new Date();
|
||||
const userName = useUserStore().userName;
|
||||
let finalData = {
|
||||
@@ -232,19 +232,6 @@ export function clearAlarm2(data: Record<string, any>) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除告警信息
|
||||
* @param ids 记录ID
|
||||
* @returns object
|
||||
*/
|
||||
export function clearAlarm(ids: string[]) {
|
||||
return request({
|
||||
url: `/neData/alarm/clear`,
|
||||
method: 'PUT',
|
||||
data: { ids },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 手工同步
|
||||
* @param data 鉴权对象
|
||||
@@ -373,137 +360,3 @@ export async function top3Sel(filterFlag?: string) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export async function alarmDashGetAct() {
|
||||
let totalSQL = `select count(*) as total from alarm where alarm_status='1' `;
|
||||
let rowsSQL = `select ne_type,alarm_id,alarm_title,orig_severity,event_time from alarm WHERE alarm_status='1' and orig_severity!='Event' order by event_time desc limit 0,10 `;
|
||||
// 查询
|
||||
|
||||
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: totalSQL,
|
||||
rowsSQL: rowsSQL,
|
||||
},
|
||||
});
|
||||
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
const data: DataList = {
|
||||
total: 0,
|
||||
rows: [],
|
||||
code: result.code,
|
||||
msg: result.msg,
|
||||
};
|
||||
result.data.data.forEach((item: any) => {
|
||||
const itemData = item['alarm'];
|
||||
if (Array.isArray(itemData)) {
|
||||
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
|
||||
data.total = itemData[0]['total'];
|
||||
} else {
|
||||
data.rows = itemData.map(v => parseObjLineToHump(v));
|
||||
}
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function alarmDashGetHis() {
|
||||
let totalSQL = `select count(*) as total from alarm where alarm_status='0' `;
|
||||
let rowsSQL = `select ne_type,alarm_id,alarm_title,orig_severity,event_time from alarm WHERE alarm_status='0' and orig_severity!='Event' order by event_time desc limit 0,10 `;
|
||||
// 查询
|
||||
|
||||
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: totalSQL,
|
||||
rowsSQL: rowsSQL,
|
||||
},
|
||||
});
|
||||
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
const data: DataList = {
|
||||
total: 0,
|
||||
rows: [],
|
||||
code: result.code,
|
||||
msg: result.msg,
|
||||
};
|
||||
result.data.data.forEach((item: any) => {
|
||||
const itemData = item['alarm'];
|
||||
if (Array.isArray(itemData)) {
|
||||
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
|
||||
data.total = itemData[0]['total'];
|
||||
} else {
|
||||
data.rows = itemData.map(v => parseObjLineToHump(v));
|
||||
}
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
export async function getAlarmTrend(params: any) {
|
||||
const days = Number(params.days || 1);
|
||||
let groupFormat = days === 1 ? '%H:00' : '%Y-%m-%d';
|
||||
let timeCondition = days === 1
|
||||
? `event_time >= DATE_SUB(NOW(), INTERVAL 1 DAY)`
|
||||
: `event_time >= DATE_SUB(CURDATE(), INTERVAL ${days} DAY)`;
|
||||
let totalSQL = `select count(*) as total from alarm where alarm_status='0' `;
|
||||
|
||||
let rowsSQL = ` SELECT
|
||||
DATE_FORMAT(event_time, '${groupFormat}') AS time,
|
||||
SUM(CASE WHEN orig_severity='Critical' THEN 1 ELSE 0 END) AS Critical,
|
||||
SUM(CASE WHEN orig_severity='Major' THEN 1 ELSE 0 END) AS Major,
|
||||
SUM(CASE WHEN orig_severity='Minor' THEN 1 ELSE 0 END) AS Minor,
|
||||
SUM(CASE WHEN orig_severity='Warning' THEN 1 ELSE 0 END) AS Warning
|
||||
FROM alarm
|
||||
WHERE alarm_status='0'
|
||||
AND ${timeCondition}
|
||||
GROUP BY time
|
||||
ORDER BY time ASC `;
|
||||
// 查询
|
||||
|
||||
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: totalSQL,
|
||||
rowsSQL: rowsSQL,
|
||||
},
|
||||
});
|
||||
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS) {
|
||||
const data: DataList = {
|
||||
total: 0,
|
||||
rows: [],
|
||||
code: result.code,
|
||||
msg: result.msg,
|
||||
};
|
||||
result.data.data.forEach((item: any) => {
|
||||
const itemData = item['alarm'];
|
||||
if (Array.isArray(itemData)) {
|
||||
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
|
||||
data.total = itemData[0]['total'];
|
||||
} else {
|
||||
data.rows = itemData.map(v => parseObjLineToHump(v));
|
||||
}
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,69 +61,3 @@ export function getCaptchaImage() {
|
||||
whithToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录认证源
|
||||
* @returns object
|
||||
*/
|
||||
export function getLoginSource() {
|
||||
return request({
|
||||
url: '/auth/login/source',
|
||||
method: 'GET',
|
||||
whithToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP登录
|
||||
* @returns object
|
||||
*/
|
||||
export function loginLDAP(data: Record<string, string>) {
|
||||
return request({
|
||||
url: '/auth/login/ldap',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
whithToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SMTP登录
|
||||
* @returns object
|
||||
*/
|
||||
export function loginSMTP(data: Record<string, string>) {
|
||||
return request({
|
||||
url: '/auth/login/smtp',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
whithToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录认证源OAuth2跳转登录URL
|
||||
* @returns object
|
||||
*/
|
||||
export function loginOAuth2URL(state: string): string {
|
||||
// 兼容旧前端可改配置文件
|
||||
const baseUrl = import.meta.env.PROD
|
||||
? sessionGet('baseUrl') || import.meta.env.VITE_API_BASE_URL
|
||||
: import.meta.env.VITE_API_BASE_URL;
|
||||
return `${baseUrl}/auth/login/oauth2/authorize?state=${state}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录认证源OAuth2认证登录
|
||||
* @returns object
|
||||
*/
|
||||
export function loginOAuth2Token(code: string, state: string) {
|
||||
return request({
|
||||
url: '/auth/login/oauth2/token',
|
||||
method: 'POST',
|
||||
data: {
|
||||
code,
|
||||
state,
|
||||
},
|
||||
whithToken: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,11 +5,10 @@ import { request } from '@/plugins/http-fetch';
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function getAllNeConfig(neType: string, neId: string) {
|
||||
export function getAllNeConfig(neType: string) {
|
||||
return request({
|
||||
url: `/ne/config/list/${neType}`,
|
||||
method: 'get',
|
||||
params: { neId },
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function exportAMFDataUE(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 备份文件-获取FTP配置
|
||||
* @returns object
|
||||
*/
|
||||
export function getBackupFTP() {
|
||||
return request({
|
||||
url: '/neData/backup/ftp',
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份文件-文件FTP发送
|
||||
* @param data 对象
|
||||
* @returns object
|
||||
*/
|
||||
export function pushBackupFTP(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/backup/ftp',
|
||||
method: 'POST',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份文件-更新FTP配置
|
||||
* @param data 对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateBackupFTP(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/backup/ftp',
|
||||
method: 'PUT',
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份文件-导出OMC
|
||||
* @returns object
|
||||
*/
|
||||
export function exportBackupOMC() {
|
||||
return request({
|
||||
url: '/neData/backup/export-omc',
|
||||
method: 'POST',
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 备份文件-导入OMC
|
||||
* @param filePath 备份文件上传返回的/upload 路径
|
||||
* @returns object
|
||||
*/
|
||||
export function importBackupOMC(filePath: string) {
|
||||
return request({
|
||||
url: '/neData/backup/import-omc',
|
||||
method: 'POST',
|
||||
data: {
|
||||
neType: 'OMC',
|
||||
path: filePath,
|
||||
},
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
@@ -38,6 +38,6 @@ export function exportIMSDataCDR(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* UDM签约用户重载数据
|
||||
* @param neId 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export function resetIMSSub(neId: string) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/resetData/${neId}`,
|
||||
method: 'put',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listIMSSub(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ue/udm/imsuser/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户信息
|
||||
* @param neId 网元ID
|
||||
* @param imsi IMSI
|
||||
* @returns object
|
||||
*/
|
||||
export function getIMSSub(neId: string, imsi: string) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/${neId}/${imsi}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户新增
|
||||
* @param data 签约对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addIMSSub(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/${data.neId}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户批量新增
|
||||
* @param data 签约对象
|
||||
* @param num 数量
|
||||
* @returns object
|
||||
*/
|
||||
export function batchAddIMSSub(data: Record<string, any>, num: number) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/${data.neId}/${num}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户修改
|
||||
* @param data 签约对象
|
||||
* @returns object
|
||||
*/
|
||||
// export function updateIMSSub(data: Record<string, any>) {
|
||||
// return request({
|
||||
// url: `/ue/udm/imsuser/${data.neId}`,
|
||||
// method: 'put',
|
||||
// data: data,
|
||||
// timeout: 180_000,
|
||||
// });
|
||||
// }
|
||||
|
||||
/**
|
||||
* UDM签约用户删除
|
||||
* @param data 签约对象
|
||||
* @returns object
|
||||
*/
|
||||
export function delIMSSub(neId: string, imsi_msisdn: string, tag: string) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/${neId}/${imsi_msisdn}`,
|
||||
method: 'delete',
|
||||
params: { volte: tag },
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户批量删除
|
||||
* @param neId 网元ID
|
||||
* @param imsi IMSI
|
||||
* @param num 数量
|
||||
* @returns object
|
||||
*/
|
||||
export function batchDelIMSSub(
|
||||
neId: string,
|
||||
imsi: string,
|
||||
num: number,
|
||||
tag: string
|
||||
) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/${neId}/${imsi}/${num}`,
|
||||
method: 'delete',
|
||||
params: { volte: tag },
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户导出
|
||||
* @param data 数据参数
|
||||
* @returns bolb
|
||||
*/
|
||||
export function exportIMSSub(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ue/udm/imsuser/export',
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM签约用户导入
|
||||
* @param data 表单数据对象
|
||||
* @returns object
|
||||
*/
|
||||
export function importIMSSub(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ue/udm/imsuser/import`,
|
||||
method: 'post',
|
||||
data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
43
src/api/neData/mf.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询IMS-CDR会话事件
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listMFDataCDR(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/mf/cdr/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* IMS-CDR会话删除
|
||||
* @param id 信息ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delMFDataCDR(cdrIds: string | number) {
|
||||
return request({
|
||||
url: `/neData/mf/cdr/${cdrIds}`,
|
||||
method: 'delete',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* IMS-CDR会话列表导出
|
||||
* @param data 查询列表条件
|
||||
* @returns object
|
||||
*/
|
||||
export function exportMFDataCDR(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/mf/cdr/export',
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export function exportMMEDataUE(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,6 @@ export function exportNBState(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,6 +38,6 @@ export function exportSGWCDataCDR(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function exportSMFDataCDR(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,6 @@ export function exportSMSCDataCDR(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -140,17 +140,3 @@ export function exportUDMAuth(data: Record<string, any>) {
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户导出DecAuth
|
||||
* @param neId 网元ID
|
||||
* @returns bolb
|
||||
*/
|
||||
export function exportUDMDecAuth(neId: string) {
|
||||
return request({
|
||||
url: `/neData/udm/auth/export-dec?neId=${neId}`,
|
||||
method: 'get',
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* UDM鉴权用户重载数据
|
||||
* @param neId 网元ID
|
||||
* @returns object
|
||||
*/
|
||||
export function resetUDMAuth(neId: string) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/resetData/${neId}`,
|
||||
method: 'put',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listUDMAuth(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ue/udm/voipauth/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户信息
|
||||
* @param neId 网元ID
|
||||
* @param userName 用户名
|
||||
* @returns object
|
||||
*/
|
||||
export function getUDMAuth(neId: string, userName: string) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/${neId}/${userName}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户新增
|
||||
* @param data 鉴权对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addUDMAuth(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/${data.neId}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户批量新增
|
||||
* @param data 鉴权对象
|
||||
* @param num 数量
|
||||
* @returns object
|
||||
*/
|
||||
export function batchAddUDMAuth(data: Record<string, any>, num: number) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/${data.neId}/${num}`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户修改
|
||||
* @param data 鉴权对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateUDMAuth(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/${data.neId}`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户删除
|
||||
* @param neId 网元ID
|
||||
* @param imsi IMSI
|
||||
* @returns object
|
||||
*/
|
||||
export function delUDMAuth(neId: string, imsi: string) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/${neId}/${imsi}`,
|
||||
method: 'delete',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户批量删除
|
||||
* @param neId 网元ID
|
||||
* @param imsi IMSI
|
||||
* @param num 数量
|
||||
* @returns object
|
||||
*/
|
||||
export function batchDelUDMAuth(neId: string, imsi: string, num: number) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/${neId}/${imsi}/${num}`,
|
||||
method: 'delete',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户导入
|
||||
* @param data 表单数据对象
|
||||
* @returns object
|
||||
*/
|
||||
export function importUDMAuth(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/ue/udm/voipauth/import`,
|
||||
method: 'post',
|
||||
data,
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UDM鉴权用户导出
|
||||
* @param data 数据参数
|
||||
* @returns bolb
|
||||
*/
|
||||
export function exportUDMAuth(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/ue/udm/voipauth/export',
|
||||
method: 'post',
|
||||
data,
|
||||
responseType: 'blob',
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
@@ -21,14 +21,12 @@ export async function listUEInfoBySMF(query: Record<string, any>) {
|
||||
};
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && result.data) {
|
||||
if (result.data.total && result.data.data) {
|
||||
data.total = result.data.total;
|
||||
data.rows = result.data.data;
|
||||
if (Array.isArray(result.data.data)) {
|
||||
const rows = result.data.data;
|
||||
data.total = rows.length;
|
||||
data.rows = rows;
|
||||
} else {
|
||||
Object.assign(data, {
|
||||
total: result.data.length,
|
||||
rows: result.data,
|
||||
});
|
||||
Object.assign(data, result.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,52 +110,78 @@ export async function getKPITitle(neType: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询黄金指标数据kpi.id转换title
|
||||
* @param neType 网元类型
|
||||
* Todo 废弃
|
||||
* 查询UPF上下行速率数据
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function getKPITitleList(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/kpi/title/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
export async function listUPFData(timeArr: any) {
|
||||
const initTime: Date = new Date();
|
||||
const twentyFourHoursAgo: Date = new Date(
|
||||
initTime.getTime() - 10 * 60 * 1000
|
||||
);
|
||||
|
||||
/**
|
||||
* 修改指标标题
|
||||
* @param data 指标标题对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateKPITitle(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/kpi/title',
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
//忙时呼叫
|
||||
export async function getbusyhour(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/ims/kpi/busy-hour',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
//MOS指标
|
||||
export async function getMosHour(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/ims/cdr/mos-hour',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
return await Promise.allSettled([
|
||||
// 获取参数规则
|
||||
request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/gold_kpi`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `SELECT gold_kpi.*,kpi_title.en_title FROM gold_kpi LEFT JOIN kpi_title on gold_kpi.kpi_id=kpi_title.kpi_id where 1=1 and gold_kpi.kpi_id ='UPF.03' AND timestamp BETWEEN DATE_SUB(NOW(), INTERVAL 10 MINUTE) AND NOW()`,
|
||||
},
|
||||
timeout: 60_000,
|
||||
}),
|
||||
// 获取对应信息
|
||||
request({
|
||||
url: `/api/rest/databaseManagement/v1/select/omc_db/gold_kpi`,
|
||||
method: 'get',
|
||||
params: {
|
||||
SQL: `SELECT gold_kpi.*,kpi_title.en_title FROM gold_kpi LEFT JOIN kpi_title on gold_kpi.kpi_id=kpi_title.kpi_id where 1=1 and gold_kpi.kpi_id ='UPF.06' AND timestamp BETWEEN DATE_SUB(NOW(), INTERVAL 10 MINUTE) AND NOW()`,
|
||||
},
|
||||
timeout: 60_000,
|
||||
}),
|
||||
]).then(resArr => {
|
||||
let upData: any = [];
|
||||
let downData: any = [];
|
||||
|
||||
//CCT指标
|
||||
export async function getCctHour(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/ims/cdr/cct-hour',
|
||||
method: 'get',
|
||||
params: query,
|
||||
// 规则数据
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
const itemV: any = resArr[0].value;
|
||||
// 解析数据
|
||||
if (
|
||||
itemV.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(itemV.data?.data)
|
||||
) {
|
||||
let itemData = itemV.data.data;
|
||||
let data = itemData[0]['gold_kpi'];
|
||||
if (Array.isArray(data)) {
|
||||
try {
|
||||
upData = data.map(v => parseObjLineToHump(v));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
const itemV = resArr[1].value;
|
||||
// 解析数据
|
||||
if (
|
||||
itemV.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(itemV.data?.data)
|
||||
) {
|
||||
let itemData = itemV.data.data;
|
||||
const data = itemData[0]['gold_kpi'];
|
||||
if (Array.isArray(data)) {
|
||||
try {
|
||||
downData = data.map(v => parseObjLineToHump(v));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { upData, downData };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询登录源列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listLoginSource(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/system/login-source/list',
|
||||
method: 'GET',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询登录源详细
|
||||
* @param id 登录源ID
|
||||
* @returns object
|
||||
*/
|
||||
export function getLoginSource(id: string | number) {
|
||||
return request({
|
||||
url: `/system/login-source/${id}`,
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增登录源
|
||||
* @param data 登录源对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addLoginSource(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/system/login-source',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改登录源
|
||||
* @param data 登录源对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateLoginSource(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/system/login-source',
|
||||
method: 'PUT',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证源删除
|
||||
* @param id 登录源ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delLoginSource(id: string | number) {
|
||||
return request({
|
||||
url: `/system/login-source/${id}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 查询部门列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listTenant(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/system/tenant/list',
|
||||
method: 'get',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部门列表(排除节点)
|
||||
* @param tenantId 部门ID
|
||||
* @returns object
|
||||
*/
|
||||
export function listTenantExcludeChild(tenantId: string | number) {
|
||||
return request({
|
||||
url: `/system/tenant/list/exclude/${tenantId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部门详细
|
||||
* @param tenantId 部门ID
|
||||
* @returns object
|
||||
*/
|
||||
export function getTenant(tenantId: string | number) {
|
||||
return request({
|
||||
url: `/system/tenant/${tenantId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增部门
|
||||
* @param data 部门对象
|
||||
* @returns object
|
||||
*/
|
||||
export function addTenant(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/system/tenant',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改部门
|
||||
* @param data 部门对象
|
||||
* @returns object
|
||||
*/
|
||||
export function updateTenant(data: Record<string, any>) {
|
||||
return request({
|
||||
url: '/system/tenant',
|
||||
method: 'put',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
* @param TenantId 部门ID
|
||||
* @returns object
|
||||
*/
|
||||
export function delTenant(TenantId: string | number) {
|
||||
return request({
|
||||
url: `/system/tenant/${TenantId}`,
|
||||
method: 'delete',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部门下拉树结构
|
||||
* @returns object
|
||||
*/
|
||||
export function tenantTreeSelect() {
|
||||
return request({
|
||||
url: '/system/tenant/treeSelect',
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门树结构列表(指定角色)
|
||||
* @param roleId 角色ID
|
||||
* @returns object
|
||||
*/
|
||||
export function roleTenantTreeSelect(roleId: string | number) {
|
||||
return request({
|
||||
url: `/system/tenant/roleDeptTreeSelect/${roleId}`,
|
||||
method: 'get',
|
||||
});
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import { encode } from 'js-base64';
|
||||
export async function downloadFile(filePath: string, range?: string) {
|
||||
return request({
|
||||
url: `/file/download/${encode(filePath)}`,
|
||||
method: 'GET',
|
||||
method: 'get',
|
||||
headers: range ? { range } : {},
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
@@ -76,8 +76,8 @@ export async function downloadFileChunk(
|
||||
*/
|
||||
export function uploadFile(data: FormData) {
|
||||
return request({
|
||||
url: '/file/upload2',
|
||||
method: 'POST',
|
||||
url: '/file/upload',
|
||||
method: 'post',
|
||||
data,
|
||||
dataType: 'form-data',
|
||||
timeout: 180_000,
|
||||
@@ -169,7 +169,7 @@ export async function uploadFileChunk(
|
||||
export function chunkCheck(identifier: string, fileName: string) {
|
||||
return request({
|
||||
url: '/file/chunkCheck',
|
||||
method: 'POST',
|
||||
method: 'post',
|
||||
data: { identifier, fileName },
|
||||
timeout: 60_000,
|
||||
});
|
||||
@@ -189,7 +189,7 @@ export function chunkMerge(
|
||||
) {
|
||||
return request({
|
||||
url: '/file/chunkMerge',
|
||||
method: 'POST',
|
||||
method: 'post',
|
||||
data: { identifier, fileName, subPath },
|
||||
timeout: 60_000,
|
||||
});
|
||||
@@ -203,57 +203,13 @@ export function chunkMerge(
|
||||
export function chunkUpload(data: FormData) {
|
||||
return request({
|
||||
url: '/file/chunkUpload',
|
||||
method: 'POST',
|
||||
method: 'post',
|
||||
data,
|
||||
dataType: 'form-data',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地文件列表
|
||||
* @param path 文件路径
|
||||
* @param search search prefix
|
||||
* @returns object
|
||||
*/
|
||||
export async function listFile(query: Record<string, any>) {
|
||||
return request({
|
||||
url: `/file/list`,
|
||||
method: 'GET',
|
||||
params: query,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地文件获取下载
|
||||
* @param path 文件路径
|
||||
* @param fileName 文件名
|
||||
* @returns object
|
||||
*/
|
||||
export async function getFile(path: string, fileName: string) {
|
||||
return request({
|
||||
url: `/file`,
|
||||
method: 'GET',
|
||||
params: { path, fileName },
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地文件删除
|
||||
* @param path 文件路径
|
||||
* @param fileName 文件名
|
||||
* @returns object
|
||||
*/
|
||||
export async function delFile(path: string, fileName: string) {
|
||||
return request({
|
||||
url: `/file`,
|
||||
method: 'DELETE',
|
||||
params: { path, fileName },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 转存上传文件到静态资源
|
||||
* @returns object
|
||||
@@ -261,7 +217,7 @@ export async function delFile(path: string, fileName: string) {
|
||||
export function transferStaticFile(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/file/transferStaticFile`,
|
||||
method: 'POST',
|
||||
method: 'post',
|
||||
data,
|
||||
timeout: 60_000,
|
||||
});
|
||||
@@ -285,12 +241,11 @@ export async function uploadFileToNE(
|
||||
if (uploadChunkRes.code === RESULT_CODE_SUCCESS) {
|
||||
const transferToNeFileRes = await request({
|
||||
url: `/ne/action/pushFile`,
|
||||
method: 'POST',
|
||||
method: 'post',
|
||||
data: {
|
||||
uploadPath: uploadChunkRes.data.fileName,
|
||||
neType,
|
||||
neId,
|
||||
delTemp: true,
|
||||
},
|
||||
timeout: 60_000,
|
||||
});
|
||||
|
||||
@@ -45,6 +45,6 @@ export function getNeViewFile(data: Record<string, any>) {
|
||||
url: '/ne/action/viewFile',
|
||||
method: 'get',
|
||||
params: data,
|
||||
timeout: 600_000,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,12 +8,3 @@ export function pingV(data: Record<string, string>) {
|
||||
params: data,
|
||||
});
|
||||
}
|
||||
|
||||
// ping RTT 时延抖动
|
||||
export function pingRTT(data: Record<string, string>) {
|
||||
return request({
|
||||
url: '/tool/ping/rtt',
|
||||
method: 'POST',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
/**
|
||||
* 查询跟踪任务列表
|
||||
@@ -79,26 +81,24 @@ export function filePullTask(traceId: string) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 跟踪任务数据列表
|
||||
* @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;
|
||||
}
|
||||
|
||||
BIN
src/assets/background_dark.jpg
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
src/assets/background_light.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1721721606045" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7339" width="200" height="200"><path d="M610.6 234.24L627 294.6c90.88-24.73 184.58 28.77 209.45 119.65l60.36-16.4C862.92 273.5 734.67 200.37 610.6 234.24z m-34.42-126.22l16.4 60.36c160.51-43.83 326.13 50.81 369.96 211.33l60.36-16.4C969.94 169.45 770.03 55.18 576.18 108.02zM179.47 922.27V827.5H0v-84.83l179.47-261.74h92.22v274.24h62.38v72.33h-62.38v94.77h-92.22z m0-167.09V598.02L72.33 755.18h107.14z m0 0" p-id="7340" fill="#ffffff"></path><path d="M695.69 752.62h-89.8v-77.29h186.99v231.9c-33.34 9.95-67.35 14.92-102.17 14.92-26.62 3.37-61.57 4.97-104.72 4.97-141.29-8.33-213.62-86.44-216.97-234.31 3.36-151.24 75.69-231.9 216.97-241.85 144.65 0 216.03 49.88 214.42 149.63H695.69c0-51.49-30.79-77.3-92.23-77.3-84.83 1.62-127.98 58.21-129.59 169.52 1.61 109.7 44.9 165.49 129.59 167.1 24.87 0 54.05-3.36 87.25-9.95-1.61 1.61 0 1.61 4.98 0v-97.34z m0 0" p-id="7341" fill="#ffffff"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1721721630123" class="icon" viewBox="0 0 1216 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="54141" width="200" height="200"><path d="M747.264 190.912l18.368 67.328a190.272 190.272 0 0 1 233.728 133.44l67.264-18.368a260.032 260.032 0 0 0-319.36-182.4z m-38.4-140.736l18.368 67.328a336.192 336.192 0 0 1 412.8 235.776l67.328-18.368A405.952 405.952 0 0 0 708.864 50.176z m133.376 719.232h-100.16v-86.272h208.64v258.752c-37.12 11.136-75.072 16.704-114.048 16.704a984.96 984.96 0 0 1-116.864 5.568c-157.632-9.28-238.336-96.448-242.048-261.504 3.712-168.768 84.416-258.752 242.048-269.888 161.344 0 241.088 55.68 239.296 166.912H842.24c0-57.472-34.368-86.208-102.912-86.208-94.592 1.856-142.848 64.896-144.64 189.184 1.792 122.368 50.048 184.576 144.64 186.368 27.776 0 60.16-3.712 97.344-11.136-1.92 1.92 0 1.92 5.568 0v-108.48zM234.24 969.216c-86.016 0-148.992-30.72-201.216-80.64l71.424-85.248c40.704 36.864 80.64 58.368 128.256 58.368 55.296 0 89.856-26.88 89.856-74.496v-1.536c0-46.08-39.168-72.96-95.232-72.96-33.792 0-64.512 9.216-89.856 19.968L69.12 687.36l15.36-264.96h330.24v103.68H185.856l-6.144 92.928a262.976 262.976 0 0 1 70.656-9.216c104.448 0 188.16 50.688 188.16 172.032v1.536c0 113.664-80.64 185.856-204.288 185.856z" p-id="54142" fill="#ffffff"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1720074329868" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4280" width="64" height="64"><path d="M616.5 151.3l-28.3 28.3c19.7 19.6 31.8 46.6 31.8 76.5 0 29.8-12.2 56.9-31.8 76.5l28.3 28.3c26.8-27 43.5-64 43.5-104.9s-16.7-77.9-43.5-104.7zM435.8 179.5l-28.3-28.3C380.7 178.1 364 215.1 364 256s16.7 77.9 43.5 104.7l28.3-28.3C416.2 312.9 404 285.8 404 256c0-29.9 12.2-56.9 31.8-76.5zM346.6 134.3L318.3 106c-38.5 38.4-62.3 91.5-62.3 150s23.8 111.6 62.3 150l28.3-28.3C315.3 346.6 296 303.5 296 256s19.3-90.6 50.6-121.7zM705.7 106l-28.3 28.3C708.7 165.4 728 208.5 728 256s-19.3 90.6-50.6 121.7l28.3 28.3c38.5-38.4 62.3-91.5 62.3-150s-23.8-111.6-62.3-150zM815.2 60.8l-0.1-0.1L786.7 89c0.1 0 0.1 0.1 0.2 0.1C831.5 133.7 856 193 856 256s-24.5 122.3-69.1 166.9c0 0-0.1 0.1-0.2 0.1l28.3 28.3 0.1-0.1C867.3 399 896 329.7 896 256s-28.7-143-80.8-195.2zM237.3 89L209 60.7l-0.1 0.1C156.7 113 128 182.3 128 256s28.7 143 80.8 195.2l0.1 0.1 28.3-28.3c-0.1 0-0.1-0.1-0.2-0.1-44.5-44.6-69-103.9-69-166.9s24.5-122.3 69.1-166.9c0.1 0 0.1-0.1 0.2-0.1zM511.712 256.002l0.283-0.283 0.283 0.283-0.283 0.283z" p-id="4281" fill="#ffffff"></path><path d="M511.719 256.021l0.282-0.282 0.283 0.282-0.283 0.283z" p-id="4282" fill="#ffffff"></path><path d="M708.4 616l-14.2-26.1-7.6-13.9-11.6-21.2L512 256 337.5 576l-21.8 40L128 960h74l33.8-64 28.9-53h494.6l28.9 53 33.8 64h74L708.4 616zM512 389.6L613.7 576H410.3L512 389.6zM388.5 616H569L305.6 768.1 388.5 616z m-63.4 187l313.6-181.1L737.5 803H325.1z" p-id="4283" fill="#ffffff"></path></svg>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1729046804258" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1511" width="200" height="200"><path d="M958.359001 286.941198c0-64.308655-52.132334-116.440989-116.440989-116.440989L181.453678 170.500209c-64.308655 0-116.440989 52.132334-116.440989 116.440989l0 448.63995c0 64.308655 52.132334 116.440989 116.440989 116.440989l660.464333 0c64.308655 0 116.440989-52.132334 116.440989-116.440989L958.359001 286.941198zM843.814198 212.455763c12.059664 0 23.444968 3.465938 33.519418 8.598842L530.749016 549.347606c-1.227967 1.161453-1.156336 0.916882-1.752924 1.868557-1.426489 1.067308-6.055926 3.922333-15.449877 3.922333-9.397021 0-14.025435-2.75474-15.4509-3.818979-0.676405-1.077541-0.662079-0.716314-1.995447-1.940189l-353.122503-324.412624c11.609409-7.557116 25.445532-12.509918 40.300868-12.509918L843.814198 212.456786zM916.403446 736.484727c0 40.857547-31.730679 73.582879-72.589248 73.582879L183.27721 810.067606c-40.85857 0-75.28566-32.725332-75.28566-73.582879L107.99155 287.657512c0-10.630105 2.860141-20.735253 6.909363-29.882588l351.486236 322.096882c4.91187 5.302773 19.479657 17.676591 47.009663 17.676591 27.799136 0 42.303478-12.618389 47.076178-17.830087l346.800517-328.899822c6.251378 10.860349 9.129938 23.433712 9.129938 36.838L916.403446 736.484727z" fill="#ffffff" p-id="1512"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,479 +0,0 @@
|
||||
<template>
|
||||
<a-modal
|
||||
v-model:open="visible"
|
||||
:title="t('common.exportCustom')"
|
||||
width="750px"
|
||||
:confirm-loading="confirmLoading"
|
||||
@ok="handleOk"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div class="export-custom-container">
|
||||
<!-- 列配置区域 -->
|
||||
<div class="columns-config">
|
||||
<div class="config-header">
|
||||
<h4>{{ t('common.exportColumns') }}</h4>
|
||||
<a-button type="link" @click="resetToDefault">
|
||||
{{ t('common.resetToDefault') }}
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div class="columns-list">
|
||||
<Container
|
||||
@drop="onDrop"
|
||||
:get-child-payload="getChildPayload"
|
||||
drag-class="drag-ghost"
|
||||
drop-class="drop-ghost"
|
||||
drag-handle-selector=".drag-handle"
|
||||
:non-drag-area-selector="'.column-name, .ant-checkbox-wrapper'"
|
||||
>
|
||||
<Draggable
|
||||
v-for="(column, index) in customColumns"
|
||||
:key="column.key"
|
||||
class="column-item"
|
||||
>
|
||||
<div class="column-controls">
|
||||
<a-checkbox
|
||||
v-model:checked="column.visible"
|
||||
@change="updateColumnVisibility(column)"
|
||||
/>
|
||||
<div class="drag-handle">
|
||||
<HolderOutlined />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column-info">
|
||||
<div class="column-name">
|
||||
<a-input
|
||||
v-model:value="column.title"
|
||||
:placeholder="t('common.columnName')"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<!-- 隐藏col_数字标签,不影响用户使用 -->
|
||||
<!-- <div class="column-key">
|
||||
<a-tag size="small" color="blue">{{ column.key }}</a-tag>
|
||||
</div> -->
|
||||
</div>
|
||||
</Draggable>
|
||||
</Container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预览区域 -->
|
||||
<div class="preview-section">
|
||||
<h4>{{ t('common.preview') }}</h4>
|
||||
<div class="preview-table">
|
||||
<a-table
|
||||
:columns="previewColumns"
|
||||
:data-source="previewData"
|
||||
:pagination="false"
|
||||
size="small"
|
||||
:scroll="{ x: 400 }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { Modal, message } from 'ant-design-vue/es';
|
||||
import { Container, Draggable } from 'vue3-smooth-dnd';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
|
||||
interface CustomColumn {
|
||||
key: string;
|
||||
title: string;
|
||||
visible: boolean;
|
||||
originalTitle: string;
|
||||
dataIndex?: string;
|
||||
customRender?: any;
|
||||
columnIndex?: number; // Excel列索引
|
||||
}
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
originalColumns: ColumnsType;
|
||||
sampleData: any[];
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:open', value: boolean): void;
|
||||
(e: 'confirm', config: CustomColumn[]): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
const { t } = useI18n();
|
||||
|
||||
const visible = ref(false);
|
||||
const confirmLoading = ref(false);
|
||||
const customColumns = ref<CustomColumn[]>([]);
|
||||
|
||||
// 监听props变化
|
||||
watch(() => props.open, (newVal) => {
|
||||
visible.value = newVal;
|
||||
if (newVal) {
|
||||
initCustomColumns();
|
||||
}
|
||||
});
|
||||
|
||||
watch(visible, (newVal) => {
|
||||
emit('update:open', newVal);
|
||||
});
|
||||
|
||||
// 初始化自定义列配置
|
||||
function initCustomColumns() {
|
||||
// 如果传入的是完整的列配置(包含columnIndex),直接使用
|
||||
if (props.originalColumns.length > 0 && (props.originalColumns[0] as any).columnIndex !== undefined) {
|
||||
const columns: CustomColumn[] = props.originalColumns.map(col => ({
|
||||
key: (col as any).key,
|
||||
title: (col as any).title,
|
||||
visible: true,
|
||||
originalTitle: (col as any).originalTitle || (col as any).title,
|
||||
dataIndex: (col as any).dataIndex,
|
||||
columnIndex: (col as any).columnIndex
|
||||
}));
|
||||
|
||||
// 尝试从本地存储加载配置
|
||||
const savedConfig = localStorage.getItem('sgwc-cdr-export-config');
|
||||
if (savedConfig) {
|
||||
try {
|
||||
const saved = JSON.parse(savedConfig);
|
||||
console.log('Loaded saved config:', saved);
|
||||
|
||||
// 基于originalTitle匹配保存的配置
|
||||
const mergedColumns = columns.map(col => {
|
||||
const savedCol = saved.find((s: any) => s.originalTitle === col.originalTitle);
|
||||
if (savedCol) {
|
||||
console.log(`Matched column: ${col.originalTitle}, visible: ${savedCol.visible}, title: ${savedCol.title}`);
|
||||
return {
|
||||
...col,
|
||||
visible: savedCol.visible !== undefined ? savedCol.visible : true,
|
||||
title: savedCol.title || col.title
|
||||
};
|
||||
}
|
||||
return col;
|
||||
});
|
||||
|
||||
// 按照保存的顺序排列(如果存在)
|
||||
const sortedColumns = sortColumnsByConfig(mergedColumns, saved);
|
||||
console.log('Final columns after merge and sort:', sortedColumns);
|
||||
customColumns.value = sortedColumns;
|
||||
} catch (error) {
|
||||
console.error('Failed to load saved export config:', error);
|
||||
customColumns.value = columns;
|
||||
}
|
||||
} else {
|
||||
console.log('No saved config found, using default columns');
|
||||
customColumns.value = columns;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 兼容旧的表格列配置
|
||||
const columns: CustomColumn[] = props.originalColumns
|
||||
.filter(col => col.key !== 'id' && (col as any).dataIndex) // 过滤掉操作列
|
||||
.map(col => ({
|
||||
key: col.key as string,
|
||||
title: col.title as string,
|
||||
visible: true,
|
||||
originalTitle: col.title as string,
|
||||
dataIndex: (col as any).dataIndex as string,
|
||||
customRender: (col as any).customRender
|
||||
}));
|
||||
|
||||
// 尝试从本地存储加载配置
|
||||
const savedConfig = localStorage.getItem('sgwc-cdr-export-config');
|
||||
if (savedConfig) {
|
||||
try {
|
||||
const saved = JSON.parse(savedConfig);
|
||||
// 合并保存的配置和当前列,只合并特定字段
|
||||
const mergedColumns = columns.map(col => {
|
||||
const savedCol = saved.find((s: CustomColumn) => s.key === col.key);
|
||||
if (savedCol) {
|
||||
return {
|
||||
...col,
|
||||
visible: savedCol.visible, // 只合并可见性
|
||||
title: savedCol.title !== savedCol.originalTitle ? savedCol.title : col.title // 只合并自定义的标题
|
||||
};
|
||||
}
|
||||
return col;
|
||||
});
|
||||
customColumns.value = mergedColumns;
|
||||
} catch (error) {
|
||||
console.error('Failed to load saved export config:', error);
|
||||
customColumns.value = columns;
|
||||
}
|
||||
} else {
|
||||
customColumns.value = columns;
|
||||
}
|
||||
}
|
||||
|
||||
// 按照保存的配置排序列
|
||||
function sortColumnsByConfig(columns: CustomColumn[], savedConfig: CustomColumn[]): CustomColumn[] {
|
||||
if (!savedConfig || savedConfig.length === 0) {
|
||||
return columns;
|
||||
}
|
||||
|
||||
// 创建一个映射:originalTitle -> savedOrder
|
||||
const orderMap = new Map<string, number>();
|
||||
savedConfig.forEach((col, index) => {
|
||||
orderMap.set(col.originalTitle, index);
|
||||
});
|
||||
|
||||
// 排序:已保存的列按保存顺序,新列放在最后
|
||||
return [...columns].sort((a, b) => {
|
||||
const orderA = orderMap.has(a.originalTitle) ? orderMap.get(a.originalTitle)! : 9999;
|
||||
const orderB = orderMap.has(b.originalTitle) ? orderMap.get(b.originalTitle)! : 9999;
|
||||
return orderA - orderB;
|
||||
});
|
||||
}
|
||||
|
||||
// 预览列配置
|
||||
const previewColumns = computed(() => {
|
||||
return customColumns.value
|
||||
.filter(col => col.visible)
|
||||
.map(col => ({
|
||||
title: col.title,
|
||||
dataIndex: col.dataIndex || col.key,
|
||||
key: col.key,
|
||||
customRender: col.customRender
|
||||
}));
|
||||
});
|
||||
|
||||
// 预览数据(只显示前2条,降低预览高度)
|
||||
const previewData = computed(() => {
|
||||
return props.sampleData.slice(0, 1);
|
||||
});
|
||||
|
||||
// 更新列可见性
|
||||
function updateColumnVisibility(column: CustomColumn) {
|
||||
// 确保至少有一列可见
|
||||
const visibleCount = customColumns.value.filter(col => col.visible).length;
|
||||
if (visibleCount === 0) {
|
||||
column.visible = true;
|
||||
message.warning(t('common.atLeastOneColumn'));
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽相关函数
|
||||
function getChildPayload(index: number) {
|
||||
return customColumns.value[index];
|
||||
}
|
||||
|
||||
function onDrop(dropResult: any) {
|
||||
const { removedIndex, addedIndex } = dropResult;
|
||||
if (removedIndex !== null && addedIndex !== null) {
|
||||
const item = customColumns.value[removedIndex];
|
||||
customColumns.value.splice(removedIndex, 1);
|
||||
customColumns.value.splice(addedIndex, 0, item);
|
||||
}
|
||||
}
|
||||
|
||||
// 重置为默认配置
|
||||
function resetToDefault() {
|
||||
Modal.confirm({
|
||||
title: t('common.confirm'),
|
||||
content: t('common.resetConfirm'),
|
||||
onOk() {
|
||||
// 清除本地存储的配置
|
||||
localStorage.removeItem('sgwc-cdr-export-config');
|
||||
// 重新初始化
|
||||
initCustomColumns();
|
||||
message.success(t('common.resetSuccess'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 确认导出
|
||||
function handleOk() {
|
||||
const visibleColumns = customColumns.value.filter(col => col.visible);
|
||||
if (visibleColumns.length === 0) {
|
||||
message.error(t('common.selectAtLeastOneColumn'));
|
||||
return;
|
||||
}
|
||||
|
||||
confirmLoading.value = true;
|
||||
|
||||
// 保存配置到本地存储
|
||||
try {
|
||||
// 保存时只保存必要的字段,用于下次加载时恢复配置
|
||||
const configToSave = customColumns.value.map((col, index) => ({
|
||||
originalTitle: col.originalTitle || col.title, // 用于匹配列
|
||||
title: col.title, // 自定义标题
|
||||
visible: col.visible, // 可见性
|
||||
order: index // 顺序
|
||||
}));
|
||||
localStorage.setItem('sgwc-cdr-export-config', JSON.stringify(configToSave));
|
||||
console.log('Export config saved:', configToSave);
|
||||
} catch (error) {
|
||||
console.error('Failed to save export config:', error);
|
||||
}
|
||||
|
||||
// 延迟一下让用户看到loading状态
|
||||
setTimeout(() => {
|
||||
emit('confirm', customColumns.value);
|
||||
confirmLoading.value = false;
|
||||
visible.value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// 取消
|
||||
function handleCancel() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.open) {
|
||||
initCustomColumns();
|
||||
}
|
||||
});
|
||||
|
||||
// 调试功能:清除有问题的缓存
|
||||
function clearProblematicCache() {
|
||||
localStorage.removeItem('sgwc-cdr-export-config');
|
||||
console.log('Cleared problematic cache');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.export-custom-container {
|
||||
.columns-config {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.config-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.columns-list {
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
|
||||
.column-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 10px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.column-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 12px;
|
||||
|
||||
.drag-handle {
|
||||
margin-left: 8px;
|
||||
cursor: grab;
|
||||
color: #999;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
color: #1890ff;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
color: #0050b3;
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
pointer-events: auto;
|
||||
cursor: default;
|
||||
|
||||
.column-name {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.column-key {
|
||||
display: none; // 隐藏标签
|
||||
}
|
||||
}
|
||||
|
||||
// 禁止复选框触发拖拽
|
||||
:deep(.ant-checkbox-wrapper) {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-section {
|
||||
h4 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.preview-table {
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
max-height: 180px;
|
||||
|
||||
:deep(.ant-table-wrapper) {
|
||||
.ant-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ant-table-thead > tr > th {
|
||||
padding: 8px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr > td {
|
||||
padding: 6px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.ant-table-body {
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽样式
|
||||
.drag-ghost {
|
||||
opacity: 0.5;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.drop-ghost {
|
||||
background: #e6f7ff;
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,3 @@ export const ADMIN_ROLE_KEY = 'system';
|
||||
|
||||
/**系统管理员-系统指定权限 */
|
||||
export const ADMIN_PERMISSION = '*:*:*';
|
||||
|
||||
/**次级系统管理员 */
|
||||
export const TENANTADMIN_ROLE_KEY = 'admin';
|
||||
|
||||
@@ -1,27 +1,10 @@
|
||||
/**网元列表,默认顺序 */
|
||||
export const NE_TYPE_LIST = [
|
||||
'OMC',
|
||||
'IMS',
|
||||
'AMF',
|
||||
'AUSF',
|
||||
'UDR',
|
||||
'UDM',
|
||||
'SMF',
|
||||
'PCF',
|
||||
'NSSF',
|
||||
'NRF',
|
||||
'UPF',
|
||||
'LMF',
|
||||
'NEF',
|
||||
'MME',
|
||||
'N3IWF',
|
||||
'MOCNGW',
|
||||
'SMSC',
|
||||
'SMSF',
|
||||
'CBC',
|
||||
'CHF',
|
||||
'HLR',
|
||||
'SGWC',
|
||||
'MF',
|
||||
'IMS',
|
||||
'SMSC',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
/**日期快捷选择 */
|
||||
export function dayjsRanges() {
|
||||
return [
|
||||
{
|
||||
label: 'Today',
|
||||
value: [dayjs().startOf('day'), dayjs()],
|
||||
},
|
||||
{
|
||||
label: 'Last 1 hour',
|
||||
value: [
|
||||
dayjs().subtract(1, 'hour').startOf('hour'),
|
||||
dayjs().subtract(1, 'hour').endOf('hour'),
|
||||
],
|
||||
},
|
||||
// {
|
||||
// label: 'Last 3 hour',
|
||||
// value: [dayjs().subtract(3, 'hours'), dayjs()],
|
||||
// },
|
||||
// {
|
||||
// label: 'Last 6 hour',
|
||||
// value: [dayjs().subtract(6, 'hours'), dayjs()],
|
||||
// },
|
||||
{
|
||||
label: 'Last 1 day',
|
||||
value: [
|
||||
dayjs().subtract(1, 'day').startOf('day'),
|
||||
dayjs().subtract(1, 'day').endOf('day'),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Last 7 day',
|
||||
value: [
|
||||
dayjs().subtract(7, 'day').startOf('day'),
|
||||
dayjs().subtract(1, 'day').endOf('day'),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Last 15 day',
|
||||
value: [
|
||||
dayjs().subtract(15, 'day').startOf('day'),
|
||||
dayjs().subtract(1, 'day').endOf('day'),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -10,14 +10,13 @@ export default {
|
||||
desc: 'Core Network Management Platform',
|
||||
loading: 'Please wait...',
|
||||
inputPlease: 'Please input',
|
||||
searchPlease: 'Search menus...',
|
||||
searchTip: 'Enter keywords to search menus',
|
||||
selectPlease: 'please select',
|
||||
tipTitle: 'Prompt',
|
||||
msgSuccess: 'Success {msg}',
|
||||
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',
|
||||
@@ -30,7 +29,6 @@ export default {
|
||||
editText: 'Edit',
|
||||
deleteText: 'Delete',
|
||||
downloadText: 'Download',
|
||||
CloudServerText:'Sync',
|
||||
import:'Import',
|
||||
export:'Export',
|
||||
uploadText: 'Upload',
|
||||
@@ -41,11 +39,6 @@ export default {
|
||||
columnSetText: 'Column Setting',
|
||||
columnSetTitle: 'Column Display / Sorting',
|
||||
sizeText: 'Density',
|
||||
preview:'Preview',
|
||||
exportCustom:'Custom Export',
|
||||
exportColumns:'Column Definition',
|
||||
resetToDefault:'Reset to default columns',
|
||||
exportDefault:'Export',
|
||||
size: {
|
||||
default: 'Default',
|
||||
middle: 'Medium',
|
||||
@@ -138,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...',
|
||||
@@ -150,7 +143,7 @@ export default {
|
||||
|
||||
// 静态路由
|
||||
router: {
|
||||
index: "Overview",
|
||||
index: "Home",
|
||||
login: "Sign In",
|
||||
register: 'Registrations',
|
||||
page403: 'No Access',
|
||||
@@ -261,16 +254,12 @@ export default {
|
||||
login: {
|
||||
tabPane1: 'Account password login',
|
||||
tabPane2: 'Login with phone number',
|
||||
registerBtn: 'Register Account',
|
||||
registerBtn: 'Register an account',
|
||||
loginBtn: 'Sign In',
|
||||
loginSuccess: 'Login Successful',
|
||||
otherMethod: 'Other Methods',
|
||||
backBtn: 'Back',
|
||||
backBtnLogin: 'Return Login',
|
||||
authorizedNotfound: 'Authorized Not Found',
|
||||
authorizedFailed: 'Authorized Failed',
|
||||
authorizedSuccess: 'Authorized Successful',
|
||||
redirectHome: 'Redirect to home page in {i} seconds',
|
||||
loginMethod: 'Other login methods:',
|
||||
loginMethodWX: 'WeChat Scan Login',
|
||||
loginMethodQQ: 'QQ Scan Code Login',
|
||||
},
|
||||
register: {
|
||||
registerBtn: 'Register',
|
||||
@@ -361,39 +350,99 @@ export default {
|
||||
description: "No data yet, try refreshing",
|
||||
},
|
||||
},
|
||||
agentManage:{
|
||||
callings:{
|
||||
callerIdNumber:'Caller Number',
|
||||
calleeIdNumber:'Callee Number',
|
||||
startTime:'Start Time',
|
||||
answeredTime:'Answered Time(s)',
|
||||
callDuration:'Call Duration',
|
||||
msdData:'MSD Info',
|
||||
},
|
||||
callback:{
|
||||
callerIdNumber:'Caller Number',
|
||||
calleeIdNumber:'Callee Number',
|
||||
status:'Status',
|
||||
ticketId:'Ticket ID',
|
||||
startTime:'Created Time',
|
||||
updateTime:'Update Time',
|
||||
msdData:'MSD Info',
|
||||
agentName: 'Agent Name',
|
||||
comment: 'Comment',
|
||||
agentEmail: 'Agent Email',
|
||||
agentMobile: 'Agent Mobile',
|
||||
title: ' Ticket List',
|
||||
}
|
||||
},
|
||||
cbc:{
|
||||
cbe:{
|
||||
neType:'CBC Object',
|
||||
title: ' CBC Event',
|
||||
delTip: 'Confirm deletion of the event data item numbered [{num}]?',
|
||||
eventName: 'Event Name',
|
||||
startTime: 'Start Time',
|
||||
endTime: 'End Time',
|
||||
repetitionPeriod: 'Repetition Period',
|
||||
numOfBcast: 'Number of Broadcasts',
|
||||
msgPWSType: 'Message Type',
|
||||
messageId: 'Message ID',
|
||||
displayMode: 'Display Mode',
|
||||
geoScope:' Geographic Scope',
|
||||
emergencyUserAlert: 'Emergency User Alert',
|
||||
activatePopup: 'Activate Popup',
|
||||
warningType: 'Warning Type',
|
||||
language:' Language',
|
||||
warningMessageText:' Broadcast Content',
|
||||
status: 'Status',
|
||||
warningAreaType: 'Warning Area Type',
|
||||
taiListTip:'TAI List cannot be empty',
|
||||
taiSonTip:'TAI List each item MCC, MNC, TAC cannot be empty',
|
||||
eutraListTip:'EUTRA CellId List cannot be empty',
|
||||
eutraSonTip:'EUTRA CellId List each item MCC, MNC, CellId cannot be empty',
|
||||
nrTip:'NR CellId List cannot be empty',
|
||||
nrSonTip:'NR CellId List each item MCC, MNC, CellId cannot be empty',
|
||||
areaTip:'Area ID List cannot be empty',
|
||||
areaSonTip:'Area ID List each item AreaID cannot be empty',
|
||||
messageIdProfile:'Message ID Profile',
|
||||
serialNumProfile:'Serial Num Profile',
|
||||
warningTypeProfile:'Warning Type Profile',
|
||||
warningMessageProfile:'Warning Message Profile',
|
||||
etws:'Earthquake and Tsunami Warning System',
|
||||
cmas:'Commercial Mobile Alert System',
|
||||
createdAt:'Create Time',
|
||||
eventNameHelp:'If it is CMAS, the recommended prefix is cmas_xxx. If it is ETWS, the recommended prefix is etws_xxx',
|
||||
repetitionPeriodHelp:'Unit is seconds',
|
||||
numOfBcastHelp:'Unit is seconds',
|
||||
cellListTip:'CellId List cannot be empty',
|
||||
cellListSonTip:'MCC、MNC、 Cannot be empty, and at least one EUTRA CellId/NR CellId must be filled in',
|
||||
letupSure:'Do you confirm the status of the broadcast event with the modification number 【{id}】?',
|
||||
tacHelp:'The TAC value is a decimal string, separated by ";" for multiple TAC values.',
|
||||
cellIdHelp:'The CellId value is a hexadecimal string, separated by ";" for multiple CellId values.',
|
||||
areaId:'The areaId value is a hexadecimal string.',
|
||||
detail:'Detail',
|
||||
}
|
||||
},
|
||||
dashboard: {
|
||||
overview:{
|
||||
title: "Core Network Dashboard",
|
||||
fullscreen: "Full Screen Display",
|
||||
fullscreen: "Click on the full-screen display",
|
||||
toRouter: "Click to jump to the detail page",
|
||||
Users:"UDM Subscription Data",
|
||||
VoNR:"IMS Online Users",
|
||||
sessions:"PDU Sessions in SMF",
|
||||
FivegNodeN:"Online gNB in AMF",
|
||||
Fiveusers:"Active users in AMF",
|
||||
FourgNodeN:"Online eNB in MME",
|
||||
Fourusers:"Active users in MME",
|
||||
UPFjump:"Key Performance Indicators",
|
||||
Networkjump:"5GC System Architecture",
|
||||
Alarmjump:"Active Alarms",
|
||||
IMSUsers:"UDM IMS User Data",
|
||||
VoIPUsers:"UDM VoIP User Data",
|
||||
psapTitle:'PSAP Dashboard',
|
||||
onlineUser:'Online User',
|
||||
totalUser:'Total User',
|
||||
parallelUser:'Parallel User',
|
||||
userTitle:'User Statistics',
|
||||
sysTitle:'System Resources',
|
||||
skim: {
|
||||
users: "Total Subscriber Base",
|
||||
userTitle:'Subscriber Information',
|
||||
users: "Users",
|
||||
userTitle:'User Information',
|
||||
imsUeNum: "VoNR/VoLTE",
|
||||
smfUeNum: "User Sessions",
|
||||
smfUeNum: "Data Sessions",
|
||||
gnbBase: "Online gNodeB",
|
||||
gnbSumBase: "Total gNodeB",
|
||||
enbBase: "Online eNodeB",
|
||||
enbSumBase: "Total eNodeB",
|
||||
gnbUeNum:'5G Active Users',
|
||||
enbUeNum:'4G Active Users',
|
||||
baseTitle:'Online Information',
|
||||
nodeBInfo: 'NodeB Information',
|
||||
onlineinfo:'UE Online Information',
|
||||
ims:'VoLTE',
|
||||
voip:'VoIP'
|
||||
},
|
||||
upfFlow:{
|
||||
title: "UPF Throughput",
|
||||
@@ -423,7 +472,6 @@ export default {
|
||||
},
|
||||
userActivity: {
|
||||
title: "User Activity",
|
||||
imsTitle: "IMS Activity",
|
||||
type: "Type",
|
||||
duration: "Duration",
|
||||
caller: "Caller",
|
||||
@@ -442,20 +490,15 @@ export default {
|
||||
rowInfo: "Info",
|
||||
type: "Type",
|
||||
duration: "Duration",
|
||||
mosAverage: "MOS",
|
||||
callConnectionTime: "Call Connection Time",
|
||||
seizureTime: "Call Start Time",
|
||||
releaseTime: "Hangup Time",
|
||||
caller: "Caller",
|
||||
called: "Called",
|
||||
result: "Result",
|
||||
resultCode: "Result Code",
|
||||
resultCause: "Result Cause",
|
||||
resultOk: "Success",
|
||||
resultFail: "Fail",
|
||||
delTip: "Confirm deletion of the data item numbered [{msg}]?",
|
||||
tenantName: "Tenant Name",
|
||||
exportTip: "Do you confirm to export the current query conditions of the CDR data?",
|
||||
exportTip: "Do you confirm to export the current query conditions of the CDR data? (Maximum 10,000 items can be exported.)",
|
||||
chargingID: 'Charging ID',
|
||||
smfSubscriptionIDData: 'Subscription ID Data',
|
||||
smfSubscriptionIDType: 'Subscription ID Type',
|
||||
@@ -468,6 +511,35 @@ export default {
|
||||
sgwcServedMSISDN: 'MSISDN',
|
||||
sgwcVolumeGPRSUplink: 'GPRS Uplink',
|
||||
sgwcVolumeGPRSDownlink: 'GPRS Downlink',
|
||||
recordPath:'Record Path',
|
||||
msd:' MSD',
|
||||
automaticActivation: 'Automatic Activation',
|
||||
positionCanBeTrusted: 'Position Can Be Trusted',
|
||||
testCall: 'Test Call',
|
||||
vehicleType:'Vehicle Type',
|
||||
messageIdentifier: 'Message Identifier',
|
||||
numberOfOccupants: 'Number Of Occupants',
|
||||
n1latitudeDelta:'Latitude Delta',
|
||||
n1longitudeDelta: 'Longitude Delta',
|
||||
n2latitudeDelta:'Latitude Delta',
|
||||
n2longitudeDelta: 'Longitude Delta',
|
||||
timestamp: 'Timestamp',
|
||||
vehicleDirection: 'Vehicle Direction',
|
||||
isovds: 'Vehicle Descriptor Section',
|
||||
isovisModelyear:'Vehicle Identifier Section Year',
|
||||
isovisSeqPlant: 'Vehicle Identifier Section Plant & Serial',
|
||||
isowmi: 'World Manufacturer Identifier',
|
||||
positionLatitude: 'Position Latitude',
|
||||
positionLongitude: 'Position Longitude',
|
||||
dieselTankPresent: 'Diesel Tank Present',
|
||||
electricEnergyStorage: 'Electric Energy Storage',
|
||||
gasolineTankPresent: 'Gasoline Tank Present',
|
||||
control:'Control',
|
||||
recentVehicleLocationN1:'Recent Vehicle Location N1',
|
||||
recentVehicleLocationN2:'Recent Vehicle Location N2',
|
||||
vehicleIdentificationNumber:'Vehicle Identification Number',
|
||||
vehicleLocation:'Vehicle Location',
|
||||
vehiclePropulsionStorageType:'Vehicle Propulsion Storage Type',
|
||||
},
|
||||
ue: {
|
||||
eventType: "Event Type",
|
||||
@@ -479,7 +551,7 @@ export default {
|
||||
result: "Result",
|
||||
resultOk: "Successes",
|
||||
delTip: "Confirm deletion of the data item numbered [{msg}]?",
|
||||
exportTip: "Do you confirm to export the event data of the current query condition?",
|
||||
exportTip: "Do you confirm to export the event data of the current query condition? (Maximum 10,000 items can be exported.)",
|
||||
},
|
||||
},
|
||||
ne: {
|
||||
@@ -502,8 +574,6 @@ export default {
|
||||
portTip: "Network element port default:33030",
|
||||
serialNum: 'Serial Number',
|
||||
expiryDate: 'Expiry Date',
|
||||
ueNumber: 'UE Number',
|
||||
nbNumber: 'Radio Number',
|
||||
normalcy: 'Normal',
|
||||
exceptions: 'Abnormal',
|
||||
restart: 'Restart',
|
||||
@@ -541,7 +611,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',
|
||||
@@ -651,8 +721,8 @@ export default {
|
||||
change: "Change License",
|
||||
reload: "Refresh Info",
|
||||
reloadTip: "Confirmed to refresh license information?",
|
||||
reloadBatch: "Refresh License Status",
|
||||
reloadBatchTip: "Do you perform a license status information refresh for the current list of NE?",
|
||||
reloadBatch: "Batch Refresh",
|
||||
reloadBatchTip: "Do you do an information refresh on checked records?",
|
||||
updateTtile: "Update License",
|
||||
downCodeTop: "Confirmed to save the license activation code to a file?",
|
||||
activationRequestCode: "License Activation Code",
|
||||
@@ -694,19 +764,6 @@ export default {
|
||||
name: "Name",
|
||||
downTip: 'Confirmed to download the backup file [{txt}]?',
|
||||
title: "Modify Backup {txt}",
|
||||
backupModal: {
|
||||
pushFileOper: "Send Current File To Remote Backup",
|
||||
title: "Setting Remote Backup Service",
|
||||
enable: "Enable",
|
||||
toIp: "Service IP",
|
||||
toIpPleace: "Please input the remote backup server IP address",
|
||||
toPort: "Service Port",
|
||||
username: "UserName",
|
||||
usernamePleace: 'Please enter the service login username',
|
||||
password: "Password",
|
||||
dir: "Save Dir",
|
||||
dirPleace: 'Please enter the service address target file directory',
|
||||
}
|
||||
},
|
||||
neQuickSetup: {
|
||||
reloadPara5G: 'Reload',
|
||||
@@ -758,32 +815,10 @@ export default {
|
||||
},
|
||||
},
|
||||
neData: {
|
||||
common: {
|
||||
startIMSI: 'Starting IMSI',
|
||||
imsi: 'IMSI',
|
||||
imsiTip: 'IMSI=MCC+MNC+MSIN',
|
||||
imsiTip1: 'MCC=Mobile Country Code, consisting of three digits.',
|
||||
imsiTip2: 'MNC = Mobile Network Number, consisting of two digits',
|
||||
imsiTip3: 'MSIN = Mobile Subscriber Identification Number, consisting of 10 equal digits.',
|
||||
imsiPlease: "Please enter IMSI correctly",
|
||||
msisdn: 'Mobile Customer Identification Number',
|
||||
msisdnPlease: "Please enter the Mobile Customer Identification Number correctly",
|
||||
loadDataConfirm: 'Confirmed to reload data?',
|
||||
loadData: 'Load Data',
|
||||
loadDataTip: 'Successfully fetched loaded data: {num} items, the system is internally updating the data, it will take about {timer} seconds, please wait!!!!!.',
|
||||
batchOper: 'Batch Operation',
|
||||
batchAddText: 'Batch Addition',
|
||||
batchDelText: 'Batch Deletion',
|
||||
batchUpdateText: 'Batch Update',
|
||||
batchNum: 'Number of releases',
|
||||
checkDel:'Check Delete',
|
||||
importTemplate: 'Download Template',
|
||||
},
|
||||
baseStation: {
|
||||
list: "List",
|
||||
topology: "Topology",
|
||||
nbName: "RAN Node Name",
|
||||
nbId: "RAN Node ID",
|
||||
nbName: "RanNodeName",
|
||||
ueNum: "UE Number",
|
||||
topologyTitle: "Radio State Graph",
|
||||
name: "Name",
|
||||
@@ -802,16 +837,10 @@ export default {
|
||||
exportTip: "Confirm exporting xlsx table files based on search criteria?",
|
||||
importDataEmpty: "Imported data is empty",
|
||||
},
|
||||
backupData: {
|
||||
auth: "UDM Authentication",
|
||||
sub: "UDM Subscribers",
|
||||
voip: "VoIP Authentication",
|
||||
volte: "IMS Subscribers",
|
||||
}
|
||||
},
|
||||
neUser: {
|
||||
auth: {
|
||||
authInfo:' Authentication of Info',
|
||||
authInfo:' Authentication Info',
|
||||
neTypePlease: 'Query network element Object',
|
||||
neType: 'UDM Object',
|
||||
export: 'Export',
|
||||
@@ -826,7 +855,7 @@ export default {
|
||||
startIMSI: 'Start IMSI',
|
||||
batchAddText: 'Batch Add',
|
||||
batchDelText: 'Batch Delete',
|
||||
numAdd: 'Number of authentication profiles',
|
||||
numAdd: 'Number of releases',
|
||||
numDel: 'Number of deleted',
|
||||
checkDel: 'Check Delete',
|
||||
imsiTip: 'IMSI=MCC+MNC+MSIN',
|
||||
@@ -839,12 +868,10 @@ export default {
|
||||
opcTip: 'The authentication key, OPC, is calculated from Ki and OP, OP is the root key of the operator, ki is the authentication key, and the maximum length is 32.',
|
||||
delSure:'Are you sure you want to delete the user with IMSI number: {imsi} ?',
|
||||
imsiConfirm:'The length of the IMSI must be 15',
|
||||
startUserName:'Start username',
|
||||
},
|
||||
sub: {
|
||||
subInfo:' Subscription Info',
|
||||
neType: 'UDM Object',
|
||||
imsType:'IMS Object',
|
||||
export: 'Export',
|
||||
exportConfirm: 'Are you sure to export all signed user data?',
|
||||
checkExport : 'Check Export',
|
||||
@@ -854,7 +881,7 @@ export default {
|
||||
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!!!!!.',
|
||||
numAdd: 'Number of subscriber profiles',
|
||||
numAdd: 'Number of releases',
|
||||
numDel: 'Number of deleted',
|
||||
checkDel: 'Check Delete',
|
||||
batchAddText: 'Batch Add',
|
||||
@@ -874,10 +901,6 @@ export default {
|
||||
rfspTip:'RFSP index, in NG-RAN, the index of a specific RRM configuration, parameter between 0 and 127',
|
||||
ueTypeTip: 'Operator-defined subscriber UE Usage Type, integer, parameter between 0 and 127',
|
||||
cnFlag: 'Whether to enable 5G Core Network service',
|
||||
cnFlag0: 'No Access Allowed',
|
||||
cnFlag1: 'Access Only 5G',
|
||||
cnFlag2: 'Access Only 4G',
|
||||
cnFlag3: 'Access 4G/5G',
|
||||
epsFlagTip: 'Whether to enable 4G EPS service',
|
||||
contextIdTip: 'To sign up for an APN Context ID, you must select it from the APN Context list.',
|
||||
apnContextTip: 'The list of APNs available to the phone, up to six, is defined in the HSS.',
|
||||
@@ -887,12 +910,6 @@ export default {
|
||||
ardTip:'Access-Restriction-Data (Access-Restriction-Data), can be used to distinguish between 2G/3G/LTE users, to facilitate the coexistence of 2G/3G/LTE network for different types of users to distinguish between the service',
|
||||
smDataTip:'The IP in sm_data=1-000001&internet-1.2.3.4&ims-1.2.3.5: 1.2.3.4 is the static IP assigned to the APN of 5G user internet, and 1.2.3.5 is the static IP assigned to the APN of 5G user ims. If it is dynamic allocation, just remove the IP and the previous connector. Need to support multiple dnn uses & connections',
|
||||
smDataArrTip:'SST,DNN/APN is required',
|
||||
tenantName:'Tenant Name',
|
||||
imsiMode:'IMSI Matching Mode',
|
||||
fuzzyMatch:'Fuzzy Match',
|
||||
prefixMatch:'Prefix Match',
|
||||
fullMatch:'Full Match',
|
||||
suffixMatch:'Suffix Match',
|
||||
},
|
||||
pcf: {
|
||||
neType: 'PCF Object',
|
||||
@@ -926,9 +943,7 @@ export default {
|
||||
rfsfTip:'RAT Frequency Selection Priority',
|
||||
},
|
||||
base5G: {
|
||||
neType: 'Radio Type',
|
||||
gnb:'5G Radios',
|
||||
enb:'4G Radios',
|
||||
neType: 'NE Object',
|
||||
},
|
||||
n3iwf: {
|
||||
neType: 'N3IWF Object',
|
||||
@@ -1040,13 +1055,8 @@ export default {
|
||||
expression:'Expression',
|
||||
description:' Description',
|
||||
kpiSet:' Statistical Settings',
|
||||
ago1Hour:'Last 1 hour',
|
||||
ago3Hour:'Last 3 hour',
|
||||
ago6Hour:'Last 6 hour',
|
||||
toDay:'Today',
|
||||
ago1Day:'Last 1 day',
|
||||
ago7Day:'Last 7 day',
|
||||
ago15Day:'Last 15 day',
|
||||
sixHoursAgo:'Six hours ago',
|
||||
threeHoursAgo:'Three hours ago.',
|
||||
delCustomTip:'Confirm deletion of data item with record Custom Indicator {num}?',
|
||||
delCustom:' Successfully delete record number {num} custom indicator',
|
||||
addCustom:' Add custom indicator',
|
||||
@@ -1063,11 +1073,6 @@ export default {
|
||||
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:',
|
||||
avg:'(average)',
|
||||
total:'(total)',
|
||||
ago1:'Past 24 hrs value',
|
||||
ago7:'Past 7 days value',
|
||||
ago30:'Past 30 days value',
|
||||
},
|
||||
kpiKeyTarget:{
|
||||
"time":"Time",
|
||||
@@ -1099,25 +1104,28 @@ export default {
|
||||
"changeBar":"Change to Bar Charts",
|
||||
"chooseShowMetrics":"Select the metric you want to display",
|
||||
"chooseMetrics":"Select an indicator",
|
||||
"tips":"The percentages and rates are averages,the counts are statistical values.",
|
||||
},
|
||||
voiceOverView:{
|
||||
"voiceTitle":"Voice Calls Dashboard",
|
||||
"tips":"Voice Call Statistics Per Minute",
|
||||
"ne":"NE",
|
||||
"now":"Now",
|
||||
"last":"Past",
|
||||
"calls":"Calls",
|
||||
"activeCall":"Active Calls",
|
||||
"callMOMT":"MO/MT Call Success Rate",
|
||||
"failedcall":"Failed Calls",
|
||||
"registration":"Registrations",
|
||||
"activeregistration":"Active Registrations",
|
||||
"registrationsuccess":"Registration Success Rate",
|
||||
"failedregistration":"Failed Registrations",
|
||||
},
|
||||
},
|
||||
traceManage: {
|
||||
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',
|
||||
@@ -1152,7 +1160,6 @@ export default {
|
||||
},
|
||||
task: {
|
||||
traceId: 'Tracing No',
|
||||
title: 'Tracing Title',
|
||||
trackType: 'Tracing Type',
|
||||
trackTypePlease: 'Please select a tracing type',
|
||||
creater: 'Created by',
|
||||
@@ -1169,35 +1176,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: {
|
||||
@@ -1217,7 +1219,7 @@ export default {
|
||||
clear: 'Clear',
|
||||
mySelf: 'Personalization',
|
||||
exportAll: 'Export All',
|
||||
disPlayFilfter: 'Filter out Alerts',
|
||||
disPlayFilfter: 'Display Filters',
|
||||
alarmId:'ID',
|
||||
alarmTitle:'Title',
|
||||
clearUser:'Clear User',
|
||||
@@ -1318,19 +1320,12 @@ export default {
|
||||
tailLines: 'End Lines',
|
||||
},
|
||||
exportFile:{
|
||||
fileSource:'File Source',
|
||||
fileSourcePlease:'Please select the source of the document',
|
||||
fileName:'File Source',
|
||||
downTip: "Confirm the download file name is [{fileName}] File?",
|
||||
downTipErr: "Failed to get file",
|
||||
deleteTip: "Confirm the delete file name is [{fileName}] File?",
|
||||
deleteTipErr: "Failed to delete file",
|
||||
sysloginLog:'System Login Log',
|
||||
sysOperateLog:'System Operation Log',
|
||||
neLog:'NE Log',
|
||||
cdrIMS:'CDR Voice',
|
||||
cdrSMF:'CDR Data',
|
||||
cdrSMSC:'CDR SMS',
|
||||
cdrSGWC:'CDR Roaming Data',
|
||||
selectTip:"Please select File Name",
|
||||
}
|
||||
},
|
||||
monitor: {
|
||||
@@ -1704,8 +1699,6 @@ export default {
|
||||
userName: 'Nick Name',
|
||||
permission: 'Role',
|
||||
className: 'Department',
|
||||
userType: 'User Type',
|
||||
tenntName:'Tenant Name',
|
||||
loginIp: 'Login Address',
|
||||
loginTime: 'Login Time',
|
||||
status: 'Status',
|
||||
@@ -1731,7 +1724,6 @@ export default {
|
||||
sex:'User Gender',
|
||||
email:'E-mail',
|
||||
fromClass:'Department',
|
||||
fromTenant:'Tenant',
|
||||
userWork:'User position',
|
||||
userWorkPlease: 'Please select user post',
|
||||
userTip:'User Description',
|
||||
@@ -1745,7 +1737,6 @@ export default {
|
||||
lock:'Lock',
|
||||
inactive:'Inactive',
|
||||
active:'Active',
|
||||
permissionTip:'If want to select a tenant role, make sure there is at least one tenant in the tenant management.',
|
||||
},
|
||||
config: {
|
||||
configName: "Config Name",
|
||||
@@ -1770,55 +1761,6 @@ export default {
|
||||
refreshCacheTip: "Are you sure you want to refresh the parameter configuration cache?",
|
||||
refreshCacheOk: "Refresh Cache Successful",
|
||||
},
|
||||
loginSource: {
|
||||
uid: "UID",
|
||||
name: "Name",
|
||||
namePlease: 'Please enter the authentication source name correctly',
|
||||
icon: "Icon",
|
||||
iconPlease: 'You can enter the image link or upload the image path address',
|
||||
type: "Type",
|
||||
activeFlag: "Status",
|
||||
remark: "Remark",
|
||||
createTime: "Create Time",
|
||||
updateTime: "Update Time",
|
||||
ldapUrl: "Server Address",
|
||||
ldapUrlPlease: 'Please enter the LDAP server address correctly',
|
||||
ldapBaseDN: "Base DN",
|
||||
baseDnPlease: 'Please enter the LDAP base DN correctly',
|
||||
ldapUserFilter: "User Filter",
|
||||
userFilterPlease: 'Please enter the LDAP user filter correctly',
|
||||
ldapBindDN: "Bind DN",
|
||||
ldapBindPassword: "Bind Password",
|
||||
smtpHost: 'Server Address',
|
||||
smtpHostPlease: 'Please enter the SMTP server address correctly',
|
||||
smtpPort: 'Port Number',
|
||||
smtpPortPlease: 'Please enter the SMTP port number correctly',
|
||||
oauth2ClientID: 'Client ID',
|
||||
oauth2ClientIDPlease: 'Please enter the OAuth2 client ID correctly',
|
||||
oauth2ClientSecret: 'Client Secret',
|
||||
oauth2ClientSecretPlease: 'Please enter the OAuth2 client secret correctly',
|
||||
oauth2AuthURL: 'Authorization URL',
|
||||
oauth2AuthURLPlease: 'Please enter the OAuth2 authorization URL correctly',
|
||||
oauth2TokenURL: 'Token URL',
|
||||
oauth2TokenURLPlease: 'Please enter the OAuth2 token URL correctly',
|
||||
oauth2UserURL: 'User Info URL',
|
||||
oauth2UserURLPlease: 'Please enter the OAuth2 user info URL correctly',
|
||||
oauth2AccountField: 'Account Field',
|
||||
oauth2AccountFieldPlease: 'Please enter the OAuth2 account field correctly',
|
||||
oauth2Scopes: 'Scopes',
|
||||
oauth2ScopesPlease: 'Please enter the OAuth2 scopes correctly',
|
||||
oauth2RedirectURL: 'Redirect URL',
|
||||
oauth2RedirectURLPlease: 'Please enter the OAuth2 redirect URL correctly',
|
||||
oauth2RedirectURLTip: 'Please jump to the specified path (omchost/#/login/oauth2), redirect with code and state address parameters',
|
||||
uploadFileTip: 'Confirm to upload the authentication source icon?',
|
||||
uploadFileOk: 'Authentication source icon upload successful',
|
||||
uploadFileErr: 'Authentication source icon upload failed',
|
||||
viewInfoErr: "Failed to get authentication source information",
|
||||
addInfo: "Add Authentication Source",
|
||||
editInfo: "Modify Authentication Source",
|
||||
delTip: "Confirm deleting the authentication source number [{num}] data item?",
|
||||
delOk: "Deleted Successfully",
|
||||
},
|
||||
setting: {
|
||||
charMaxLen: 'characters length',
|
||||
saveSubmit: 'Submit&Save',
|
||||
@@ -1878,10 +1820,6 @@ export default {
|
||||
home: 'Home Page',
|
||||
homeTip:'Do you want to submit the current interface as the system interface?',
|
||||
homeSet:'Home Page Settings',
|
||||
backup: 'System Backup',
|
||||
backupInstruction: 'System backup will back up the net element information records and configuration files running on the current system, and can restore the system to the previous state!',
|
||||
backupExportTip: 'Confirm to export system backup?',
|
||||
backupImportTip: 'Confirm to import system backup?',
|
||||
},
|
||||
role:{
|
||||
allScopeOptions:'All data permissions',
|
||||
@@ -1926,7 +1864,6 @@ export default {
|
||||
cancelGive:'Cancel authorization',
|
||||
cancelSure:'Confirm to cancel the authorization of the data item with user number [{userId}]?',
|
||||
batchCancel:'Batch cancellation of authorization',
|
||||
selectUser:'Assign Users',
|
||||
},
|
||||
dept:{
|
||||
classInfo:' Department Information',
|
||||
@@ -1947,37 +1884,6 @@ export default {
|
||||
phone:'Contact Number',
|
||||
email:'Mail',
|
||||
},
|
||||
tenant:{
|
||||
classInfo:' Tenant Information',
|
||||
className:'Name',
|
||||
classId:'Number',
|
||||
classSort:'Sorting',
|
||||
status:'Status',
|
||||
type:' Tenancy Asset',
|
||||
createTime:'Creation Time',
|
||||
highClass:'Root Level',
|
||||
key:'Asset Key',
|
||||
emailTip:'Please input the correct email address',
|
||||
phoneTip:'Please enter the correct phone number',
|
||||
node:'Root Node',
|
||||
delSure:'Are you sure to delete the data item with TenantName: {title}?',
|
||||
delTypeSure:'Are you sure to delete the data item with Tenancy Asset: {tenancyAsset}, Asset Key: {tenancyKey}?',
|
||||
open:'Exhibition',
|
||||
close:'Fold',
|
||||
addClass:'Add Tenant',
|
||||
admin:'Principal',
|
||||
phone:'Contact Number',
|
||||
email:'Mail',
|
||||
SDSST:'Network Slice',
|
||||
APN:'Access Point Name',
|
||||
IMSI:'SIM Card',
|
||||
treeSelectTip:'Please select a tenant from the tenant list on the left for configuration',
|
||||
upfTip:'Please select from the drop-down list of UPF Resource UIDs',
|
||||
imsiTip:'Please select the matching type and fill in the SIM card number fragment',
|
||||
radioTip:'Please select from the drop-down list of radio type and radio ID',
|
||||
defaultTip:'Please select from the drop-down list of Tenancy Asset, and then fill the Asset Key',
|
||||
patternTip:'The Asset Key cannot contain special characters',
|
||||
},
|
||||
post:{
|
||||
positionInfo:'Position Information',
|
||||
positionId:'Position Number',
|
||||
@@ -2000,7 +1906,7 @@ export default {
|
||||
requestMe:'Request Method',
|
||||
host:'Request Host',
|
||||
operStatus:'Status',
|
||||
operDate:'Time Stamp',
|
||||
operDate:'Time',
|
||||
useTime:'Time Lap',
|
||||
logInfo:'Operation Log Information',
|
||||
delSure:'Are you sure to delete the data item with access number [{ids}]?',
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import { language } from "@/plugins/http-fetch";
|
||||
import { eventData } from "@/views/dashboard/overview/hooks/useUserActivity";
|
||||
import { start, status } from "nprogress";
|
||||
|
||||
export default {
|
||||
// 语言
|
||||
i18n: '中文',
|
||||
@@ -10,14 +14,13 @@ export default {
|
||||
desc: '核心网管理平台',
|
||||
loading: '请稍等...',
|
||||
inputPlease: '请输入',
|
||||
searchPlease: '搜索菜单...',
|
||||
searchTip: '输入关键词搜索菜单',
|
||||
selectPlease: '请选择',
|
||||
tipTitle: '提示',
|
||||
msgSuccess: '{msg} 成功',
|
||||
errorFields: '请正确填写 {num} 处必填信息!',
|
||||
tablePaginationTotal: '总共 {total} 条',
|
||||
noData: "暂无数据",
|
||||
zebra:'表格斑马纹',
|
||||
ok: '确定',
|
||||
cancel: '取消',
|
||||
close: '关闭',
|
||||
@@ -30,7 +33,6 @@ export default {
|
||||
editText: '编辑',
|
||||
deleteText: '删除',
|
||||
downloadText: '下载',
|
||||
CloudServerText:'同步',
|
||||
import:'导入',
|
||||
export:'导出',
|
||||
uploadText: '上传',
|
||||
@@ -41,10 +43,6 @@ export default {
|
||||
columnSetText: '列设置',
|
||||
columnSetTitle: '列展示/排序',
|
||||
sizeText: '密度',
|
||||
exportCustom:'自定义导出',
|
||||
exportColumns:'列定义',
|
||||
resetToDefault:'重置为默认列',
|
||||
exportDefault:'全部导出',
|
||||
size: {
|
||||
default: '默认',
|
||||
middle: '中等',
|
||||
@@ -137,7 +135,7 @@ export default {
|
||||
},
|
||||
LockScreen: {
|
||||
inputPlacePwd:'请输入锁屏密码',
|
||||
enter:'进入',
|
||||
validSucc:'校验通过',
|
||||
validError:'校验失败',
|
||||
backLogin:'退出并重新登录',
|
||||
backReload:'正在重启,请稍等...',
|
||||
@@ -149,7 +147,7 @@ export default {
|
||||
|
||||
// 静态路由
|
||||
router: {
|
||||
index: "概览",
|
||||
index: "主页",
|
||||
login: "登录",
|
||||
register: '注册',
|
||||
page403: '没有访问权限',
|
||||
@@ -263,13 +261,9 @@ export default {
|
||||
registerBtn: '注册账号',
|
||||
loginBtn: '登录',
|
||||
loginSuccess: '登录成功',
|
||||
otherMethod: '其他方式',
|
||||
backBtn: '返回',
|
||||
backBtnLogin: '返回登录',
|
||||
authorizedNotfound: '授权无效',
|
||||
authorizedFailed: '授权失败',
|
||||
authorizedSuccess: '授权成功',
|
||||
redirectHome: '{i} 秒后跳转主页',
|
||||
loginMethod: '其他登录方式:',
|
||||
loginMethodWX: '微信扫一扫登录',
|
||||
loginMethodQQ: 'QQ扫码登录',
|
||||
},
|
||||
register: {
|
||||
registerBtn: '注册',
|
||||
@@ -360,39 +354,99 @@ export default {
|
||||
description: "暂无数据,尝试刷新看看",
|
||||
},
|
||||
},
|
||||
agentManage:{
|
||||
callings:{
|
||||
callerIdNumber:'主叫号码',
|
||||
calleeIdNumber:'被叫号码',
|
||||
startTime:'开始时间',
|
||||
answeredTime:'接听时间(s)',
|
||||
callDuration:'通话时长',
|
||||
msdData:'MSD内容',
|
||||
},
|
||||
callback:{
|
||||
callerIdNumber:'主叫号码',
|
||||
calleeIdNumber:'被叫号码',
|
||||
status:'状态',
|
||||
ticketId:'工单编号',
|
||||
startTime:'创建时间',
|
||||
updateTime:'更新时间',
|
||||
msdData:'MSD内容',
|
||||
agentName: '座席名称',
|
||||
comment: '备注',
|
||||
agentEmail: '座席邮箱',
|
||||
agentMobile: '座席手机',
|
||||
title: '工单列表',
|
||||
}
|
||||
},
|
||||
cbc:{
|
||||
cbe:{
|
||||
neType:'CBC网元对象',
|
||||
title: '广播事件',
|
||||
delTip: '确认删除编号为【{num}】的CBC事件吗?',
|
||||
eventName: '事件名称',
|
||||
startTime: '开始时间',
|
||||
endTime: '结束时间',
|
||||
repetitionPeriod: '广播周期',
|
||||
numOfBcast: '广播次数',
|
||||
msgPWSType: '消息类型',
|
||||
messageId: '消息编号',
|
||||
displayMode: '显示模式',
|
||||
geoScope:'广播覆盖范围',
|
||||
emergencyUserAlert: '紧急用户提示',
|
||||
activatePopup: '弹窗提示',
|
||||
warningType: '预警类型标识',
|
||||
language:'语言',
|
||||
warningMessageText:'广播内容',
|
||||
status: '状态',
|
||||
warningAreaType: '预警区域类型',
|
||||
taiListTip:'TAI List 不能为空',
|
||||
taiSonTip:'TAI List 每项的 MCC、MNC、TAC 都不能为空',
|
||||
eutraListTip:'EUTRA CellId List 不能为空',
|
||||
eutraSonTip:'EUTRA CellId List 每项的 MCC、MNC、CellId 都不能为空',
|
||||
nrTip:'NR CellId List 不能为空',
|
||||
nrSonTip:'NR CellId List 每项的 MCC、MNC、CellId 都不能为空',
|
||||
areaTip:'Area ID List 不能为空',
|
||||
areaSonTip:'Area ID List 每项的 AreaID 都不能为空',
|
||||
messageIdProfile:'消息标识配置',
|
||||
serialNumProfile:'序列号配置',
|
||||
warningTypeProfile:'预警类型配置',
|
||||
warningMessageProfile:'预警消息配置',
|
||||
etws:'地震海啸预警',
|
||||
cmas:'公共预警广播',
|
||||
createdAt:'创建时间',
|
||||
eventNameHelp:'如果为CMAS,推荐前缀为cmas_xxx,如果为ETWS,推荐前缀为etws_xxx',
|
||||
repetitionPeriodHelp:'单位是秒',
|
||||
numOfBcastHelp:'单位是秒',
|
||||
cellListTip:'CellId List不能为空',
|
||||
cellListSonTip:'MCC、MNC、不能为空以及EUTRA CellId/NR CellId至少填写一个',
|
||||
letupSure:'确认修改编号为 【{id}】 的广播事件的状态嘛?',
|
||||
tacHelp:'TAC值是十进制字符串,通过";"分隔多个TAC值',
|
||||
cellIdHelp:'CellId值是十六进制字符串,通过";"分隔多个CellId值',
|
||||
areaId:'areaId值是十六进制字符串',
|
||||
detail:'详情',
|
||||
}
|
||||
},
|
||||
dashboard: {
|
||||
overview:{
|
||||
title: "核心网系统看板",
|
||||
fullscreen: "点击全屏显示",
|
||||
toRouter: "点击跳转详情页面",
|
||||
Users:"UDM 订阅数据",
|
||||
VoNR:"IMS在线用户",
|
||||
sessions:"SMF的PDU会话",
|
||||
FivegNodeN:"5G在线基站数",
|
||||
Fiveusers:"AMF在线用户数",
|
||||
FourgNodeN:"4G在线基站数",
|
||||
Fourusers:"MME在线用户数",
|
||||
UPFjump:"点击跳转到UPF黄金指标界面",
|
||||
Networkjump:"点击跳转到网元系统拓扑图",
|
||||
Alarmjump:"点击跳转到活动告警",
|
||||
IMSUsers:"点击跳转到 IMS 用户",
|
||||
VoIPUsers:"点击跳转到 VoIP 用户",
|
||||
psapTitle:'PSAP看板',
|
||||
onlineUser:'在线座席数',
|
||||
totalUser:'总座席数',
|
||||
parallelUser:'并行通话数',
|
||||
userTitle:'用户统计',
|
||||
sysTitle:'系统资源',
|
||||
skim: {
|
||||
users: "用户数",
|
||||
userTitle:'用户信息',
|
||||
imsUeNum: "IMS 会话数",
|
||||
smfUeNum: "Data 会话数",
|
||||
gnbBase: "5G 基站数",
|
||||
gnbSumBase: "5G 基站总数",
|
||||
gnbUeNum:'5G 用户数',
|
||||
enbBase: "4G 基站数",
|
||||
enbSumBase: "4G 基站总数",
|
||||
enbUeNum:'4G 用户数',
|
||||
baseTitle:'在线信息',
|
||||
nodeBInfo: '基站信息',
|
||||
onlineinfo:'User Online Infomation',
|
||||
ims:'IMS',
|
||||
voip:'VoIP'
|
||||
},
|
||||
upfFlow:{
|
||||
title: "用户面吞吐量",
|
||||
@@ -422,7 +476,6 @@ export default {
|
||||
},
|
||||
userActivity: {
|
||||
title: "用户活动",
|
||||
imsTitle: "IMS 活动",
|
||||
type: "类型",
|
||||
duration: "时长",
|
||||
caller: "主叫",
|
||||
@@ -441,20 +494,15 @@ export default {
|
||||
rowInfo: "记录信息",
|
||||
type: "记录类型",
|
||||
duration: "通话时长",
|
||||
mosAverage: "MOS",
|
||||
callConnectionTime: "Call Connection Time",
|
||||
seizureTime: "呼叫开始时间",
|
||||
releaseTime: "挂断结束时间",
|
||||
caller: "主叫",
|
||||
called: "被叫",
|
||||
result: "结果",
|
||||
resultCode: "结果码",
|
||||
resultCause: "结果原因",
|
||||
resultOk: "成功",
|
||||
resultFail: "失败",
|
||||
delTip: "确认删除编号为【{msg}】的数据项?",
|
||||
tenantName: "租户名称",
|
||||
exportTip: "确认导出当前查询条件的话单数据吗?",
|
||||
exportTip: "确认导出当前查询条件的话单数据吗?(导出最大支持一万条)",
|
||||
chargingID: '计费ID',
|
||||
smfSubscriptionIDData: '订阅 ID 数据',
|
||||
smfSubscriptionIDType: '订阅 ID 类型',
|
||||
@@ -467,6 +515,35 @@ export default {
|
||||
sgwcServedMSISDN: 'MSISDN',
|
||||
sgwcVolumeGPRSUplink: 'GPRS 上行链路',
|
||||
sgwcVolumeGPRSDownlink: 'GPRS 下行链路',
|
||||
recordPath:'录音文件路径',
|
||||
msd:'最小数据集',
|
||||
automaticActivation: '自动激活',
|
||||
positionCanBeTrusted: '位置可信',
|
||||
testCall: '测试呼叫',
|
||||
vehicleType:'车辆类型',
|
||||
messageIdentifier: '消息标识',
|
||||
numberOfOccupants: '乘员数量',
|
||||
n1latitudeDelta:'纬度增量',
|
||||
n1longitudeDelta: '经度增量',
|
||||
n2latitudeDelta:' 纬度增量',
|
||||
n2longitudeDelta: '经度增量',
|
||||
timestamp: '时间戳',
|
||||
vehicleDirection: '车辆行驶方向',
|
||||
isovds: '车辆描述段',
|
||||
isovisModelyear:'车辆标识段年份',
|
||||
isovisSeqPlant: '车辆标识段的工厂编码与生产序号',
|
||||
isowmi: '世界制造商标识',
|
||||
positionLatitude: '位置纬度',
|
||||
positionLongitude: '位置经度',
|
||||
dieselTankPresent: '柴油罐存在',
|
||||
electricEnergyStorage: '电力储能',
|
||||
gasolineTankPresent: '汽油罐存在',
|
||||
control:'控制',
|
||||
recentVehicleLocationN1:'最近车辆位置N1',
|
||||
recentVehicleLocationN2:'最近车辆位置N2',
|
||||
vehicleIdentificationNumber:'车辆识别号码',
|
||||
vehicleLocation:'车辆位置',
|
||||
vehiclePropulsionStorageType:'车辆推进存储类型',
|
||||
},
|
||||
ue: {
|
||||
eventType: "事件类型",
|
||||
@@ -478,7 +555,7 @@ export default {
|
||||
result: "结果",
|
||||
resultOk: "成功",
|
||||
delTip: "确认删除编号为【{msg}】的数据项?",
|
||||
exportTip: "确认导出当前查询条件的事件数据吗?",
|
||||
exportTip: "确认导出当前查询条件的事件数据吗?(导出最大支持一万条)",
|
||||
},
|
||||
},
|
||||
ne: {
|
||||
@@ -501,8 +578,6 @@ export default {
|
||||
portTip: "网元服务端口,默认:33030",
|
||||
serialNum: '序列号',
|
||||
expiryDate: '许可证到期日期',
|
||||
ueNumber: '用户数',
|
||||
nbNumber: '基站数',
|
||||
normalcy: '正常',
|
||||
exceptions: '异常',
|
||||
restart: '重启',
|
||||
@@ -540,7 +615,7 @@ export default {
|
||||
delTip: '确认删除网元信息数据项吗?',
|
||||
oam: {
|
||||
title: 'OAM配置',
|
||||
restart: '下发后重启网元',
|
||||
sync: '同步到网元',
|
||||
oamEnable: '服务',
|
||||
oamPort: '端口',
|
||||
snmpEnable: '服务',
|
||||
@@ -650,8 +725,8 @@ export default {
|
||||
change: "变更许可证",
|
||||
reload: "刷新信息",
|
||||
reloadTip: "确认要刷新许可证信息吗?",
|
||||
reloadBatch: "刷新许可证状态",
|
||||
reloadBatchTip: "对当前列表网元进行许可证状态信息刷新吗?",
|
||||
reloadBatch: "批量刷新",
|
||||
reloadBatchTip: "对勾选的记录进行信息刷新吗?",
|
||||
updateTtile: "更新许可证",
|
||||
downCodeTop: "确认要将许可激活码保存到文件吗?",
|
||||
activationRequestCode: "许可激活码",
|
||||
@@ -693,19 +768,6 @@ export default {
|
||||
name: "名称",
|
||||
downTip: '确认要下载备份文件【{txt}】吗?',
|
||||
title: "修改备份信息 {txt}",
|
||||
backupModal: {
|
||||
pushFileOper: "将当前文件发送到远程备份",
|
||||
title: "设置远程备份服务",
|
||||
enable: "启用",
|
||||
toIp: "服务IP",
|
||||
toIpPleace: "请输入远程备份服务器 IP 地址",
|
||||
toPort: "服务端口",
|
||||
username: "登录用户名",
|
||||
usernamePleace: '请输入服务登录用户名',
|
||||
password: "登录密码",
|
||||
dir: "保存目录",
|
||||
dirPleace: '请输入服务地址目标文件目录',
|
||||
}
|
||||
},
|
||||
neQuickSetup: {
|
||||
reloadPara5G: '刷新',
|
||||
@@ -757,32 +819,10 @@ export default {
|
||||
},
|
||||
},
|
||||
neData: {
|
||||
common: {
|
||||
startIMSI: '起始IMSI',
|
||||
imsi: 'IMSI',
|
||||
imsiTip: 'IMSI=MCC+MNC+MSIN',
|
||||
imsiTip1: 'MCC=移动国家号码, 由三位数字组成',
|
||||
imsiTip2: 'MNC=移动网络号,由两位数字组成',
|
||||
imsiTip3: 'MSIN=移动客户识别码,采用等长10位数字构成',
|
||||
imsiPlease: "请正确输入IMSI",
|
||||
msisdn: '移动客户识别码',
|
||||
msisdnPlease: "请正确输入移动客户识别码",
|
||||
loadDataConfirm: '确认要重新加载数据吗?',
|
||||
loadData: '加载数据',
|
||||
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!',
|
||||
batchOper: '批量操作',
|
||||
batchAddText: '批量新增',
|
||||
batchDelText: '批量删除',
|
||||
batchUpdateText: '批量更新',
|
||||
batchNum: '批量个数',
|
||||
checkDel:'勾选删除',
|
||||
importTemplate: '导入模板',
|
||||
},
|
||||
baseStation: {
|
||||
list: "列表",
|
||||
topology: "拓扑图",
|
||||
nbName: "设备名称",
|
||||
nbId: "设备ID",
|
||||
ueNum: "在线用户数",
|
||||
topologyTitle: "基站状态关系图",
|
||||
name: "基站名称",
|
||||
@@ -801,12 +841,6 @@ export default {
|
||||
exportTip: "确认根据搜索条件导出xlsx表格文件吗?",
|
||||
importDataEmpty: "导入数据为空",
|
||||
},
|
||||
backupData: {
|
||||
auth: "UDM鉴权用户",
|
||||
sub: "UDM签约用户",
|
||||
voip: "VOIP鉴权用户",
|
||||
volte: "IMS签约用户",
|
||||
}
|
||||
},
|
||||
neUser: {
|
||||
auth: {
|
||||
@@ -838,12 +872,10 @@ export default {
|
||||
opcTip: '鉴权秘钥,OPC是由Ki和OP经过计算得来的,OP为运营商的根秘钥,ki是鉴权秘钥,最大长度为32',
|
||||
delSure:'确认删除IMSI编号为: {imsi} 的用户吗?',
|
||||
imsiConfirm:'IMSI的长度必须为15',
|
||||
startUserName:'起始用户名',
|
||||
},
|
||||
sub: {
|
||||
subInfo:'签约信息',
|
||||
neType: 'UDM网元类型',
|
||||
imsType:'IMS网元类型',
|
||||
export: '导出',
|
||||
exportConfirm: '确认导出全部签约用户数据吗?',
|
||||
checkExport : '勾选导出',
|
||||
@@ -873,10 +905,6 @@ export default {
|
||||
rfspTip:'RFSP 索引,在 NG-RAN 中,特定 RRM 配置的索引,参数介于0到127之间',
|
||||
ueTypeTip: '运营商定义的用户 UE Usage Type,整型,参数介于0到127之间',
|
||||
cnFlag: '是否开启 5G Core Network 服务',
|
||||
cnFlag0: '不允许接入',
|
||||
cnFlag1: '只能接入 5G',
|
||||
cnFlag2: '只能接入 4G',
|
||||
cnFlag3: '允许接入 4G/5G',
|
||||
epsFlagTip: '是否开启 4G EPS 服务',
|
||||
contextIdTip: '签约APN 上下文ID,必须从APN Context list 中选择。',
|
||||
apnContextTip: '手机可用的APN列表,最多六个,在HSS中定义。',
|
||||
@@ -886,12 +914,6 @@ export default {
|
||||
ardTip:'接入控制标志(Access-Restriction-Data),可用于区分2G/3G/LTE用户,便于为2G/3G/LTE网络共存时,对不同类型用户进行区分服务',
|
||||
smDataTip:'sm_data=1-000001&internet-1.2.3.4&ims-1.2.3.5中的IP:1.2.3.4为5G用户internet这个APN分配的静态IP,1.2.3.5为5G用户ims这个APN分配的静态IP。如果是动态分配,把IP以及前面一个连接符去掉即可。需支持多个dnn用&连接',
|
||||
smDataArrTip:'SST,DNN/APN为必填项',
|
||||
tenantName:'租户名称',
|
||||
imsiMode:'IMSI匹配模式',
|
||||
fuzzyMatch:'模糊匹配',
|
||||
prefixMatch:'前缀匹配',
|
||||
fullMatch:'全匹配',
|
||||
suffixMatch:'后缀匹配',
|
||||
},
|
||||
pcf: {
|
||||
neType: 'PCF网元对象',
|
||||
@@ -926,8 +948,6 @@ export default {
|
||||
},
|
||||
base5G: {
|
||||
neType: '网元对象',
|
||||
gnb:'5G 基站',
|
||||
enb:'4G 基站',
|
||||
},
|
||||
n3iwf: {
|
||||
neType: 'N3IWF网元对象',
|
||||
@@ -940,7 +960,7 @@ export default {
|
||||
},
|
||||
nssf:{
|
||||
neType: 'NSSF网元对象',
|
||||
}
|
||||
},
|
||||
},
|
||||
perfManage: {
|
||||
taskManage:{
|
||||
@@ -1039,13 +1059,8 @@ export default {
|
||||
expression:'计算公式',
|
||||
description:'描述',
|
||||
kpiSet:'统计设置',
|
||||
ago1Hour:'过去1小时',
|
||||
ago3Hour:'过去3小时',
|
||||
ago6Hour:'过去6小时',
|
||||
toDay:'今天',
|
||||
ago1Day:'过去1天',
|
||||
ago7Day:'过去7天',
|
||||
ago15Day:'过去15天',
|
||||
sixHoursAgo:'6小时前',
|
||||
threeHoursAgo:'3小时前',
|
||||
delCustomTip:'确认删除自定义指标项为 {num} 的数据项?',
|
||||
delCustom:'成功删除记录编号为 {num} 自定义指标',
|
||||
addCustom:'添加自定义指标',
|
||||
@@ -1062,11 +1077,6 @@ export default {
|
||||
expressionErrorTip:'请检查表达式,错误的指标为{kpiId}',
|
||||
expressionNoIdTip:'请检查表达式,没有找到任何有效的指标',
|
||||
unitSelect:'为更好展示图需选择相同单位,当前单位为:',
|
||||
avg:'(平均)',
|
||||
total:'(累计)',
|
||||
ago1:'近1天值',
|
||||
ago7:'近7天值',
|
||||
ago30:'近30天值',
|
||||
},
|
||||
kpiKeyTarget:{
|
||||
"time":"时间",
|
||||
@@ -1098,25 +1108,28 @@ export default {
|
||||
"changeBar":"切换为柱状图",
|
||||
"chooseShowMetrics":"选择需要显示的指标",
|
||||
"chooseMetrics":"选择指标",
|
||||
"tips":"百分比和比率是平均值,计数是统计值。",
|
||||
},
|
||||
voiceOverView:{
|
||||
"voiceTitle":"语音通话仪表盘",
|
||||
"tips":"每分钟数据语音统计",
|
||||
"ne":"网元",
|
||||
"now":"现在",
|
||||
"last":"过去",
|
||||
"calls":"呼叫",
|
||||
"activeCall":"正在通话",
|
||||
"callMOMT":"呼叫 主叫接通率/被叫接通率",
|
||||
"failedcall":"失败呼叫",
|
||||
"registration":"注册",
|
||||
"activeregistration":"主动注册",
|
||||
"registrationsuccess":"注册成功率",
|
||||
"failedregistration":"失败注册",
|
||||
},
|
||||
},
|
||||
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: '命令',
|
||||
@@ -1151,7 +1164,6 @@ export default {
|
||||
},
|
||||
task: {
|
||||
traceId: '跟踪编号',
|
||||
title: '跟踪标题',
|
||||
trackType: '跟踪类型',
|
||||
trackTypePlease: '请选择跟踪类型',
|
||||
creater: '创建人',
|
||||
@@ -1168,17 +1180,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} 的数据项?',
|
||||
@@ -1188,15 +1204,6 @@ export default {
|
||||
traceFile: "跟踪文件",
|
||||
errMsg: "错误信息",
|
||||
imsiORmsisdn: "imsi 或 msisdn 是空值,不能开始任务",
|
||||
dataView: "跟踪数据",
|
||||
protocolOrInterface: "协议/接口",
|
||||
msgNe: '消息网元',
|
||||
msgEvent: '消息事件',
|
||||
msgType: '消息类型',
|
||||
msgDirect: '消息方向',
|
||||
msgLen: '消息长度',
|
||||
rowTime: '消息时间',
|
||||
taskInfo: '任务信息',
|
||||
},
|
||||
},
|
||||
faultManage: {
|
||||
@@ -1317,19 +1324,12 @@ export default {
|
||||
tailLines: '末尾行数',
|
||||
},
|
||||
exportFile:{
|
||||
fileSource:'文件来源',
|
||||
fileSourcePlease:'请选择文件来源',
|
||||
fileName:'文件来源',
|
||||
downTip: "确认下载文件名为 【{fileName}】 文件?",
|
||||
downTipErr: "文件获取失败",
|
||||
deleteTip: "确认删除文件名为 【{fileName}】 文件?",
|
||||
deleteTipErr: "文件删除失败",
|
||||
sysloginLog:'系统登录日志',
|
||||
sysOperateLog:'系统操作日志',
|
||||
neLog:'网元日志',
|
||||
cdrIMS:'语音话单',
|
||||
cdrSMF:'数据话单',
|
||||
cdrSMSC:'短信话单',
|
||||
cdrSGWC:'漫游数据话单',
|
||||
selectTip:"请选择文件名",
|
||||
}
|
||||
},
|
||||
monitor: {
|
||||
@@ -1701,10 +1701,8 @@ export default {
|
||||
userNum: '用户编号',
|
||||
account: '登录账号',
|
||||
userName: '用户昵称',
|
||||
permission: '用户角色',
|
||||
permission: '用户权限',
|
||||
className: '部门名称',
|
||||
userType: '用户类型',
|
||||
tenntName:'租户名称',
|
||||
loginIp: '登录地址',
|
||||
loginTime: '登录时间',
|
||||
status: '用户状态',
|
||||
@@ -1730,7 +1728,6 @@ export default {
|
||||
sex:'用户性别',
|
||||
email:'电子邮箱',
|
||||
fromClass:'所属部门',
|
||||
fromTenant:'所属租户',
|
||||
userWork:'用户岗位',
|
||||
userWorkPlease: '请选择用户岗位',
|
||||
userTip:'用户说明',
|
||||
@@ -1744,7 +1741,6 @@ export default {
|
||||
lock:'锁定',
|
||||
inactive:'未激活',
|
||||
active:'激活',
|
||||
permissionTip:'若要选择租户角色,需保证起码租户管理中有一个租户',
|
||||
},
|
||||
config: {
|
||||
configName: "参数名称",
|
||||
@@ -1769,55 +1765,6 @@ export default {
|
||||
refreshCacheTip: "确定要刷新参数配置缓存吗?",
|
||||
refreshCacheOk: "刷新缓存成功",
|
||||
},
|
||||
loginSource: {
|
||||
uid: "唯一标识",
|
||||
name: "名称",
|
||||
namePlease: '请正确输入认证源名称',
|
||||
icon: "图标",
|
||||
iconPlease: '可填入图片链接或上传图片路径地址',
|
||||
type: "类型",
|
||||
activeFlag: "状态",
|
||||
remark: "备注说明",
|
||||
createTime: "创建时间",
|
||||
updateTime: "更新时间",
|
||||
ldapUrl: "服务器地址",
|
||||
ldapUrlPlease: '请正确输入LDAP 服务器地址',
|
||||
ldapBaseDN: "基础DN",
|
||||
baseDnPlease: '请正确输入LDAP 基础DN',
|
||||
ldapUserFilter: "用户过滤",
|
||||
userFilterPlease: '请正确输入LDAP 用户过滤',
|
||||
ldapBindDN: "绑定DN",
|
||||
ldapBindPassword: "绑定密码",
|
||||
smtpHost: '服务器地址',
|
||||
smtpHostPlease: '请正确输入SMTP 服务器地址',
|
||||
smtpPort: '端口号',
|
||||
smtpPortPlease: '请正确输入SMTP 端口号',
|
||||
oauth2ClientID: '客户端ID',
|
||||
oauth2ClientIDPlease: '请正确输入OAuth2 客户端ID',
|
||||
oauth2ClientSecret: '客户端密钥',
|
||||
oauth2ClientSecretPlease: '请正确输入OAuth2 客户端密钥',
|
||||
oauth2AuthURL: '授权URL',
|
||||
oauth2AuthURLPlease: '请正确输入OAuth2 授权URL',
|
||||
oauth2TokenURL: '令牌URL',
|
||||
oauth2TokenURLPlease: '请正确输入OAuth2 令牌URL',
|
||||
oauth2UserURL: '用户信息URL',
|
||||
oauth2UserURLPlease: '请正确输入OAuth2 用户信息URL',
|
||||
oauth2AccountField: '账号字段',
|
||||
oauth2AccountFieldPlease: '请正确输入OAuth2 账号字段',
|
||||
oauth2Scopes: '作用域',
|
||||
oauth2ScopesPlease: '请正确输入OAuth2 作用域',
|
||||
oauth2RedirectURL: '重定向URL',
|
||||
oauth2RedirectURLPlease: '请正确输入OAuth2 重定向URL',
|
||||
oauth2RedirectURLTip: '请跳转指定路径(omchost/#/login/oauth2), 重定向携带code和state地址参数',
|
||||
uploadFileTip: '确认要上传认证源图标吗?',
|
||||
uploadFileOk: '认证源图标上传成功',
|
||||
uploadFileErr: '认证源图标上传失败',
|
||||
viewInfoErr: "获取认证源信息失败",
|
||||
addInfo: "添加认证源",
|
||||
editInfo: "修改认证源",
|
||||
delTip: "确认删除认证源编号为 【{num}】 的数据项?",
|
||||
delOk: "删除成功",
|
||||
},
|
||||
setting: {
|
||||
charMaxLen: '位字符长度',
|
||||
saveSubmit: '提交保存',
|
||||
@@ -1877,10 +1824,6 @@ export default {
|
||||
home: '系统首页',
|
||||
homeTip:'确认要提交当前界面为系统界面吗?',
|
||||
homeSet:'系统首页设置',
|
||||
backup: '系统备份',
|
||||
backupInstruction: '系统备份将会对当前系统上运行的网元信息记录及配置文件进行备份,可进行系统的恢复操作!',
|
||||
backupExportTip: '确认要导出系统备份吗?',
|
||||
backupImportTip: '确认要导入系统备份吗?',
|
||||
},
|
||||
role:{
|
||||
allScopeOptions:'全部数据权限',
|
||||
@@ -1925,7 +1868,6 @@ export default {
|
||||
cancelGive:'取消授权',
|
||||
cancelSure:'确认取消用户编号为 【{userId}】 的数据项授权?',
|
||||
batchCancel:'批量取消授权',
|
||||
selectUser:'分配用户',
|
||||
},
|
||||
dept:{
|
||||
classInfo:'部门信息',
|
||||
@@ -1946,37 +1888,6 @@ export default {
|
||||
phone:'联系电话',
|
||||
email:'邮箱',
|
||||
},
|
||||
tenant:{
|
||||
classInfo:'租户信息',
|
||||
className:'名称',
|
||||
classId:'编号',
|
||||
classSort:'排序',
|
||||
status:'状态',
|
||||
type:'租赁类型',
|
||||
createTime:'创建时间',
|
||||
highClass:'根级',
|
||||
key:'租赁标识',
|
||||
emailTip:'请输入正确的邮箱地址',
|
||||
phoneTip:'请输入正确的手机号码',
|
||||
node:'根节点',
|
||||
delSure:'确认删除租户名为 【{title}】 的数据项?',
|
||||
delTypeSure:'确认删除租赁类型为 【{tenancyAsset}】,租赁标识为【{tenancyKey}】 的数据项?',
|
||||
open:'展',
|
||||
close:'折',
|
||||
addClass:'新增租户',
|
||||
admin:'负责人',
|
||||
phone:'联系电话',
|
||||
email:'邮箱',
|
||||
SDSST:'网络切片',
|
||||
APN:'接入点',
|
||||
IMSI:'SIM卡',
|
||||
treeSelectTip:'请从左侧租户列表中选择租户进行配置',
|
||||
upfTip:'请选择下拉列表UPF的资源唯一标识',
|
||||
imsiTip:'请选择匹配类型和填入SIM卡号段',
|
||||
radioTip:'请从下拉列表选择基站类型和基站ID',
|
||||
defaultTip:'请选择租赁资产类别, 然后填写资产标识',
|
||||
patternTip:'租赁标识不能包含特殊字符',
|
||||
},
|
||||
post:{
|
||||
positionInfo:'岗位信息',
|
||||
positionId:'岗位编号',
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
import { useRouter } from 'vue-router';
|
||||
import useLayoutStore from '@/store/modules/layout';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useRouterStore from '@/store/modules/router';
|
||||
import useTabsStore from '@/store/modules/tabs';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
@@ -28,14 +27,17 @@ import useAlarmStore from '@/store/modules/alarm';
|
||||
import { getServerTime } from '@/api';
|
||||
import { MENU_PATH_INLINE } from '@/constants/menu-constants';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { parseDateToStr, YYYY_MM_DD_HH_MM_SSZ } from '@/utils/date-utils';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { parseUrlPath } from '@/plugins/file-static-url';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
|
||||
const { proConfig, waterMarkContent } = useLayoutStore();
|
||||
const { t, currentLocale } = useI18n();
|
||||
const routerStore = useRouterStore();
|
||||
const tabsStore = useTabsStore();
|
||||
const appStore = useAppStore();
|
||||
const router = useRouter();
|
||||
const neListStore = useNeInfoStore();
|
||||
|
||||
/**菜单面板 */
|
||||
const layoutState = reactive({
|
||||
@@ -65,8 +67,8 @@ watch(
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 动态路由添加到菜单面板
|
||||
const menuData = computed(() => {
|
||||
// 动态路由添加到菜单面板
|
||||
const rootRoute = router.getRoutes().find(r => r.name === 'Root');
|
||||
if (rootRoute) {
|
||||
const children = routerStore.setRootRouterData(rootRoute.children);
|
||||
@@ -76,15 +78,11 @@ const menuData = computed(() => {
|
||||
} else {
|
||||
rootRoute.children = children;
|
||||
}
|
||||
// console.log(JSON.parse(JSON.stringify(rootRoute.children)));
|
||||
if (!useUserStore().roles.includes('tenant')) {
|
||||
rootRoute.children = rootRoute.children.filter(
|
||||
item => item.name !== 'Index'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
|
||||
const neTypes = neListStore.getNeSelectOtions.map(v => v.value);
|
||||
let routes = clearMenuItem(router.getRoutes());
|
||||
routes = routerStore.clearMenuItemByNeList(routes, neTypes);
|
||||
const { menuData } = getMenuData(routes);
|
||||
return menuData;
|
||||
});
|
||||
|
||||
@@ -208,7 +206,6 @@ let serverTime = reactive({
|
||||
zone: 'UTC', // 时区 UTC
|
||||
interval: null as any, // 定时器
|
||||
});
|
||||
let activeAlarmRefresh = 0;
|
||||
|
||||
// 获取服务器时间
|
||||
function fnGetServerTime() {
|
||||
@@ -222,18 +219,10 @@ function fnGetServerTime() {
|
||||
serverTime.timestamp = parseInt(res.data.timestamp);
|
||||
serverTime.interval = setInterval(() => {
|
||||
serverTime.timestamp += 1000;
|
||||
activeAlarmRefresh += 1;
|
||||
// serverTimeStr.value = parseDateToStr(serverTime.timestamp);
|
||||
// 用DOM直接修改
|
||||
if (serverTimeDom) {
|
||||
serverTimeDom.innerText = parseDateToStr(
|
||||
serverTime.timestamp,
|
||||
YYYY_MM_DD_HH_MM_SSZ
|
||||
);
|
||||
}
|
||||
if (activeAlarmRefresh === 5) {
|
||||
useAlarmStore().fnGetActiveAlarmInfo();
|
||||
activeAlarmRefresh = 0;
|
||||
serverTimeDom.innerText = parseDateToStr(serverTime.timestamp);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
@@ -274,7 +263,6 @@ onUnmounted(() => {
|
||||
v-bind="proConfig"
|
||||
:iconfont-url="scriptUrl"
|
||||
:locale="fnLocale"
|
||||
:sider-width="256"
|
||||
>
|
||||
<!--插槽-菜单头-->
|
||||
<template #menuHeaderRender>
|
||||
|
||||
@@ -4,7 +4,6 @@ import svgDark from '@/assets/svg/dark.svg';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import { viewTransitionTheme } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import SearchMenu from './SearchMenu.vue';
|
||||
import { ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
@@ -15,9 +14,6 @@ import useAppStore from '@/store/modules/app';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useAlarmStore from '@/store/modules/alarm';
|
||||
import useMaskStore from '@/store/modules/mask';
|
||||
import { dbClear } from '@/utils/cache-db-utils';
|
||||
import { CACHE_DB_TABLE_DND } from '@/constants/cache-keys-constants';
|
||||
import { TENANTADMIN_ROLE_KEY } from '@/constants/admin-constants';
|
||||
const { isFullscreen, toggle } = useFullscreen();
|
||||
const { t, changeLocale, optionsLocale } = useI18n();
|
||||
const layoutStore = useLayoutStore();
|
||||
@@ -37,10 +33,7 @@ function fnClick({ key }: MenuInfo) {
|
||||
router.push({ name: 'Profile' });
|
||||
break;
|
||||
case 'logout':
|
||||
userStore.fnLogOut().finally(() => {
|
||||
dbClear(CACHE_DB_TABLE_DND);
|
||||
router.push({ name: 'Login' });
|
||||
});
|
||||
userStore.fnLogOut().finally(() => router.push({ name: 'Login' }));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -85,33 +78,21 @@ function fnChangeLocale(e: any) {
|
||||
|
||||
<template>
|
||||
<a-space :size="12" align="center">
|
||||
<!-- 搜索功能 -->
|
||||
<span >
|
||||
<SearchMenu />
|
||||
</span>
|
||||
|
||||
<span v-roles:has="[TENANTADMIN_ROLE_KEY]">
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>{{ t('loayouts.rightContent.alarm') }}</template>
|
||||
<a-button
|
||||
type="text"
|
||||
style="color: inherit"
|
||||
@click="fnClickAlarm"
|
||||
v-perms:has="['faultManage:active-alarm:index']"
|
||||
>
|
||||
<template #icon>
|
||||
<a-badge
|
||||
:count="useAlarmStore().activeAlarmTotal"
|
||||
:overflow-count="99"
|
||||
status="warning"
|
||||
style="color: inherit"
|
||||
>
|
||||
<BellOutlined />
|
||||
</a-badge>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</span>
|
||||
<a-tooltip placement="bottomRight">
|
||||
<template #title>{{ t('loayouts.rightContent.alarm') }}</template>
|
||||
<a-button type="text" style="color: inherit" @click="fnClickAlarm">
|
||||
<template #icon>
|
||||
<a-badge
|
||||
:count="useAlarmStore().activeAlarmTotal"
|
||||
:overflow-count="99"
|
||||
status="warning"
|
||||
style="color: inherit"
|
||||
>
|
||||
<BellOutlined />
|
||||
</a-badge>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<!-- 锁屏操作 -->
|
||||
<span v-perms:has="['system:setting:lock']">
|
||||
|
||||
@@ -1,380 +0,0 @@
|
||||
<template>
|
||||
<!-- 搜索按钮 -->
|
||||
<a-tooltip placement="bottom">
|
||||
<template #title>{{ t('common.search') }}</template>
|
||||
<a-button type="text" style="color: inherit" @click="fnClickSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<!-- 搜索弹窗 -->
|
||||
<ProModal
|
||||
:drag="false"
|
||||
:center-y="true"
|
||||
:width="600"
|
||||
:minHeight="400"
|
||||
:mask-closable="true"
|
||||
v-model:open="searchModalOpen"
|
||||
:title="t('common.search')"
|
||||
:footer="null"
|
||||
@cancel="fnCloseSearch"
|
||||
>
|
||||
<div class="search-modal-content">
|
||||
<a-input
|
||||
ref="searchInputRef"
|
||||
v-model:value="searchKeyword"
|
||||
:placeholder="t('common.searchPlease')"
|
||||
size="large"
|
||||
style="margin-bottom: 16px"
|
||||
@keydown="fnHandleKeydown"
|
||||
>
|
||||
<template #prefix>
|
||||
<SearchOutlined style="color: #bfbfbf" />
|
||||
</template>
|
||||
</a-input>
|
||||
|
||||
<div class="search-results">
|
||||
<div v-if="filteredMenus.length === 0" class="no-results">
|
||||
<a-empty
|
||||
:description="
|
||||
searchKeyword ? t('common.noData') : t('common.searchTip')
|
||||
"
|
||||
:image="false"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="menu-list">
|
||||
<div
|
||||
v-for="menu in filteredMenus"
|
||||
:key="menu.key"
|
||||
class="menu-item"
|
||||
@click="fnSelectMenu(menu)"
|
||||
>
|
||||
<div class="menu-icon">
|
||||
<!-- 处理自定义图标字体 -->
|
||||
<IconFont
|
||||
v-if="menu.icon && menu.icon.startsWith('icon-')"
|
||||
:type="menu.icon"
|
||||
class="icon"
|
||||
/>
|
||||
<!-- 处理Ant Design图标组件 -->
|
||||
<component
|
||||
:is="menu.icon"
|
||||
v-else-if="menu.icon && !menu.icon.startsWith('icon-')"
|
||||
class="icon"
|
||||
/>
|
||||
<!-- 默认图标 -->
|
||||
<FolderOutlined v-else class="icon" />
|
||||
</div>
|
||||
<div class="menu-info">
|
||||
<div class="menu-title">{{ menu.title }}</div>
|
||||
</div>
|
||||
<div class="menu-action">
|
||||
<RightOutlined />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ProModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getMenuData, clearMenuItem } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import IconFont from '@/components/IconFont/index.vue';
|
||||
import { ref, computed, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useRouterStore from '@/store/modules/router';
|
||||
|
||||
const { t } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
const routerStore = useRouterStore();
|
||||
const router = useRouter();
|
||||
|
||||
// 搜索相关状态
|
||||
const searchModalOpen = ref<boolean>(false);
|
||||
const searchKeyword = ref<string>('');
|
||||
const searchInputRef = ref();
|
||||
|
||||
// 获取所有可搜索的菜单项
|
||||
const searchableMenus = computed(() => {
|
||||
const menus: Array<{
|
||||
title: string;
|
||||
path: string;
|
||||
icon?: string;
|
||||
key: string;
|
||||
routeName?: string;
|
||||
}> = [];
|
||||
|
||||
// 使用和BasicLayout完全相同的菜单数据获取逻辑
|
||||
const getMenuDataForSearch = () => {
|
||||
// 动态路由添加到菜单面板
|
||||
const rootRoute = router.getRoutes().find(r => r.name === 'Root');
|
||||
if (rootRoute) {
|
||||
const children = routerStore.setRootRouterData(rootRoute.children);
|
||||
const buildRouterData = routerStore.buildRouterData;
|
||||
if (buildRouterData.length > 0) {
|
||||
rootRoute.children = children.concat(buildRouterData);
|
||||
} else {
|
||||
rootRoute.children = children;
|
||||
}
|
||||
|
||||
// 根据用户角色过滤
|
||||
if (!userStore.roles.includes('tenant')) {
|
||||
rootRoute.children = rootRoute.children.filter(
|
||||
item => item.name !== 'Index'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用和BasicLayout相同的处理方法
|
||||
const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
|
||||
return menuData;
|
||||
};
|
||||
|
||||
// 递归获取所有路由项
|
||||
const getRouteItems = (routes: any[], parentPath = '') => {
|
||||
routes.forEach((route: any) => {
|
||||
// 跳过根路径和不显示的菜单
|
||||
if (route.path === '/' || route.path === '' || route.meta?.hideInMenu) {
|
||||
// 继续处理子路由
|
||||
if (route.children && route.children.length > 0) {
|
||||
getRouteItems(route.children, route.path === '/' ? '' : route.path);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建完整路径
|
||||
let fullPath = route.path;
|
||||
if (!route.path.startsWith('/') && parentPath) {
|
||||
fullPath = `${parentPath}/${route.path}`;
|
||||
}
|
||||
|
||||
// 只添加有meta.title的路由项
|
||||
if (route.meta && route.meta.title) {
|
||||
try {
|
||||
const title = t(route.meta.title);
|
||||
// 避免重复添加已存在的路由
|
||||
const exists = menus.find(
|
||||
m => m.routeName === route.name || m.path === fullPath
|
||||
);
|
||||
if (!exists) {
|
||||
menus.push({
|
||||
title: title,
|
||||
path: fullPath,
|
||||
icon: route.meta.icon,
|
||||
key: route.name || fullPath,
|
||||
routeName: route.name,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果翻译失败,使用原始标题
|
||||
const exists = menus.find(
|
||||
m => m.routeName === route.name || m.path === fullPath
|
||||
);
|
||||
if (!exists) {
|
||||
menus.push({
|
||||
title: route.meta.title,
|
||||
path: fullPath,
|
||||
icon: route.meta.icon,
|
||||
key: route.name || fullPath,
|
||||
routeName: route.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理子路由
|
||||
if (route.children && route.children.length > 0) {
|
||||
getRouteItems(route.children, fullPath);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 使用和菜单面板相同的数据源
|
||||
const menuRoutes = getMenuDataForSearch();
|
||||
|
||||
if (menuRoutes && menuRoutes.length > 0) {
|
||||
getRouteItems(menuRoutes);
|
||||
}
|
||||
|
||||
return menus;
|
||||
});
|
||||
|
||||
// 过滤的菜单项
|
||||
const filteredMenus = computed(() => {
|
||||
if (!searchKeyword.value.trim()) {
|
||||
return searchableMenus.value.slice(0, 10); // 默认显示前10个
|
||||
}
|
||||
|
||||
// return searchableMenus.value.filter(menu =>
|
||||
// menu.title.toLowerCase().includes(searchKeyword.value.toLowerCase()) ||
|
||||
// menu.path.toLowerCase().includes(searchKeyword.value.toLowerCase())
|
||||
// ).slice(0, 10);
|
||||
const value = searchKeyword.value.toLowerCase();
|
||||
return searchableMenus.value
|
||||
.filter(menu => menu.title.toLowerCase().includes(value))
|
||||
.slice(0, 10);
|
||||
});
|
||||
|
||||
/**打开搜索弹窗 */
|
||||
function fnClickSearch() {
|
||||
searchModalOpen.value = true;
|
||||
searchKeyword.value = '';
|
||||
|
||||
nextTick(() => {
|
||||
searchInputRef.value?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**关闭搜索弹窗 */
|
||||
function fnCloseSearch() {
|
||||
searchModalOpen.value = false;
|
||||
searchKeyword.value = '';
|
||||
}
|
||||
|
||||
/**选择菜单项并跳转 */
|
||||
function fnSelectMenu(menu: any) {
|
||||
try {
|
||||
// 优先使用路由名称跳转
|
||||
if (menu.routeName) {
|
||||
router.push({ name: menu.routeName });
|
||||
} else {
|
||||
router.push(menu.path);
|
||||
}
|
||||
fnCloseSearch();
|
||||
} catch (error) {
|
||||
// 如果跳转失败,尝试直接使用路径
|
||||
try {
|
||||
router.push(menu.path);
|
||||
fnCloseSearch();
|
||||
} catch (secondError) {
|
||||
// 可以在这里添加错误提示
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**键盘事件处理 */
|
||||
function fnHandleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
fnCloseSearch();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search-modal-content {
|
||||
.search-results {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
|
||||
.no-results {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.menu-action {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
margin-right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-color: #f0f2f5;
|
||||
border-radius: 6px;
|
||||
|
||||
.icon {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.menu-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
margin-bottom: 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.menu-path {
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-action {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
color: #bfbfbf;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 暗黑主题支持
|
||||
[data-theme='dark'] {
|
||||
.search-modal-content {
|
||||
.search-results {
|
||||
.menu-list {
|
||||
.menu-item {
|
||||
&:hover {
|
||||
background-color: #303030;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
background-color: #262626;
|
||||
|
||||
.icon {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-info {
|
||||
.menu-title {
|
||||
color: #f0f0f0;
|
||||
}
|
||||
|
||||
.menu-path {
|
||||
color: #8c8c8c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -61,7 +61,7 @@ type OptionsType = {
|
||||
/**请求地址 */
|
||||
url: string;
|
||||
/**请求方法 */
|
||||
method: 'get' | 'post' | 'put' | 'delete' | 'PATCH' | 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||
method: 'get' | 'post' | 'put' | 'delete' | 'PATCH';
|
||||
/**请求头 */
|
||||
headers?: HeadersInit;
|
||||
/**地址栏参数 */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import useUserStore from '@/store/modules/user';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useRouterStore from '@/store/modules/router';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import useNeListStore from '@/store/modules/ne_list';
|
||||
|
||||
// NProgress Configuration
|
||||
NProgress.configure({ showSpinner: false });
|
||||
@@ -32,15 +31,12 @@ const constantRoutes: RouteRecordRaw[] = [
|
||||
name: 'Root',
|
||||
component: BasicLayout,
|
||||
redirect: '/index',
|
||||
// redirect: '/monitor/dashboard',
|
||||
children: [
|
||||
{
|
||||
path: '/index',
|
||||
name: 'Index',
|
||||
meta: { title: 'router.index', icon: 'icon-pcduan' },
|
||||
component: () => import('@/views/index.vue'),
|
||||
// meta: { title: 'router.index', icon: 'icon-pcduan', hideInMenu: true },
|
||||
// redirect: '/monitor/dashboard',
|
||||
},
|
||||
{
|
||||
path: '/account',
|
||||
@@ -68,16 +64,11 @@ const constantRoutes: RouteRecordRaw[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/login/oauth2',
|
||||
name: 'LoginOAuth2', // 第三方认证重定向
|
||||
component: () => import('@/views/login/oauth2.vue'),
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
meta: { title: 'router.login' },
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
component: () => import('@/views/login.vue'),
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
@@ -154,10 +145,17 @@ const router = createRouter({
|
||||
});
|
||||
|
||||
/**全局路由-后置守卫 */
|
||||
// 在路由后置守卫中清理计数器
|
||||
router.afterEach((to, from, failure) => {
|
||||
NProgress.done();
|
||||
|
||||
// 清理成功导航的重定向计数
|
||||
if (!failure) {
|
||||
const redirectKey = `${from.path}->${to.path}`;
|
||||
redirectCount.delete(redirectKey);
|
||||
}
|
||||
|
||||
const title = to.meta?.title;
|
||||
// 设置标题
|
||||
if (!failure && title) {
|
||||
useAppStore().setTitle(to.meta.title);
|
||||
}
|
||||
@@ -166,17 +164,34 @@ router.afterEach((to, from, failure) => {
|
||||
/**无Token可访问页面地址白名单 */
|
||||
const WHITE_LIST: string[] = [
|
||||
'/login',
|
||||
'/login/oauth2',
|
||||
'/auth-redirect',
|
||||
'/help',
|
||||
'/register',
|
||||
'/quick-start',
|
||||
'/trace-task-hlr',
|
||||
];
|
||||
|
||||
const redirectCount = new Map<string, number>();
|
||||
const MAX_REDIRECT_COUNT = 3; // 最大重定向次数
|
||||
|
||||
/**全局路由-前置守卫 */
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start();
|
||||
|
||||
// 检查是否是 F5 刷新(from.name 为 null 且 to.path 不是根路径)
|
||||
const isRefresh = !from.name && from.path === '/';
|
||||
|
||||
// 重定向计数检查
|
||||
const redirectKey = `${from.path}->${to.path}`;
|
||||
const currentCount = redirectCount.get(redirectKey) || 0;
|
||||
|
||||
if (currentCount > MAX_REDIRECT_COUNT) {
|
||||
console.warn(`检测到重定向循环: ${redirectKey},强制跳转到首页`);
|
||||
redirectCount.delete(redirectKey);
|
||||
next({ name: 'Index', replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取系统配置信息
|
||||
const appStore = useAppStore();
|
||||
if (!appStore.loginBackground) {
|
||||
@@ -212,42 +227,321 @@ router.beforeEach(async (to, from, next) => {
|
||||
|
||||
// 有Token
|
||||
if (token) {
|
||||
// 防止重复访问登录页面
|
||||
if (to.path === '/login') {
|
||||
next({ name: 'Index' });
|
||||
} else {
|
||||
// 判断当前用户是否有角色信息
|
||||
const user = useUserStore();
|
||||
if (user.roles && user.roles.length === 0) {
|
||||
try {
|
||||
useNeInfoStore().fnRefreshNelist();
|
||||
useNeListStore().fnNelistRefresh();
|
||||
|
||||
// 获取用户信息
|
||||
await useNeInfoStore().fnNelist();
|
||||
await user.fnGetInfo();
|
||||
// 获取路由信息
|
||||
const accessRoutes = await useRouterStore().generateRoutes();
|
||||
// 根据后台配置生成可访问的路由表
|
||||
|
||||
if (accessRoutes && accessRoutes.length !== 0) {
|
||||
for (const route of accessRoutes) {
|
||||
// 动态添加可访问路由表,http开头会异常
|
||||
if (!validHttp(route.path)) {
|
||||
router.addRoute(route);
|
||||
}
|
||||
}
|
||||
|
||||
// F5 刷新时,如果目标路由有效,直接跳转,不进行重定向
|
||||
if (isRefresh && await isRouteAccessible(to, accessRoutes)) {
|
||||
console.log(`F5 刷新,目标路由有效,直接跳转: ${to.path}`);
|
||||
next({ ...to, replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (await isRouteAccessible(to, accessRoutes)) {
|
||||
next({ ...to, replace: true });
|
||||
} else {
|
||||
// F5 刷新时,如果目标路由无效,优先跳转到首页
|
||||
if (isRefresh) {
|
||||
console.log(`F5 刷新,目标路由无效,跳转到首页: ${to.path}`);
|
||||
next({ name: 'Index', replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
const validRedirect = findValidRedirect(to, accessRoutes);
|
||||
if (validRedirect && validRedirect !== to.path) {
|
||||
redirectCount.set(redirectKey, currentCount + 1);
|
||||
console.log(`重定向到有效路由: ${to.path} -> ${validRedirect}`);
|
||||
next({ path: validRedirect, replace: true });
|
||||
} else {
|
||||
next({ name: 'Index', replace: true });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
next({ name: 'Index', replace: true });
|
||||
}
|
||||
// 刷新替换原先路由,确保addRoutes已完成
|
||||
next({ ...to, replace: true });
|
||||
} catch (error: any) {
|
||||
console.error(`[${to.path}]: ${error.message}`);
|
||||
await user.fnLogOut();
|
||||
next({ name: 'Login' });
|
||||
} catch (error) {
|
||||
console.error('Route guard error:', error);
|
||||
next(`/login?redirect=${to.fullPath}`);
|
||||
}
|
||||
} else if (
|
||||
to.meta.neType &&
|
||||
to.meta.neType.length > 0 &&
|
||||
!useNeInfoStore().fnHasNe(to.meta.neType)
|
||||
) {
|
||||
// 找到有效的替代路由
|
||||
const validRedirect = findValidAlternative(to);
|
||||
if (validRedirect && validRedirect !== to.path) {
|
||||
redirectCount.set(redirectKey, currentCount + 1);
|
||||
console.log(`403 重定向: ${to.path} -> ${validRedirect}`);
|
||||
next({ path: validRedirect, replace: true });
|
||||
} else {
|
||||
console.log(`无有效替代路由,跳转到权限错误页面: ${to.path}`);
|
||||
next({ name: 'NotPermission' });
|
||||
}
|
||||
} else {
|
||||
// 清除重定向计数
|
||||
redirectCount.clear();
|
||||
next();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 检查路由是否可访问
|
||||
*/
|
||||
async function isRouteAccessible(to: any, accessRoutes: any[]): Promise<boolean> {
|
||||
// 检查路由是否存在于 accessRoutes 中
|
||||
const routeExists = findRouteInAccessRoutes(to.path, accessRoutes);
|
||||
|
||||
console.log(`检查路由可访问性: ${to.path}`, routeExists ? '找到' : '未找到');
|
||||
|
||||
if (!routeExists) return false;
|
||||
|
||||
// 检查网元类型
|
||||
if (to.meta?.neType && to.meta.neType.length > 0) {
|
||||
const hasNe = useNeInfoStore().fnHasNe(to.meta.neType);
|
||||
console.log(`网元类型检查: ${to.meta.neType}`, hasNe ? '有效' : '无效');
|
||||
return hasNe;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在访问路由中查找指定路径
|
||||
*/
|
||||
function findRouteInAccessRoutes(targetPath: string, routes: any[], parentPath: string = ''): any {
|
||||
for (const route of routes) {
|
||||
// 构建完整路径
|
||||
let fullPath = route.path;
|
||||
|
||||
// 如果不是绝对路径,需要拼接父路径
|
||||
if (!fullPath.startsWith('/')) {
|
||||
const cleanParentPath = parentPath.replace(/\/$/, ''); // 移除末尾斜杠
|
||||
const cleanRoutePath = fullPath.replace(/^\//, ''); // 移除开头斜杠
|
||||
fullPath = cleanParentPath + '/' + cleanRoutePath;
|
||||
}
|
||||
|
||||
// 标准化路径,移除多余的斜杠
|
||||
fullPath = fullPath.replace(/\/+/g, '/');
|
||||
|
||||
console.log(`匹配路径: ${targetPath} vs ${fullPath}`);
|
||||
|
||||
if (fullPath === targetPath) {
|
||||
return route;
|
||||
}
|
||||
|
||||
// 递归查找子路由
|
||||
if (route.children && route.children.length > 0) {
|
||||
const found = findRouteInAccessRoutes(targetPath, route.children, fullPath);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找有效的重定向目标
|
||||
*/
|
||||
function findValidRedirect(to: any, accessRoutes: any[]): string | null {
|
||||
const neStore = useNeInfoStore();
|
||||
|
||||
console.log(`查找重定向目标: ${to.path}`);
|
||||
|
||||
// 1. 查找父路由的 redirect
|
||||
const parentRoute = findParentRouteWithRedirect(to.path, accessRoutes);
|
||||
|
||||
if (parentRoute?.redirect) {
|
||||
console.log(`找到父路由重定向: ${parentRoute.path} -> ${parentRoute.redirect}`);
|
||||
|
||||
// 验证 redirect 目标是否有效
|
||||
const redirectTarget = findRouteInAccessRoutes(parentRoute.redirect, accessRoutes);
|
||||
if (redirectTarget &&
|
||||
(!redirectTarget.meta?.neType || neStore.fnHasNe(redirectTarget.meta.neType))) {
|
||||
return parentRoute.redirect;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 查找同级的第一个有效路由
|
||||
const siblingRoute = findFirstValidSibling(to.path, accessRoutes);
|
||||
if (siblingRoute) {
|
||||
console.log(`找到同级有效路由: ${siblingRoute}`);
|
||||
return siblingRoute;
|
||||
}
|
||||
|
||||
// 3. 查找根级别的第一个有效路由
|
||||
const rootRoute = findFirstValidRootRoute(accessRoutes);
|
||||
if (rootRoute) {
|
||||
console.log(`找到根级有效路由: ${rootRoute}`);
|
||||
return rootRoute;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找具有 redirect 的父路由
|
||||
*/
|
||||
function findParentRouteWithRedirect(targetPath: string, routes: any[], parentPath: string = ''): any {
|
||||
for (const route of routes) {
|
||||
let fullPath = route.path;
|
||||
|
||||
if (!fullPath.startsWith('/')) {
|
||||
const cleanParentPath = parentPath.replace(/\/$/, '');
|
||||
const cleanRoutePath = fullPath.replace(/^\//, '');
|
||||
fullPath = cleanParentPath + '/' + cleanRoutePath;
|
||||
}
|
||||
|
||||
fullPath = fullPath.replace(/\/+/g, '/');
|
||||
|
||||
// 检查是否是父路径且有 redirect
|
||||
if (targetPath.startsWith(fullPath) &&
|
||||
route.redirect &&
|
||||
fullPath !== targetPath) {
|
||||
return { ...route, path: fullPath };
|
||||
}
|
||||
|
||||
// 递归查找
|
||||
if (route.children && route.children.length > 0) {
|
||||
const found = findParentRouteWithRedirect(targetPath, route.children, fullPath);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找同级的第一个有效路由
|
||||
*/
|
||||
function findFirstValidSibling(targetPath: string, routes: any[], parentPath: string = ''): string | null {
|
||||
const neStore = useNeInfoStore();
|
||||
|
||||
// 获取父路径
|
||||
const parentRouteResult = findParentOfTarget(targetPath, routes, parentPath);
|
||||
if (!parentRouteResult) return null;
|
||||
|
||||
const { parentRoute, parentFullPath } = parentRouteResult;
|
||||
|
||||
if (parentRoute.children) {
|
||||
for (const sibling of parentRoute.children) {
|
||||
let siblingFullPath = sibling.path;
|
||||
|
||||
if (!siblingFullPath.startsWith('/')) {
|
||||
const cleanParentPath = parentFullPath.replace(/\/$/, '');
|
||||
const cleanSiblingPath = siblingFullPath.replace(/^\//, '');
|
||||
siblingFullPath = cleanParentPath + '/' + cleanSiblingPath;
|
||||
}
|
||||
|
||||
siblingFullPath = siblingFullPath.replace(/\/+/g, '/');
|
||||
|
||||
if (siblingFullPath !== targetPath &&
|
||||
(!sibling.meta?.neType || neStore.fnHasNe(sibling.meta.neType))) {
|
||||
return siblingFullPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找目标路径的父路由
|
||||
*/
|
||||
function findParentOfTarget(targetPath: string, routes: any[], parentPath: string = ''): { parentRoute: any, parentFullPath: string } | null {
|
||||
for (const route of routes) {
|
||||
let fullPath = route.path;
|
||||
|
||||
if (!fullPath.startsWith('/')) {
|
||||
const cleanParentPath = parentPath.replace(/\/$/, '');
|
||||
const cleanRoutePath = fullPath.replace(/^\//, '');
|
||||
fullPath = cleanParentPath + '/' + cleanRoutePath;
|
||||
}
|
||||
|
||||
fullPath = fullPath.replace(/\/+/g, '/');
|
||||
|
||||
// 检查子路由
|
||||
if (route.children && route.children.length > 0) {
|
||||
for (const child of route.children) {
|
||||
let childFullPath = child.path;
|
||||
|
||||
if (!childFullPath.startsWith('/')) {
|
||||
const cleanParentPath = fullPath.replace(/\/$/, '');
|
||||
const cleanChildPath = childFullPath.replace(/^\//, '');
|
||||
childFullPath = cleanParentPath + '/' + cleanChildPath;
|
||||
}
|
||||
|
||||
childFullPath = childFullPath.replace(/\/+/g, '/');
|
||||
|
||||
if (childFullPath === targetPath) {
|
||||
return { parentRoute: route, parentFullPath: fullPath };
|
||||
}
|
||||
}
|
||||
|
||||
// 递归查找
|
||||
const found = findParentOfTarget(targetPath, route.children, fullPath);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找根级别的第一个有效路由
|
||||
*/
|
||||
function findFirstValidRootRoute(routes: any[]): string | null {
|
||||
const neStore = useNeInfoStore();
|
||||
|
||||
for (const route of routes) {
|
||||
if (!route.meta?.neType || neStore.fnHasNe(route.meta.neType)) {
|
||||
if (route.children && route.children.length > 0) {
|
||||
// 如果有子路由,返回第一个有效子路由的完整路径
|
||||
for (const child of route.children) {
|
||||
let childFullPath = child.path;
|
||||
|
||||
if (!childFullPath.startsWith('/')) {
|
||||
const cleanParentPath = route.path.replace(/\/$/, '');
|
||||
const cleanChildPath = childFullPath.replace(/^\//, '');
|
||||
childFullPath = cleanParentPath + '/' + cleanChildPath;
|
||||
}
|
||||
|
||||
childFullPath = childFullPath.replace(/\/+/g, '/');
|
||||
|
||||
if (!child.meta?.neType || neStore.fnHasNe(child.meta.neType)) {
|
||||
return childFullPath;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return route.path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找有效的替代路由(用于 403 情况)
|
||||
*/
|
||||
function findValidAlternative(to: any): string | null {
|
||||
const routerStore = useRouterStore();
|
||||
const buildRoutes = routerStore.buildRouterData;
|
||||
|
||||
return findValidRedirect(to, buildRoutes);
|
||||
}
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import {
|
||||
RESULT_CODE_SUCCESS,
|
||||
RESULT_MSG_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { parseDataToOptions } from '@/utils/parse-tree-utils';
|
||||
|
||||
/**网元列表信息类型 */
|
||||
type NeList = {
|
||||
/**网元列表 */
|
||||
neList: Record<string, any>[];
|
||||
/**级联options树结构 */
|
||||
neCascaderOptions: Record<string, any>[];
|
||||
/**选择器单级父类型 */
|
||||
neSelectOtions: Record<string, any>[];
|
||||
};
|
||||
|
||||
const useNeListStore = defineStore('ne_list', {
|
||||
state: (): NeList => ({
|
||||
neList: [],
|
||||
neCascaderOptions: [],
|
||||
neSelectOtions: [],
|
||||
}),
|
||||
getters: {
|
||||
/**
|
||||
* 网元列表
|
||||
* @param state 内部属性不用传入
|
||||
* @returns 级联options
|
||||
*/
|
||||
getNeList(state) {
|
||||
return state.neList;
|
||||
},
|
||||
/**
|
||||
* 获取级联options树结构
|
||||
* @param state 内部属性不用传入
|
||||
* @returns 级联options
|
||||
*/
|
||||
getNeCascaderOptions(state) {
|
||||
return state.neCascaderOptions;
|
||||
},
|
||||
/**
|
||||
* 选择器单级父类型
|
||||
* @param state 内部属性不用传入
|
||||
* @returns 选择options
|
||||
*/
|
||||
getNeSelectOtions(state) {
|
||||
return state.neSelectOtions;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 刷新网元列表
|
||||
async fnNelistRefresh() {
|
||||
this.neList = [];
|
||||
return await this.fnNelist();
|
||||
},
|
||||
// 获取网元列表
|
||||
async fnNelist() {
|
||||
// 有数据不请求
|
||||
if (this.neList.length > 0) {
|
||||
return {
|
||||
code: RESULT_CODE_SUCCESS,
|
||||
msg: RESULT_MSG_SUCCESS['en_US'],
|
||||
data: this.neList,
|
||||
};
|
||||
}
|
||||
const res = await listAllNeInfo({
|
||||
bandStatus: false,
|
||||
bandHost: false,
|
||||
});
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 原始列表
|
||||
this.neList = JSON.parse(JSON.stringify(res.data));
|
||||
|
||||
// 转级联数据
|
||||
const options = parseDataToOptions(
|
||||
res.data,
|
||||
'neType',
|
||||
'neName',
|
||||
'neId'
|
||||
);
|
||||
this.neCascaderOptions = options;
|
||||
|
||||
// 转选择器单级父类型
|
||||
this.neSelectOtions = options.map(item => {
|
||||
return {
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
};
|
||||
});
|
||||
}
|
||||
return res;
|
||||
},
|
||||
/**
|
||||
* 含有网元
|
||||
* @param metaNeType ['udm', 'ims', 'udm+ims', 'SGWC'] 支持大小写
|
||||
* @returns boolean
|
||||
*/
|
||||
fnHasNe(metaNeType: string[]) {
|
||||
if (this.neList.length > 0) {
|
||||
const neTypes = this.neSelectOtions.map(item => item.value);
|
||||
let match = false; // 匹配
|
||||
for (const netype of metaNeType) {
|
||||
if (netype.indexOf('+') > -1) {
|
||||
metaNeType = netype.split('+');
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
// 同时匹配
|
||||
return metaNeType.every(item => neTypes.includes(item.toUpperCase()));
|
||||
}
|
||||
// 有一种
|
||||
return metaNeType.some(item => neTypes.includes(item.toUpperCase()));
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default useNeListStore;
|
||||
@@ -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,11 +104,37 @@ 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;
|
||||
},
|
||||
/**
|
||||
* 含有网元
|
||||
* @param metaNeType ['udm', 'ims', 'udm+ims', 'SGWC'] 支持大小写
|
||||
* @returns boolean
|
||||
*/
|
||||
fnHasNe(metaNeType: string[]) {
|
||||
if (this.neList.length > 0) {
|
||||
const neTypes = this.neSelectOtions.map(item => item.value);
|
||||
let match = false; // 匹配
|
||||
for (const netype of metaNeType) {
|
||||
if (netype.indexOf('+') > -1) {
|
||||
metaNeType = netype.split('+');
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
// 同时匹配
|
||||
return metaNeType.every(item => neTypes.includes(item.toUpperCase()));
|
||||
}
|
||||
// 有一种
|
||||
return metaNeType.some(item => neTypes.includes(item.toUpperCase()));
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@ import type {
|
||||
RouteComponent,
|
||||
RouteLocationRaw,
|
||||
RouteMeta,
|
||||
RouteRecord,
|
||||
RouteRecordRaw,
|
||||
} from 'vue-router';
|
||||
import { getRouters } from '@/api/router';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import BasicLayout from '@/layouts/BasicLayout.vue';
|
||||
import BlankLayout from '@/layouts/BlankLayout.vue';
|
||||
import LinkLayout from '@/layouts/LinkLayout.vue';
|
||||
@@ -48,19 +50,95 @@ const useRouterStore = defineStore('router', {
|
||||
async generateRoutes() {
|
||||
const res = await getRouters();
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
const buildRoutes = buildRouters(res.data.concat());
|
||||
// 获取当前网元类型
|
||||
const neTypes = useNeInfoStore().getNeSelectOtions.map(v => v.value);
|
||||
// 先过滤
|
||||
const filteredRoutes = this.clearMenuItemByNeList(res.data.concat(), neTypes);
|
||||
// 再 build
|
||||
const buildRoutes = buildRouters(filteredRoutes);
|
||||
this.buildRouterData = buildRoutes;
|
||||
return buildRoutes;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
/**
|
||||
* 根据网元类型过滤菜单
|
||||
* @param routes 经过clearMenuItem(router.getRoutes())处理
|
||||
* @param neTypes 网元类型
|
||||
* @returns 过滤后的菜单
|
||||
*/
|
||||
clearMenuItemByNeList(
|
||||
routes: RouteRecord[] | RouteRecordRaw[],
|
||||
neTypes: string[]
|
||||
): RouteRecordRaw[] {
|
||||
return routes
|
||||
.map((item: RouteRecord | RouteRecordRaw) => {
|
||||
const finalItem = { ...item };
|
||||
// 过滤网元类型
|
||||
if (
|
||||
Array.isArray(finalItem.meta?.neType) &&
|
||||
finalItem.meta?.neType.length > 0
|
||||
) {
|
||||
let metaNeType: string[] = finalItem.meta.neType;
|
||||
let match = false; // 匹配
|
||||
for (const netype of metaNeType) {
|
||||
if (netype.indexOf('+') > -1) {
|
||||
metaNeType = netype.split('+');
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match && !metaNeType.every(item => neTypes.includes(item))) {
|
||||
// 同时匹配
|
||||
return null;
|
||||
} else if (!metaNeType.some(item => neTypes.includes(item))) {
|
||||
// 有一种
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 有子菜单进行递归
|
||||
if (finalItem.children && finalItem.children.length > 0) {
|
||||
const children = this.clearMenuItemByNeList(
|
||||
finalItem.children,
|
||||
neTypes
|
||||
);
|
||||
// 如果子菜单都被过滤掉了,就不显示
|
||||
if (children.length === 0) {
|
||||
return null;
|
||||
}
|
||||
finalItem.children = children;
|
||||
|
||||
// 只重定向到第一个可用的子菜单
|
||||
// finalItem.redirect = finalItem.children[0].path;
|
||||
// console.log(`finalItem.redirect`, finalItem.redirect);
|
||||
// 如果有子菜单,且没有重定向,则设置重定向到第一个子菜单
|
||||
if (children.length > 0) {
|
||||
let childPath = children[0].path;
|
||||
if (!childPath.startsWith('/')) {
|
||||
// 确保父路径以 / 结尾,子路径不以 / 开头
|
||||
const parentPath = finalItem.path.replace(/\/$/, '');
|
||||
childPath = parentPath + '/' + childPath.replace(/^\//, '');
|
||||
}
|
||||
finalItem.redirect = childPath;
|
||||
}
|
||||
|
||||
return finalItem;
|
||||
}
|
||||
|
||||
delete finalItem.children;
|
||||
return finalItem;
|
||||
})
|
||||
.filter(item => item) as RouteRecordRaw[];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/**异步路由类型 */
|
||||
type RecordRaws = {
|
||||
path: string;
|
||||
name: string;
|
||||
name?: string;
|
||||
meta: RouteMeta;
|
||||
redirect: RouteLocationRaw;
|
||||
component: string;
|
||||
@@ -75,17 +153,18 @@ type RecordRaws = {
|
||||
* @param recordRaws 异步路由列表
|
||||
* @returns 可添加的路由列表
|
||||
*/
|
||||
function buildRouters(recordRaws: RecordRaws[]): RouteRecordRaw[] {
|
||||
function buildRouters(recordRaws: RouteRecordRaw[]): RouteRecordRaw[] {
|
||||
const routers: RouteRecordRaw[] = [];
|
||||
for (const item of recordRaws) {
|
||||
// 过滤旧前端菜单 是layui的菜单跳过
|
||||
if (['', '/page"'].includes(item.path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 路由页面组件
|
||||
let component: RouteComponent = {};
|
||||
if (item.component) {
|
||||
const comp = item.component;
|
||||
const comp = item.component as unknown as string; // 添加类型断言
|
||||
if (comp === MENU_COMPONENT_LAYOUT_BASIC) {
|
||||
component = BasicLayout;
|
||||
} else if (comp === MENU_COMPONENT_LAYOUT_BLANK) {
|
||||
@@ -102,6 +181,17 @@ function buildRouters(recordRaws: RecordRaws[]): RouteRecordRaw[] {
|
||||
let children: RouteRecordRaw[] = [];
|
||||
if (item.children && item.children.length > 0) {
|
||||
children = buildRouters(item.children);
|
||||
|
||||
// 如果没有 redirect 但有子菜单,设置 redirect 到第一个子菜单
|
||||
if (!item.redirect && children.length > 0) {
|
||||
let childPath = children[0].path;
|
||||
if (!childPath?.startsWith('/')) {
|
||||
childPath = item.path.replace(/\/$/, '') + '/' + (childPath || '').replace(/^\//, '');
|
||||
}
|
||||
|
||||
// 修改 item 的 redirect(需要类型断言)
|
||||
(item as any).redirect = childPath;
|
||||
}
|
||||
}
|
||||
|
||||
// 对元数据特殊参数进行处理
|
||||
@@ -109,7 +199,9 @@ function buildRouters(recordRaws: RecordRaws[]): RouteRecordRaw[] {
|
||||
if (!metaIcon.startsWith('icon-')) {
|
||||
metaIcon = '';
|
||||
}
|
||||
item.meta = Object.assign(item.meta, {
|
||||
|
||||
// 更新 meta(需要类型断言)
|
||||
(item as any).meta = Object.assign(item.meta || {}, {
|
||||
icon: metaIcon,
|
||||
});
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ type UserInfo = {
|
||||
email: string;
|
||||
/**用户性别 */
|
||||
sex: string | undefined;
|
||||
/**用户类型 */
|
||||
userType: string;
|
||||
/**其他信息 */
|
||||
profile: Record<string, any>;
|
||||
};
|
||||
|
||||
const useUserStore = defineStore('user', {
|
||||
@@ -42,7 +42,7 @@ const useUserStore = defineStore('user', {
|
||||
phonenumber: '',
|
||||
email: '',
|
||||
sex: undefined,
|
||||
userType: 'System',
|
||||
profile: {},
|
||||
}),
|
||||
getters: {
|
||||
/**
|
||||
@@ -122,7 +122,6 @@ const useUserStore = defineStore('user', {
|
||||
this.phonenumber = user.phonenumber;
|
||||
this.email = user.email;
|
||||
this.sex = user.sex;
|
||||
this.userType = user.userType;
|
||||
|
||||
// 验证返回的roles是否是一个非空数组
|
||||
if (Array.isArray(roles) && roles.length > 0) {
|
||||
|
||||
2
src/typings/router.d.ts
vendored
@@ -9,5 +9,7 @@ declare module 'vue-router' {
|
||||
permissions?: string[];
|
||||
/**角色 */
|
||||
roles?: string[];
|
||||
/**网元类型信息 */
|
||||
neType?: string[];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,24 +37,6 @@ export async function dbGet(storeName: string, key: string) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**数据级缓存全部移除 */
|
||||
export async function dbClear(storeName: string) {
|
||||
if (!storeName ) {
|
||||
return false;
|
||||
}
|
||||
localforage.config({
|
||||
name: import.meta.env.VITE_APP_CODE,
|
||||
storeName: storeName,
|
||||
});
|
||||
try {
|
||||
await localforage.clear();
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**数据级缓存移除 */
|
||||
export async function dbRemove(storeName: string, key: string) {
|
||||
if (!storeName || !key) {
|
||||
|
||||
@@ -20,12 +20,8 @@ export const YYYYMMDDHHMMSS = 'YYYYMMDDHHmmss';
|
||||
/**年-月-日 时:分:秒 列如:2022-12-30 01:01:59 */
|
||||
export const YYYY_MM_DD_HH_MM_SS = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
/**特殊 列如:2025-04-28 08:00:46 GMT+08:00 */
|
||||
export const YYYY_MM_DD_HH_MM_SSZ = 'YYYY-MM-DD HH:mm:ss [GMT]Z';
|
||||
/**国际时间 列如:2022-12-30T01:01:59+08:00 */
|
||||
export const RFC3339 = 'YYYY-MM-DDTHH:mm:ssZ';
|
||||
/**国际时间 列如:Thu, Nov 14 2024 10:19 GMT+08:00 */
|
||||
export const RFC822Z = 'ddd, MMM DD YYYY HH:mm [GMT]Z';
|
||||
/**年-月-日 时:分:秒 列如:2022-12-30T01:01:59+08:00 */
|
||||
export const YYYY_MM_DD_HH_MM_SSZ = 'YYYY-MM-DD HH:mm:ssZZ';
|
||||
|
||||
/**
|
||||
* 格式时间字符串
|
||||
|
||||
@@ -170,6 +170,25 @@ export function parseSizeFromKbs(sizeByte: number, timeInterval: number): any {
|
||||
return [(realBit / 1000 / 1000).toFixed(2), ' Mbits/sec'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 位数据转换单位
|
||||
* @param bits 位Bit大小 64009540 = 512.08 MB
|
||||
* @returns xx B / KB / MB / GB / TB / PB / EB / ZB / YB
|
||||
*/
|
||||
export function parseSizeFromBits(bits: number | string): string {
|
||||
bits = Number(bits) || 0;
|
||||
if (bits <= 0) return '0 B';
|
||||
bits = bits * 8;
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
const unitIndex = Math.floor(Math.log2(bits) / 10);
|
||||
const value = bits / Math.pow(1000, unitIndex);
|
||||
const unti = units[unitIndex];
|
||||
if (unitIndex > 0) {
|
||||
return `${value.toFixed(2)} ${unti}`;
|
||||
}
|
||||
return `${value} ${unti}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数转换单位
|
||||
* @param byte 字节Byte大小 64009540 = 512.08 MB
|
||||
|
||||
@@ -7,9 +7,7 @@ import StyleLayout from './components/style-layout.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -31,7 +29,8 @@ onActivated(() => {
|
||||
// 调用时机为首次挂载
|
||||
// 以及每次从缓存中被重新插入时
|
||||
fnLocale();
|
||||
});
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -44,15 +43,12 @@ onActivated(() => {
|
||||
<a-tab-pane
|
||||
key="reset-passwd"
|
||||
:tab="t('views.account.settings.resetPasswd')"
|
||||
v-if="userStore.userType === 'System'"
|
||||
>
|
||||
<ResetPasswd></ResetPasswd>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane
|
||||
key="style-layout"
|
||||
:tab="t('views.account.settings.styleLayout')"
|
||||
v-if="false"
|
||||
>
|
||||
<StyleLayout></StyleLayout>
|
||||
</a-tab-pane>
|
||||
|
||||
647
src/views/agentManage/callback/index.vue
Normal file
@@ -0,0 +1,647 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
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, parseStrToDate } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listCallBack, updateStatus } from '@/api/agentManage/callback';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import { create } from 'domain';
|
||||
import { commentProps } from 'ant-design-vue/es/comment';
|
||||
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
let dictStatus = ref<DictType[]>([]);
|
||||
|
||||
/**网元参数 */
|
||||
let neOtions = ref<Record<string, any>[]>([]);
|
||||
/**开始结束时间 */
|
||||
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neId: '001',
|
||||
/**记录时间 */
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
callerNumber: '',
|
||||
agentName: '',
|
||||
status: undefined,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
neId: '001',
|
||||
status: undefined,
|
||||
beginTime: '',
|
||||
endTime: '',
|
||||
callerNumber: '',
|
||||
agentName: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
queryRangePicker.value = ['', ''];
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.agentManage.callback.ticketId'),
|
||||
dataIndex: 'ticketId',
|
||||
align: 'center',
|
||||
width: 5,
|
||||
},
|
||||
{
|
||||
title: t('views.agentManage.callback.agentName'),
|
||||
dataIndex: 'agentName',
|
||||
align: 'center',
|
||||
width: 5,
|
||||
},
|
||||
{
|
||||
title: t('views.agentManage.callback.callerIdNumber'),
|
||||
dataIndex: 'callerNumber',
|
||||
align: 'center',
|
||||
width: 5,
|
||||
},
|
||||
{
|
||||
title: t('views.agentManage.callback.calleeIdNumber'),
|
||||
dataIndex: 'calleeNumber',
|
||||
align: 'center',
|
||||
width: 5,
|
||||
},
|
||||
{
|
||||
title: t('views.agentManage.callback.status'),
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
width: 4,
|
||||
},
|
||||
{
|
||||
title: t('views.agentManage.callback.startTime'),
|
||||
dataIndex: 'createdAt',
|
||||
align: 'center',
|
||||
width: 6,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value / 1000);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.agentManage.callback.updateTime'),
|
||||
dataIndex: 'updatedAt',
|
||||
align: 'center',
|
||||
width: 6,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value / 1000);
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: t('views.agentManage.callback.msdData'),
|
||||
// dataIndex: 'msdData',
|
||||
// align: 'center',
|
||||
// width: 6,
|
||||
// },
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'callbackId',
|
||||
align: 'left',
|
||||
width: 6,
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**查询备份信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
|
||||
if (!queryRangePicker.value) {
|
||||
queryRangePicker.value = ['', ''];
|
||||
}
|
||||
queryParams.startTime = queryRangePicker.value[0];
|
||||
queryParams.endTime = queryRangePicker.value[1];
|
||||
|
||||
listCallBack(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.data;
|
||||
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;
|
||||
/**更新加载数据按钮 loading */
|
||||
loadDataLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByView: false,
|
||||
openByEdit: false,
|
||||
title: 'callback',
|
||||
from: {
|
||||
id: undefined,
|
||||
ticketId: '',
|
||||
agentName: '',
|
||||
callerNumber: '',
|
||||
calleeNumber: '',
|
||||
agentEmail: '',
|
||||
agentMobile: '',
|
||||
status: '',
|
||||
createdAt: '',
|
||||
comment: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
loadDataLoading: false,
|
||||
});
|
||||
|
||||
function fnModalVisibleByEdit(record: any) {
|
||||
modalState.title = t('common.viewText') + t('views.agentManage.callback.title');
|
||||
modalState.openByEdit = true;
|
||||
modalState.from = Object.assign(modalState.from, record, {
|
||||
createdAt: record.createdAt ? parseDateToStr(record.createdAt / 1000) : '',
|
||||
});
|
||||
}
|
||||
|
||||
function fnModalOk() {
|
||||
const from = Object.assign({}, toRaw(modalState.from));
|
||||
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
updateStatus(from)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: modalState.title }),
|
||||
duration: 3,
|
||||
});
|
||||
fnGetList(1);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
fnModalCancel();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
function fnModalCancel() {
|
||||
modalState.openByEdit = false;
|
||||
//modalState.openByView = false;
|
||||
//modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDict('callback_status').then(res => {
|
||||
dictStatus.value = res;
|
||||
});
|
||||
// 获取网元网元列表
|
||||
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 === 'MF') {
|
||||
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();
|
||||
});
|
||||
});
|
||||
</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="PSAP对象" name="neId ">
|
||||
<a-select
|
||||
v-model:value="queryParams.neId"
|
||||
:options="neOtions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnGetList(1)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.agentManage.callback.callerIdNumber')"
|
||||
name="callerNumber"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.callerNumber"
|
||||
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.agentManage.callback.agentName')"
|
||||
name="agentName"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.agentName"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<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="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.agentManage.callback.status')"
|
||||
name="status"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="queryParams.status"
|
||||
:options="dictStatus"
|
||||
:allow-clear="true"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.agentManage.callback.startTime')"
|
||||
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> </template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<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: 1500, y: 400 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dictStatus" :value="record.status" />
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'callbackId'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.viewText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record)"
|
||||
>
|
||||
<template #icon><InfoCircleOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
:destroyOnClose="true"
|
||||
style="top: 0px"
|
||||
:body-style="{ maxHeight: '600px', 'overflow-y': 'auto' }"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:open="modalState.openByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@cancel="fnModalCancel"
|
||||
@ok="fnModalOk"
|
||||
:okText=" t('views.faultManage.activeAlarm.confirm') "
|
||||
>
|
||||
<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.agentManage.callback.ticketId')"
|
||||
name="ticketId"
|
||||
>
|
||||
<a-input
|
||||
:disabled="true"
|
||||
v-model:value="modalState.from.ticketId"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.agentManage.callback.agentName')"
|
||||
name="agentName"
|
||||
>
|
||||
<a-input
|
||||
:disabled="true"
|
||||
v-model:value="modalState.from.agentName"
|
||||
></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.agentManage.callback.callerIdNumber')"
|
||||
name="callerNumber"
|
||||
>
|
||||
<a-input
|
||||
:disabled="true"
|
||||
v-model:value="modalState.from.callerNumber"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.agentManage.callback.calleeIdNumber')"
|
||||
name="calleeNumber"
|
||||
>
|
||||
<a-input
|
||||
:disabled="true"
|
||||
v-model:value="modalState.from.calleeNumber"
|
||||
></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.agentManage.callback.agentEmail')"
|
||||
name="agentEmail"
|
||||
>
|
||||
<a-input
|
||||
:disabled="true"
|
||||
v-model:value="modalState.from.agentEmail"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.agentManage.callback.agentMobile')"
|
||||
name="agentMobile"
|
||||
>
|
||||
<a-input
|
||||
:disabled="true"
|
||||
v-model:value="modalState.from.agentMobile"
|
||||
></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.agentManage.callback.status')"
|
||||
name="status"
|
||||
>
|
||||
<a-input
|
||||
:disabled="true"
|
||||
v-model:value="modalState.from.status"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.agentManage.callback.startTime')"
|
||||
name="createdAt"
|
||||
>
|
||||
<a-input
|
||||
:disabled="true"
|
||||
v-model:value="modalState.from.createdAt"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.agentManage.callback.comment')"
|
||||
:label-col="{ span: 3 }"
|
||||
name="comment"
|
||||
>
|
||||
<a-input
|
||||
:disabled="true"
|
||||
v-model:value="modalState.from.comment"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.alarmTitleText {
|
||||
max-width: 300px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
216
src/views/agentManage/callings/index.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
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 { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listCallings } from '@/api/agentManage/callings';
|
||||
import useNeInfoStore from '@/store/modules/neinfo';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
|
||||
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neId: '001',
|
||||
/**记录时间 */
|
||||
beginTime: '',
|
||||
endTime: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.agentManage.callings.callerIdNumber'),
|
||||
dataIndex: 'callerIdNumber',
|
||||
align: 'center',
|
||||
width: 5,
|
||||
},
|
||||
{
|
||||
title: t('views.agentManage.callings.calleeIdNumber'),
|
||||
dataIndex: 'calleeIdNumber',
|
||||
align: 'center',
|
||||
width: 5,
|
||||
},
|
||||
{
|
||||
title: t('views.agentManage.callings.startTime'),
|
||||
dataIndex: 'createdTime',
|
||||
align: 'center',
|
||||
width: 6,
|
||||
},
|
||||
{
|
||||
title: t('views.agentManage.callings.answeredTime'),
|
||||
dataIndex: 'answeredTime',
|
||||
align: 'center',
|
||||
width: 4,
|
||||
},
|
||||
{
|
||||
title: t('views.agentManage.callings.callDuration'),
|
||||
dataIndex: 'callDuration',
|
||||
align: 'center',
|
||||
width: 5,
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**查询备份信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if(pageNum){
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
|
||||
|
||||
listCallings(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
tablePagination.total = res.total;
|
||||
tableState.data = res.data;
|
||||
if (tablePagination.total <=(queryParams.pageNum - 1) * tablePagination.pageSize &&queryParams.pageNum !== 1) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title> </template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<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: 1500, y: 400 }"
|
||||
>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
.alarmTitleText {
|
||||
max-width: 300px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
1711
src/views/cbc/cbe/index.vue
Normal file
@@ -149,6 +149,14 @@ async function fnGetList(reload: boolean = false) {
|
||||
|
||||
// 初始
|
||||
if (!reload) {
|
||||
// 选择第一个
|
||||
if (tableState.data.length > 0) {
|
||||
const id = tableState.data[0].id;
|
||||
fnTableSelectedRowKeys([id]);
|
||||
} else {
|
||||
fnTableSelectedRowKeys(tableState.selectedRowKeys);
|
||||
}
|
||||
|
||||
if (statusBar.value) {
|
||||
fnDesign(statusBar.value, rightNum, errorNum);
|
||||
}
|
||||
@@ -208,6 +216,44 @@ function fnDesign(container: HTMLElement, rightNum: number, errorNum: number) {
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
if (keys.length <= 0) return;
|
||||
const id = keys[0];
|
||||
const row: any = tableState.data.find((item: any) => item.id === id);
|
||||
if (!row) {
|
||||
message.error(t('views.index.neStatus'), 2);
|
||||
return;
|
||||
}
|
||||
const neState = row.serverState;
|
||||
if (!neState?.online) {
|
||||
message.error(t('views.index.neStatus'), 2);
|
||||
return;
|
||||
}
|
||||
tableState.selectedRowKeys = keys;
|
||||
// Mem 将KB转换为MB
|
||||
const totalMemInKB = neState.mem?.totalMem;
|
||||
const nfUsedMemInKB = neState.mem?.nfUsedMem;
|
||||
const sysMemUsageInKB = neState.mem?.sysMemUsage;
|
||||
const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100;
|
||||
const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
|
||||
const sysMemUsageInMB = Math.round((sysMemUsageInKB / 1024) * 100) / 100;
|
||||
|
||||
// CPU
|
||||
const nfCpu = neState.cpu?.nfCpuUsage;
|
||||
const sysCpu = neState.cpu?.sysCpuUsage;
|
||||
const nfCpuP = Math.round(nfCpu) / 100;
|
||||
const sysCpuP = Math.round(sysCpu) / 100;
|
||||
|
||||
serverState.value = Object.assign(
|
||||
{
|
||||
cpuUse: `NE:${nfCpuP}%; SYS:${sysCpuP}%`,
|
||||
memoryUse: `Total: ${totalMemInMB}MB; NE: ${nfUsedMemInMB}MB; SYS: ${sysMemUsageInMB}MB`,
|
||||
},
|
||||
neState
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化翻译转换
|
||||
*/
|
||||
@@ -231,7 +277,6 @@ let dict: {
|
||||
});
|
||||
|
||||
let timer: any;
|
||||
let timerFlag: boolean = false;
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('ne_info_status'), getDict('index_status')])
|
||||
@@ -247,7 +292,7 @@ onMounted(() => {
|
||||
fnLocale();
|
||||
await fnGetList(false);
|
||||
timer = setInterval(() => {
|
||||
if (timerFlag) return;
|
||||
if (!timer) return;
|
||||
fnGetList(true);
|
||||
}, 10_000); // 每隔10秒执行一次
|
||||
});
|
||||
@@ -257,7 +302,6 @@ onMounted(() => {
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
timerFlag = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -275,6 +319,12 @@ onBeforeUnmount(() => {
|
||||
:loading="tableState.loading"
|
||||
:pagination="false"
|
||||
:scroll="{ x: true }"
|
||||
:row-selection="{
|
||||
type: 'radio',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
@@ -291,6 +341,46 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<div style="width: 100%; min-height: 200px" ref="statusBar"></div>
|
||||
</a-card>
|
||||
<a-card
|
||||
:loading="tableState.loading"
|
||||
:title="`${t('views.index.mark')} - ${serverState.neName || 'OMC'}`"
|
||||
style="margin-top: 16px"
|
||||
size="small"
|
||||
>
|
||||
<a-descriptions
|
||||
bordered
|
||||
:column="1"
|
||||
:label-style="{ width: '160px' }"
|
||||
>
|
||||
<a-descriptions-item :label="t('views.index.hostName')">
|
||||
{{ serverState.hostname }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.osInfo')">
|
||||
{{ serverState.os }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.ipAddress')">
|
||||
{{ serverState.neIP }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.version')">
|
||||
{{ serverState.version }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.capability')">
|
||||
{{ serverState.capability }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.cpuUse')">
|
||||
{{ serverState.cpuUse }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.memoryUse')">
|
||||
{{ serverState.memoryUse }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.serialNum')">
|
||||
{{ serverState.sn }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.expiryDate')">
|
||||
{{ serverState.expire }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</PageContainer>
|
||||
|
||||
@@ -5,7 +5,6 @@ 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 TableColumnsDnd from '@/components/TableColumnsDnd/index.vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
@@ -19,9 +18,6 @@ import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import saveAs from 'file-saver';
|
||||
import PQueue from 'p-queue';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { TENANTADMIN_ROLE_KEY } from '@/constants/admin-constants';
|
||||
import { listTenant } from '@/api/system/tenant';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
const { t } = useI18n();
|
||||
@@ -53,13 +49,11 @@ let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
tenantNameArr: <Record<string, any>[]>[],
|
||||
/**网元类型 */
|
||||
neType: 'AMF',
|
||||
neId: '001',
|
||||
eventType: '',
|
||||
imsi: '',
|
||||
tenantName: '',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
/**开始时间 */
|
||||
@@ -78,9 +72,8 @@ function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
eventType: '',
|
||||
imsi: '',
|
||||
tenantName: '',
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
@@ -123,17 +116,14 @@ let tableState: TabeStateType = reactive({
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列排序 */
|
||||
let tableColumnsDnd = ref<ColumnsType>([]);
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
// {
|
||||
// title: t('common.rowId'),
|
||||
// dataIndex: 'id',
|
||||
// align: 'left',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'IMSI',
|
||||
dataIndex: 'eventJSON',
|
||||
@@ -162,26 +152,15 @@ let tableColumns: ColumnsType = [
|
||||
title: t('views.dashboard.ue.time'),
|
||||
dataIndex: 'eventJSON',
|
||||
align: 'left',
|
||||
width: 250,
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
const record = opt.value;
|
||||
console.log(record);
|
||||
if (record?.time) {
|
||||
return parseDateToStr(record.time);
|
||||
return record.time;
|
||||
}
|
||||
if (record?.timestamp) {
|
||||
return parseDateToStr(+record.timestamp * 1000);
|
||||
}
|
||||
return '';
|
||||
return parseDateToStr(+record.timestamp * 1000);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.tenantName'),
|
||||
dataIndex: 'tenantName',
|
||||
align: 'center',
|
||||
key: 'tenantName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
@@ -338,18 +317,14 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
if (modalState.confirmLoading || tablePagination.total === 0) return;
|
||||
if (modalState.confirmLoading) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.dashboard.ue.exportTip'),
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const querys = toRaw(queryParams);
|
||||
querys.pageNum = 1;
|
||||
querys.pageSize = tablePagination.total;
|
||||
querys.startTime = Number(querys.startTime);
|
||||
querys.endTime = Number(querys.endTime);
|
||||
querys.pageSize = 10000;
|
||||
exportAMFDataUE(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
@@ -497,33 +472,8 @@ onMounted(() => {
|
||||
})
|
||||
.finally(() => {
|
||||
// 获取列表数据
|
||||
if (useUserStore().roles.includes('tenant')) {
|
||||
const operateColumnIndex = tableColumns.findIndex(
|
||||
column => column.key === 'tenantName'
|
||||
);
|
||||
|
||||
if (operateColumnIndex !== -1) {
|
||||
tableColumns.splice(operateColumnIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
fnGetList();
|
||||
});
|
||||
|
||||
//查询租户
|
||||
listTenant({ parentId: 0 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
queryParams.tenantNameArr = []; //上面置为空数组时会报错 故在此
|
||||
res.data.forEach((item: any) => {
|
||||
if (item.parentId === '0') {
|
||||
queryParams.tenantNameArr.push({
|
||||
value: item.tenantName,
|
||||
label: item.tenantName,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -576,17 +526,6 @@ onBeforeUnmount(() => {
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24" v-roles:has="[TENANTADMIN_ROLE_KEY]">
|
||||
<a-form-item
|
||||
:label="t('views.neUser.sub.tenantName')"
|
||||
name="tenantName "
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.tenantName"
|
||||
:options="queryParams.tenantNameArr"
|
||||
></a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
@@ -636,11 +575,7 @@ onBeforeUnmount(() => {
|
||||
cancel-text="No"
|
||||
@confirm="fnRealTime()"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
:danger="realTimeData"
|
||||
v-roles:has="[TENANTADMIN_ROLE_KEY]"
|
||||
>
|
||||
<a-button type="primary" :danger="realTimeData">
|
||||
<template #icon><FundOutlined /> </template>
|
||||
{{
|
||||
!realTimeData
|
||||
@@ -688,11 +623,6 @@ onBeforeUnmount(() => {
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<TableColumnsDnd
|
||||
cache-id="amfUeData"
|
||||
:columns="tableColumns"
|
||||
v-model:columns-dnd="tableColumnsDnd"
|
||||
></TableColumnsDnd>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
@@ -724,7 +654,7 @@ onBeforeUnmount(() => {
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumnsDnd"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
|
||||
@@ -21,10 +21,8 @@ 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 { listTenant } from '@/api/system/tenant';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import dayjs, { type Dayjs } from 'dayjs';
|
||||
import { dayjsRanges } from '@/hooks/useRangePicker';
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
@@ -37,12 +35,9 @@ let dict: {
|
||||
cdrSipCode: DictType[];
|
||||
/**CDR 呼叫类型 */
|
||||
cdrCallType: DictType[];
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCodeCause: DictType[];
|
||||
} = reactive({
|
||||
cdrSipCode: [],
|
||||
cdrCallType: [],
|
||||
cdrSipCodeCause: [],
|
||||
});
|
||||
|
||||
/**网元可选 */
|
||||
@@ -53,17 +48,30 @@ 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 queryParams = reactive({
|
||||
tenantNameArr: <Record<string, any>[]>[],
|
||||
/**网元类型 */
|
||||
neType: 'IMS',
|
||||
neId: '001',
|
||||
recordType: '',
|
||||
callerParty: '',
|
||||
calledParty: '',
|
||||
tenantName: '',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
/**开始时间 */
|
||||
@@ -83,7 +91,6 @@ function fnQueryReset() {
|
||||
recordType: '',
|
||||
callerParty: '',
|
||||
calledParty: '',
|
||||
tenantName: '',
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
pageNum: 1,
|
||||
@@ -130,12 +137,12 @@ let tableState: TabeStateType = reactive({
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
// {
|
||||
// title: t('common.rowId'),
|
||||
// dataIndex: 'id',
|
||||
// align: 'left',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.recordType'),
|
||||
dataIndex: 'cdrJSON',
|
||||
@@ -175,6 +182,13 @@ let tableColumns: ColumnsType = [
|
||||
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',
|
||||
@@ -188,77 +202,32 @@ let tableColumns: ColumnsType = [
|
||||
: parseDuration(cdrJSON.callDuration);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.resultCode'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'code',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.resultCause'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'cause',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.mosAverage'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'mosAverage',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return cdrJSON.mosAverage;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.callConnectionTime'),
|
||||
dataIndex: 'cdrJSON',
|
||||
key: 'callConnectionTime',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
return cdrJSON.callType === 'sms'
|
||||
? '-'
|
||||
: parseDuration(cdrJSON.callConnectionTime);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.seizureTime'),
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 250,
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
if (typeof cdrJSON.seizureTime === 'number') {
|
||||
return parseDateToStr(+cdrJSON.seizureTime * 1000);
|
||||
}
|
||||
return parseDateToStr(cdrJSON.seizureTime);
|
||||
return cdrJSON.seizureTime;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.releaseTime'),
|
||||
dataIndex: 'cdrJSON',
|
||||
align: 'left',
|
||||
width: 250,
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
const cdrJSON = opt.value;
|
||||
if (typeof cdrJSON.releaseTime === 'number') {
|
||||
return parseDateToStr(+cdrJSON.releaseTime * 1000);
|
||||
}
|
||||
return parseDateToStr(cdrJSON.releaseTime);
|
||||
return cdrJSON.releaseTime;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.tenantName'),
|
||||
dataIndex: 'tenantName',
|
||||
align: 'left',
|
||||
key: 'tenantName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
@@ -419,25 +388,20 @@ function fnGetList(pageNum?: number) {
|
||||
modalState.maxId = Number(res.rows[0].id);
|
||||
}
|
||||
}
|
||||
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
if (modalState.confirmLoading || tablePagination.total === 0) return;
|
||||
if (modalState.confirmLoading) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.dashboard.cdr.exportTip'),
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const querys = toRaw(queryParams);
|
||||
querys.pageNum = 1;
|
||||
querys.pageSize = tablePagination.total;
|
||||
querys.startTime = Number(querys.startTime);
|
||||
querys.endTime = Number(querys.endTime);
|
||||
querys.pageSize = 10000;
|
||||
exportIMSDataCDR(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
@@ -530,21 +494,16 @@ function wsMessage(res: Record<string, any>) {
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('cdr_sip_code'),
|
||||
getDict('cdr_call_type'),
|
||||
getDict('cdr_sip_code_cause'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrSipCode = resArr[0].value;
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.cdrCallType = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.cdrSipCodeCause = resArr[2].value;
|
||||
}
|
||||
});
|
||||
);
|
||||
// 获取网元网元列表
|
||||
useNeInfoStore()
|
||||
.fnNelist()
|
||||
@@ -573,21 +532,6 @@ onMounted(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
|
||||
//查询租户
|
||||
listTenant({ parentId: 0 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
queryParams.tenantNameArr = []; //上面置为空数组时会报错 故在此
|
||||
res.data.forEach((item: any) => {
|
||||
if (item.parentId === '0') {
|
||||
queryParams.tenantNameArr.push({
|
||||
value: item.tenantName,
|
||||
label: item.tenantName,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -607,7 +551,7 @@ onBeforeUnmount(() => {
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="IMS" name="neId ">
|
||||
<a-select
|
||||
v-model:value="queryParams.neId"
|
||||
@@ -617,7 +561,7 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.dashboard.cdr.called')"
|
||||
name="calledParty"
|
||||
@@ -629,7 +573,7 @@ onBeforeUnmount(() => {
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.dashboard.cdr.caller')"
|
||||
name="callerParty "
|
||||
@@ -641,6 +585,20 @@ onBeforeUnmount(() => {
|
||||
></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')"
|
||||
@@ -662,7 +620,7 @@ onBeforeUnmount(() => {
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
:presets="dayjsRanges()"
|
||||
:presets="rangePickerPresets"
|
||||
:bordered="true"
|
||||
:allow-clear="false"
|
||||
style="width: 100%"
|
||||
@@ -671,31 +629,6 @@ onBeforeUnmount(() => {
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.neUser.sub.tenantName')"
|
||||
name="tenantName "
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.tenantName"
|
||||
:options="queryParams.tenantNameArr"
|
||||
></a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="4" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
@@ -799,7 +732,7 @@ onBeforeUnmount(() => {
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: tableColumns.length * 160, y: 'calc(100vh - 480px)' }"
|
||||
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
columnWidth: '48px',
|
||||
@@ -814,7 +747,7 @@ onBeforeUnmount(() => {
|
||||
:value="record.cdrJSON.callType"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'code'">
|
||||
<template v-if="column.key === 'cause'">
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
<DictTag
|
||||
:options="dict.cdrSipCode"
|
||||
@@ -826,16 +759,6 @@ onBeforeUnmount(() => {
|
||||
{{ t('views.dashboard.cdr.resultOk') }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'cause'">
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
<DictTag
|
||||
:options="dict.cdrSipCodeCause"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else> Call failure for other reason </span>
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
@@ -864,6 +787,93 @@ onBeforeUnmount(() => {
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
<template #expandedRowRender="{ record }">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="8" :md="12" :xs="24" :offset="2">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.cdr.cdrInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.ne.common.neName') }}: </span>
|
||||
<span>{{ record.neName }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.common.rmUid') }}: </span>
|
||||
<span>{{ record.rmUID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.time') }}: </span>
|
||||
<span>
|
||||
{{
|
||||
typeof record.cdrJSON.releaseTime === 'number'
|
||||
? parseDateToStr(+record.cdrJSON.releaseTime * 1000)
|
||||
: record.cdrJSON.releaseTime
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.dashboard.cdr.rowInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.type') }}: </span>
|
||||
<DictTag
|
||||
:options="dict.cdrCallType"
|
||||
:value="record.cdrJSON.callType"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.duration') }}: </span>
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
{{ parseDuration(record.cdrJSON.callDuration) }}
|
||||
</span>
|
||||
<span v-else> - </span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.caller') }}: </span>
|
||||
<span>{{ record.cdrJSON.callerParty }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.called') }}: </span>
|
||||
<span>{{ record.cdrJSON.calledParty }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.result') }}: </span>
|
||||
<span v-if="record.cdrJSON.callType !== 'sms'">
|
||||
<DictTag
|
||||
:options="dict.cdrSipCode"
|
||||
:value="record.cdrJSON.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.cdr.resultOk') }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.seizureTime') }}: </span>
|
||||
<span>
|
||||
{{
|
||||
typeof record.cdrJSON.seizureTime === 'number'
|
||||
? parseDateToStr(+record.cdrJSON.seizureTime * 1000)
|
||||
: record.cdrJSON.seizureTime
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.cdr.releaseTime') }}: </span>
|
||||
<span>
|
||||
{{
|
||||
typeof record.cdrJSON.releaseTime === 'number'
|
||||
? parseDateToStr(+record.cdrJSON.releaseTime * 1000)
|
||||
: record.cdrJSON.releaseTime
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
|
||||
1334
src/views/dashboard/mfCDR/index.vue
Normal file
@@ -18,7 +18,6 @@ import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import saveAs from 'file-saver';
|
||||
import PQueue from 'p-queue';
|
||||
import { listTenant } from '@/api/system/tenant';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
const { copy } = useClipboard({ legacy: true });
|
||||
const { t } = useI18n();
|
||||
@@ -51,13 +50,11 @@ let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
tenantNameArr: <Record<string, any>[]>[],
|
||||
/**网元类型 */
|
||||
neType: 'MME',
|
||||
neId: '001',
|
||||
eventType: '',
|
||||
imsi: '',
|
||||
tenantName: '',
|
||||
sortField: 'timestamp',
|
||||
sortOrder: 'desc',
|
||||
/**开始时间 */
|
||||
@@ -76,9 +73,8 @@ function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
eventType: '',
|
||||
imsi: '',
|
||||
tenantName: '',
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
@@ -123,12 +119,12 @@ let tableState: TabeStateType = reactive({
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
// {
|
||||
// title: t('common.rowId'),
|
||||
// dataIndex: 'id',
|
||||
// align: 'left',
|
||||
// width: 100,
|
||||
// },
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'IMSI',
|
||||
dataIndex: 'eventJSON',
|
||||
@@ -157,25 +153,15 @@ let tableColumns: ColumnsType = [
|
||||
title: t('views.dashboard.ue.time'),
|
||||
dataIndex: 'eventJSON',
|
||||
align: 'left',
|
||||
width: 250,
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
const record = opt.value;
|
||||
if (record?.time) {
|
||||
return parseDateToStr(record.time);
|
||||
return record.time;
|
||||
}
|
||||
if (record?.timestamp) {
|
||||
return parseDateToStr(+record.timestamp * 1000);
|
||||
}
|
||||
return '';
|
||||
return parseDateToStr(+record.timestamp * 1000);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.dashboard.cdr.tenantName'),
|
||||
dataIndex: 'tenantName',
|
||||
align: 'center',
|
||||
key: 'tenantName',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
@@ -332,18 +318,14 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
if (modalState.confirmLoading || tablePagination.total === 0) return;
|
||||
if (modalState.confirmLoading) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.dashboard.ue.exportTip'),
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const querys = toRaw(queryParams);
|
||||
querys.pageNum = 1;
|
||||
querys.pageSize = tablePagination.total;
|
||||
querys.startTime = Number(querys.startTime);
|
||||
querys.endTime = Number(querys.endTime);
|
||||
querys.pageSize = 10000;
|
||||
exportMMEDataUE(querys)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
@@ -499,21 +481,6 @@ onMounted(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
|
||||
//查询租户
|
||||
listTenant({ parentId: 0 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
queryParams.tenantNameArr = []; //上面置为空数组时会报错 故在此
|
||||
res.data.forEach((item: any) => {
|
||||
if (item.parentId === '0') {
|
||||
queryParams.tenantNameArr.push({
|
||||
value: item.tenantName,
|
||||
label: item.tenantName,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
@@ -566,17 +533,6 @@ onBeforeUnmount(() => {
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.neUser.sub.tenantName')"
|
||||
name="tenantName "
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.tenantName"
|
||||
:options="queryParams.tenantNameArr"
|
||||
></a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
|
||||
@@ -236,11 +236,8 @@ function handleRanderChart(
|
||||
}
|
||||
|
||||
function fnChangeData(data: any[], itemID: string) {
|
||||
const neType=itemID.split('_')[0];
|
||||
const neID=itemID.split('_')[1];
|
||||
let info = data.find((item: any) => item.id === neType);
|
||||
|
||||
if (!info || !info.neStateMap[neID]?.online) return;
|
||||
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;
|
||||
@@ -252,16 +249,16 @@ function fnChangeData(data: any[], itemID: string) {
|
||||
// console.log(info.neState.disk);
|
||||
let sysCpuUsage = 0;
|
||||
let nfCpuUsage = 0;
|
||||
if (info.neStateMap[neID].cpu) {
|
||||
nfCpuUsage = info.neStateMap[neID].cpu.nfCpuUsage;
|
||||
const nfCpu = +(info.neStateMap[neID].cpu.nfCpuUsage / 100);
|
||||
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.neStateMap[neID].cpu.sysCpuUsage;
|
||||
let sysCpu = +(info.neStateMap[neID].cpu.sysCpuUsage / 100);
|
||||
sysCpuUsage = info.neState.cpu.sysCpuUsage;
|
||||
let sysCpu = +(info.neState.cpu.sysCpuUsage / 100);
|
||||
sysCpuUsage = +sysCpu.toFixed(2);
|
||||
if (sysCpuUsage > 100) {
|
||||
sysCpuUsage = 100;
|
||||
@@ -269,8 +266,8 @@ function fnChangeData(data: any[], itemID: string) {
|
||||
}
|
||||
|
||||
let sysMemUsage = 0;
|
||||
if (info.neStateMap[neID].mem) {
|
||||
const men = info.neStateMap[neID].mem.sysMemUsage;
|
||||
if (info.neState.mem) {
|
||||
const men = info.neState.mem.sysMemUsage;
|
||||
sysMemUsage = +(men / 100).toFixed(2);
|
||||
if (sysMemUsage > 100) {
|
||||
sysMemUsage = 100;
|
||||
@@ -278,8 +275,8 @@ function fnChangeData(data: any[], itemID: string) {
|
||||
}
|
||||
|
||||
let sysDiskUsage = 0;
|
||||
if (info.neStateMap[neID].disk && Array.isArray(info.neStateMap[neID].disk.partitionInfo)) {
|
||||
let disks: any[] = info.neStateMap[neID].disk.partitionInfo;
|
||||
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];
|
||||
|
||||
@@ -22,25 +22,18 @@ const graphG6Dom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图节点展示 */
|
||||
const graphNodeTooltip = new Tooltip({
|
||||
offsetX: 10,
|
||||
offsetX: 20,
|
||||
offsetY: 20,
|
||||
getContent(evt) {
|
||||
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
|
||||
const { id, label, neState, neInfoList, neStateMap }: any = evt.item?.getModel();
|
||||
//console.log(neInfoList,neState,neInfoList);
|
||||
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>`;
|
||||
}
|
||||
|
||||
// 获取同类型网元列表
|
||||
const sameTypeNes = neInfoList || [];
|
||||
|
||||
// 如果没有网元或只有一个网元,显示原来的信息
|
||||
if (sameTypeNes.length <= 1) {
|
||||
return `
|
||||
return `
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
@@ -63,7 +56,7 @@ const graphNodeTooltip = new Tooltip({
|
||||
<div><strong>${t('views.monitor.topology.name')}:</strong><span>
|
||||
${neState.neName ?? '--'}
|
||||
</span></div>
|
||||
<div><strong>IP:</strong><span>${neState.neIP ?? '--'}</span></div>
|
||||
<div><strong>IP:</strong><span>${neState.neIP}</span></div>
|
||||
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
|
||||
${neState.version ?? '--'}
|
||||
</span></div>
|
||||
@@ -72,71 +65,24 @@ const graphNodeTooltip = new Tooltip({
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
|
||||
${neState.expire ?? '--'}
|
||||
</span></div>
|
||||
</span></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 如果有多个网元,聚合显示
|
||||
let content = `
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
"
|
||||
>
|
||||
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||
${
|
||||
neState.online
|
||||
? t('views.monitor.topology.normalcy')
|
||||
: t('views.monitor.topology.exceptions')
|
||||
}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.refreshTime')}:</strong><span>
|
||||
${neState.refreshTime ?? '--'}
|
||||
</span></div>
|
||||
<div>========================</div>`;
|
||||
|
||||
// 为每个同类型网元添加信息
|
||||
sameTypeNes.forEach((ne: any, index: number) => {
|
||||
// 获取该网元的状态信息
|
||||
const neStateInfo = neStateMap?.[ne.neId] ||
|
||||
(ne.neId === neState.neId ? neState : {});
|
||||
|
||||
content += `
|
||||
<div style="margin-top: 8px;"><strong>${t('views.monitor.topology.name')}:${ne.neName || id + '_' + ne.neId}</strong></div>
|
||||
<div><strong>ID:</strong><span>${ne.neId || '--'}</span></div>
|
||||
<div><strong>IP:</strong><span>${neStateInfo.neIP || ne.neIP || '--'}</span></div>
|
||||
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
|
||||
${neStateInfo.version || ne.version || '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.serialNum')}:</strong><span>
|
||||
${neStateInfo.sn || ne.sn || '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
|
||||
${neStateInfo.expire || ne.expire || '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||
${
|
||||
neStateInfo.online !== undefined
|
||||
? (neStateInfo.online
|
||||
? t('views.monitor.topology.normalcy')
|
||||
: t('views.monitor.topology.exceptions'))
|
||||
: 'undefined'
|
||||
}
|
||||
</span></div>
|
||||
${index < sameTypeNes.length - 1 ? '<div>------------------------</div>' : ''}
|
||||
`;
|
||||
});
|
||||
|
||||
content += '</div>';
|
||||
return content;
|
||||
},
|
||||
itemTypes: ['node'],
|
||||
});
|
||||
|
||||
|
||||
/**图绑定事件 */
|
||||
function 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(
|
||||
@@ -158,7 +104,7 @@ function handleRanderGraph(
|
||||
fitViewPadding: [20],
|
||||
autoPaint: true,
|
||||
modes: {
|
||||
// default: ['drag-canvas', 'zoom-canvas'],
|
||||
default: ['drag-canvas', 'zoom-canvas'],
|
||||
},
|
||||
groupByTypes: false,
|
||||
nodeStateStyles: {
|
||||
@@ -176,6 +122,7 @@ function handleRanderGraph(
|
||||
graph.data(data);
|
||||
graph.render();
|
||||
|
||||
fnGraphEvent(graph);
|
||||
|
||||
graphG6.value = graph;
|
||||
|
||||
@@ -203,14 +150,14 @@ function handleRanderGraph(
|
||||
* 获取图组数据渲染到画布
|
||||
* @param reload 是否重载数据
|
||||
*/
|
||||
function fnGraphDataLoad(reload: boolean = false) {
|
||||
function fnGraphDataLoad(reload: boolean = false) {
|
||||
Promise.all([
|
||||
getGraphData(graphState.group),
|
||||
listAllNeInfo({
|
||||
bandStatus: false,
|
||||
}),
|
||||
])
|
||||
.then(resArr => {
|
||||
.then(resArr => {
|
||||
const graphRes = resArr[0];
|
||||
const neRes = resArr[1];
|
||||
if (
|
||||
@@ -236,45 +183,27 @@ function handleRanderGraph(
|
||||
if (!res) return;
|
||||
const { combos, edges, nodes } = res.graphData;
|
||||
|
||||
// 按网元类型分组
|
||||
const neTypeMap = new Map();
|
||||
res.neList.forEach(ne => {
|
||||
if (!ne.neType) return;
|
||||
if (!neTypeMap.has(ne.neType)) {
|
||||
neTypeMap.set(ne.neType, []);
|
||||
}
|
||||
neTypeMap.get(ne.neType).push(ne);
|
||||
});
|
||||
|
||||
// 节点过滤
|
||||
const nf: Record<string, any>[] = nodes.filter(
|
||||
(node: Record<string, any>) => {
|
||||
Reflect.set(node, 'neState', { online: false });
|
||||
Reflect.set(node, 'neStateMap', {}); // 初始化状态映射
|
||||
|
||||
// 图片路径处理
|
||||
if (node.img) node.img = parseBasePath(node.img);
|
||||
if (node.icon.show && node.icon?.img){
|
||||
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;
|
||||
}
|
||||
//(neTypeMap.get(nodeID),nodeID,node.neState)
|
||||
// 处理网元节点
|
||||
if (neTypeMap.has(nodeID)) {
|
||||
// all NeInfo
|
||||
Reflect.set(node, 'neInfoList', neTypeMap.get(nodeID));
|
||||
|
||||
Reflect.set(node, 'neInfo', neTypeMap.get(nodeID)[0] || {});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
@@ -286,6 +215,7 @@ function handleRanderGraph(
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ 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';
|
||||
import { upfWhoId } from '../../hooks/useWS';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -151,7 +151,7 @@ function handleRanderChart() {
|
||||
top: '14%',
|
||||
left: '4%',
|
||||
right: '4%',
|
||||
bottom: '16%',
|
||||
bottom: '12%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
@@ -160,7 +160,7 @@ function handleRanderChart() {
|
||||
data: lineXTime,
|
||||
axisLabel: {
|
||||
formatter: function (params: any) {
|
||||
return params;
|
||||
return params.split(' ')[1];
|
||||
},
|
||||
fontSize: 14,
|
||||
},
|
||||
@@ -208,10 +208,9 @@ function fnGetInitData() {
|
||||
|
||||
listKPIData({
|
||||
neType: 'UPF',
|
||||
neId: upfWhoId.value,
|
||||
neId: '001',
|
||||
startTime: nowDate - 5 * 60 * 1000,
|
||||
endTime: nowDate,
|
||||
|
||||
interval: 5, // 5秒
|
||||
sortField: 'timeGroup',
|
||||
sortOrder: 'asc',
|
||||
|
||||
@@ -19,15 +19,12 @@ let dict: {
|
||||
ueEventType: DictType[];
|
||||
/**UE 事件CM状态 */
|
||||
ueEventCmState: DictType[];
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCodeCause: DictType[];
|
||||
} = reactive({
|
||||
cdrSipCode: [],
|
||||
cdrCallType: [],
|
||||
ueAauthCode: [],
|
||||
ueEventType: [],
|
||||
ueEventCmState: [],
|
||||
cdrSipCodeCause: [],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
@@ -38,7 +35,6 @@ onMounted(() => {
|
||||
getDict('ue_auth_code'),
|
||||
getDict('ue_event_type'),
|
||||
getDict('ue_event_cm_state'),
|
||||
getDict('cdr_sip_code_cause'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrSipCode = resArr[0].value;
|
||||
@@ -55,9 +51,6 @@ onMounted(() => {
|
||||
if (resArr[4].status === 'fulfilled') {
|
||||
dict.ueEventCmState = resArr[4].value;
|
||||
}
|
||||
if (resArr[5].status === 'fulfilled') {
|
||||
dict.cdrSipCodeCause = resArr[5].value;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -82,7 +75,16 @@ onMounted(() => {
|
||||
</span>
|
||||
</div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<span :title="item.data.releaseTime">
|
||||
{{
|
||||
typeof item.data.releaseTime === 'number'
|
||||
? parseDateToStr(+item.data.releaseTime * 1000)
|
||||
: item.data.releaseTime
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-cdr-item">
|
||||
<div>
|
||||
@@ -103,16 +105,6 @@ onMounted(() => {
|
||||
</div>
|
||||
<div v-else></div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<span :title="item.data.releaseTime">
|
||||
{{
|
||||
typeof item.data.releaseTime === 'number'
|
||||
? parseDateToStr(+item.data.releaseTime * 1000)
|
||||
: parseDateToStr(item.data.releaseTime)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span v-if="item.data.callType !== 'sms'">
|
||||
@@ -120,11 +112,6 @@ onMounted(() => {
|
||||
:options="dict.cdrSipCode"
|
||||
:value="item.data.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
<DictTag
|
||||
:options="dict.cdrSipCodeCause"
|
||||
:value="item.data.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
@@ -148,7 +135,15 @@ onMounted(() => {
|
||||
<div>
|
||||
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span>
|
||||
</div>
|
||||
<div></div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<template v-if="item.data?.time">
|
||||
{{ item.data.time }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ parseDateToStr(+item.data.timestamp * 1000) }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||
@@ -162,16 +157,7 @@ onMounted(() => {
|
||||
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<template v-if="item.data?.time">
|
||||
{{ parseDateToStr(item.data.time) }}
|
||||
</template>
|
||||
<template v-else-if="item.data?.timestamp">
|
||||
{{ parseDateToStr(+item.data.timestamp * 1000) }}
|
||||
</template>
|
||||
<template v-else> - </template>
|
||||
</div>
|
||||
|
||||
<div v-if="item.type === 'auth-result'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span>
|
||||
@@ -201,7 +187,7 @@ onMounted(() => {
|
||||
<span v-if="item.type === 'cm-state'">
|
||||
{{
|
||||
dict.ueEventType
|
||||
.find((s: any) => s.value === item.type)
|
||||
.find(s => s.value === item.type)
|
||||
?.label.replace('CM', 'ECM')
|
||||
}}
|
||||
</span>
|
||||
@@ -212,7 +198,16 @@ onMounted(() => {
|
||||
<div>
|
||||
IMSI: <span :title="item.data?.imsi">{{ item.data?.imsi }}</span>
|
||||
</div>
|
||||
<div></div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<span :title="item.data?.timestamp">
|
||||
{{
|
||||
typeof item.data?.timestamp === 'number'
|
||||
? parseDateToStr(+item.data?.timestamp * 1000)
|
||||
: item.data?.timestamp
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||
@@ -226,20 +221,6 @@ onMounted(() => {
|
||||
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<template v-if="item.data?.time">
|
||||
{{ parseDateToStr(item.data.time) }}
|
||||
</template>
|
||||
<template v-else-if="item.data?.timestamp">
|
||||
{{
|
||||
typeof item.data?.timestamp === 'number'
|
||||
? parseDateToStr(+item.data?.timestamp * 1000)
|
||||
: parseDateToStr(item.data?.timestamp)
|
||||
}}
|
||||
</template>
|
||||
<template v-else> - </template>
|
||||
</div>
|
||||
<div v-if="item.type === 'auth-result'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span>
|
||||
|
||||
@@ -0,0 +1,561 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
reactive,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
markRaw,
|
||||
useTemplateRef, ref
|
||||
} from 'vue';
|
||||
import TrendChart from './TrendChart.vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { listNeInfo } from '@/api/ne/neInfo';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { getNeConfigData } from '@/api/ne/neConfig';
|
||||
import { listCallings } from '@/api/agentManage/callings';
|
||||
import { useRouter } from 'vue-router';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
|
||||
/**10s调度器 */
|
||||
const interval10s = ref<any>(null);
|
||||
|
||||
// 模拟数据
|
||||
const activeCallsData = ref([]);
|
||||
const mosData = ref([]);
|
||||
const failedCallsData = ref([]);
|
||||
|
||||
// 新增三个卡片的模拟数据
|
||||
const networkCpuData = ref([]);
|
||||
const systemCpuData = ref([]);
|
||||
const systemStorageData = ref([]);
|
||||
const systemMemData = ref([]);
|
||||
|
||||
|
||||
// 是否是第一次加载资源数据
|
||||
const isFirstLoad = ref(true);
|
||||
|
||||
// 更新图表数据的函数
|
||||
function updateChartData(newValue: number, dataArray: any) {
|
||||
// 如果是第一次加载,用当前值填充整个数组
|
||||
if (isFirstLoad.value) {
|
||||
dataArray.value = Array(7).fill(newValue);
|
||||
} else {
|
||||
// 非第一次加载,正常更新数据(移除第一个,添加新值)
|
||||
const newData = [...dataArray.value];
|
||||
newData.shift();
|
||||
newData.push(newValue);
|
||||
dataArray.value = newData;
|
||||
}
|
||||
}
|
||||
|
||||
// 是否是第一次加载用户数据
|
||||
const isFirstLoadUser = ref(true);
|
||||
function updateUserChartData(newValue: number, dataArray: any) {
|
||||
// 如果是第一次加载,用当前值填充整个数组
|
||||
if (isFirstLoadUser.value) {
|
||||
dataArray.value = Array(7).fill(newValue);
|
||||
} else {
|
||||
// 非第一次加载,正常更新数据(移除第一个,添加新值)
|
||||
const newData = [...dataArray.value];
|
||||
newData.shift();
|
||||
newData.push(newValue);
|
||||
dataArray.value = newData;
|
||||
}
|
||||
}
|
||||
|
||||
// 是否是第一次加载并行用户数据
|
||||
const isFirstLoadFailed = ref(true);
|
||||
function updateFailedChartData(newValue: number, dataArray: any) {
|
||||
// 如果是第一次加载,用当前值填充整个数组
|
||||
if (isFirstLoadFailed.value) {
|
||||
dataArray.value = Array(7).fill(newValue);
|
||||
} else {
|
||||
// 非第一次加载,正常更新数据(移除第一个,添加新值)
|
||||
const newData = [...dataArray.value];
|
||||
newData.shift();
|
||||
newData.push(newValue);
|
||||
dataArray.value = newData;
|
||||
}
|
||||
}
|
||||
|
||||
// 当前资源使用率
|
||||
const currentNfCpuUsage = ref(0);
|
||||
const currentSysCpuUsage = ref(0);
|
||||
const currentSysDiskUsage = ref(0);
|
||||
const currentSysMemUsage = ref(0);
|
||||
|
||||
// 上一次的资源使用率
|
||||
const prevNfCpuUsage = ref(0);
|
||||
const prevSysCpuUsage = ref(0);
|
||||
const prevSysDiskUsage = ref(0);
|
||||
const prevSysMemUsage = ref(0);
|
||||
|
||||
// 资源变化百分比
|
||||
const nfCpuChange = ref(0);
|
||||
const sysCpuChange = ref(0);
|
||||
const sysDiskChange = ref(0);
|
||||
const sysMemChange = ref(0);
|
||||
|
||||
|
||||
// 当前资源使用率
|
||||
const activeCalls = ref(0);
|
||||
const onlineCount = ref(0);
|
||||
const failedCallsCount = ref(0);
|
||||
|
||||
// 上一次的资源使用率
|
||||
const prevActiveCalls = ref(0);
|
||||
const prevOnlineCount = ref(0);
|
||||
const prevFailedCallsCount = ref(0);
|
||||
|
||||
|
||||
|
||||
// 用户数变化百分比
|
||||
const activeCallsChange = ref(0);
|
||||
const onlineCountChange = ref(0);
|
||||
const failedCallsCountChange = ref(0);
|
||||
|
||||
|
||||
|
||||
|
||||
/**解析网元状态携带的资源利用率 */
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
// 计算变化百分比
|
||||
function calculateChange(current: number, previous: number): number {
|
||||
if (previous === 0) return 0;
|
||||
return Number(((current - previous) * 100 / previous).toFixed(1));
|
||||
}
|
||||
|
||||
let tableState: any = [];
|
||||
|
||||
|
||||
function fnGetList() {
|
||||
listNeInfo({
|
||||
neType: 'MF',
|
||||
bandStatus: true,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows) && res.rows.length > 0) {
|
||||
// 获取第一个网元的资源使用情况
|
||||
const item = res.rows[0];
|
||||
let resouresUsage = {
|
||||
sysDiskUsage: 0,
|
||||
sysMemUsage: 0,
|
||||
sysCpuUsage: 0,
|
||||
nfCpuUsage: 0,
|
||||
};
|
||||
|
||||
const neState = item.serverState;
|
||||
if (neState) {
|
||||
resouresUsage = parseResouresUsage(neState);
|
||||
}
|
||||
|
||||
// 保存上一次的值
|
||||
prevNfCpuUsage.value = currentNfCpuUsage.value;
|
||||
prevSysCpuUsage.value = currentSysCpuUsage.value;
|
||||
prevSysDiskUsage.value = currentSysDiskUsage.value;
|
||||
prevSysMemUsage.value = currentSysMemUsage.value;
|
||||
|
||||
// 更新当前值
|
||||
currentNfCpuUsage.value = resouresUsage.nfCpuUsage;
|
||||
currentSysCpuUsage.value = resouresUsage.sysCpuUsage;
|
||||
currentSysDiskUsage.value = resouresUsage.sysDiskUsage;
|
||||
currentSysMemUsage.value = resouresUsage.sysMemUsage;
|
||||
|
||||
|
||||
// 计算变化百分比
|
||||
nfCpuChange.value = calculateChange(currentNfCpuUsage.value, prevNfCpuUsage.value);
|
||||
sysCpuChange.value = calculateChange(currentSysCpuUsage.value, prevSysCpuUsage.value);
|
||||
sysDiskChange.value = calculateChange(currentSysDiskUsage.value, prevSysDiskUsage.value);
|
||||
sysMemChange.value = calculateChange(currentSysMemUsage.value, prevSysMemUsage.value);
|
||||
|
||||
// 更新图表数据
|
||||
updateChartData(currentNfCpuUsage.value, networkCpuData);
|
||||
updateChartData(currentSysCpuUsage.value, systemCpuData);
|
||||
updateChartData(currentSysDiskUsage.value, systemStorageData);
|
||||
updateChartData(currentSysMemUsage.value, systemMemData);
|
||||
|
||||
// 第一次加载完成后,设置标志为false
|
||||
if (isFirstLoad.value) {
|
||||
isFirstLoad.value = false;
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
// 获取网元端的配置数据
|
||||
getNeConfigData({
|
||||
neType: 'MF',
|
||||
neId: '001',
|
||||
paramName: 'agents',
|
||||
}).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
const neData = res.data;
|
||||
|
||||
prevActiveCalls.value = activeCalls.value;
|
||||
prevOnlineCount.value = onlineCount.value;
|
||||
|
||||
|
||||
// 更新 activeCallsData 和 mosData
|
||||
activeCalls.value = neData.length; // 数组长度
|
||||
onlineCount.value = neData.filter((item: any) => item.online).length; // online 为 true 的数量
|
||||
|
||||
activeCallsChange.value = prevActiveCalls.value ? prevActiveCalls.value - activeCalls.value : 0;
|
||||
onlineCountChange.value = prevOnlineCount.value ? prevOnlineCount.value - onlineCount.value : 0;
|
||||
// 更新图表数据
|
||||
updateUserChartData(activeCalls.value ? activeCalls.value : 0, activeCallsData);
|
||||
updateUserChartData(onlineCount.value ? onlineCount.value : 0, mosData);
|
||||
|
||||
// 第一次加载完成后,设置标志为false
|
||||
if (isFirstLoadUser.value) {
|
||||
isFirstLoadUser.value = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
updateUserChartData(0, activeCallsData);
|
||||
updateUserChartData(0, mosData);
|
||||
|
||||
// 第一次加载完成后,设置标志为false
|
||||
if (isFirstLoadUser.value) {
|
||||
isFirstLoadUser.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
listCallings({ neId: '001' }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
const callData: any = res.data;
|
||||
prevFailedCallsCount.value = failedCallsCount.value;
|
||||
|
||||
|
||||
failedCallsCount.value = res.total; // 数组长度
|
||||
|
||||
failedCallsCountChange.value = prevFailedCallsCount.value ? prevFailedCallsCount.value - failedCallsCount.value : 0;
|
||||
|
||||
// 更新图表数据
|
||||
updateFailedChartData(failedCallsCount.value, failedCallsData);
|
||||
|
||||
// 第一次加载完成后,设置标志为false
|
||||
if (isFirstLoadFailed.value) {
|
||||
isFirstLoadFailed.value = false;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**栏目信息跳转 */
|
||||
function fnToRouter(name: string, query?: any) {
|
||||
if (useUserStore().roles.includes('agent')) {
|
||||
return;
|
||||
}
|
||||
router.push({ name, query });
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始获取数据
|
||||
fnGetList();
|
||||
|
||||
// 设置10秒定时器
|
||||
interval10s.value = setInterval(() => {
|
||||
fnGetList();
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
// 组件销毁前清除定时器
|
||||
onBeforeUnmount(() => {
|
||||
if (interval10s.value) {
|
||||
clearInterval(interval10s.value);
|
||||
interval10s.value = null;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card>
|
||||
<div style="font-size:32px; font-weight: bold; margin-bottom: 20px; text-align: center;">
|
||||
{{ t('views.dashboard.overview.psapTitle') }}</div>
|
||||
|
||||
<div class="row-title">{{ t('views.dashboard.overview.userTitle') }}</div>
|
||||
<a-row :gutter="[48, 48]">
|
||||
<a-col :xs="24" :sm="24" :lg="8">
|
||||
<a-card :bordered="false" class="metric-card"
|
||||
@click="fnToRouter('NeConfig_2146', { neType: 'MF', treeName: 'agents' })">
|
||||
<div class="card-title">{{ t('views.dashboard.overview.totalUser') }}</div>
|
||||
<div class="card-content">
|
||||
<div class="trend-chart">
|
||||
<TrendChart :data="activeCallsData" color="#4CAF50" />
|
||||
</div>
|
||||
<div class="metric-info">
|
||||
<div class="metric-value">
|
||||
{{ activeCalls }}
|
||||
<a-icon :class="['trend-icon', activeCallsChange >= 0 ? 'up' : 'down']"
|
||||
:type="activeCallsChange >= 0 ? 'arrow-up' : 'arrow-down'" />
|
||||
</div>
|
||||
<div class="metric-change">{{ activeCallsChange >= 0 ? '+' : '' }}{{ activeCallsChange }}% last 10s</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :xs="24" :sm="24" :lg="8">
|
||||
<a-card :bordered="false" class="metric-card"
|
||||
@click="fnToRouter('NeConfig_2146', { neType: 'MF', treeName: 'agents' })">
|
||||
<div class="card-title">{{ t('views.dashboard.overview.onlineUser') }}</div>
|
||||
<div class="card-content">
|
||||
<div class="trend-chart">
|
||||
<TrendChart :data="mosData" color="#2196F3" />
|
||||
</div>
|
||||
<div class="metric-info">
|
||||
<div class="metric-value">
|
||||
{{ onlineCount }}
|
||||
<a-icon :class="['trend-icon', onlineCountChange >= 0 ? 'up' : 'down']"
|
||||
:type="onlineCountChange >= 0 ? 'arrow-up' : 'arrow-down'" />
|
||||
</div>
|
||||
<div class="metric-change">{{ onlineCountChange >= 0 ? '+' : '' }}{{ onlineCountChange }} last 10s</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
|
||||
|
||||
<a-col :xs="24" :sm="24" :lg="8">
|
||||
<a-card :bordered="false" class="metric-card" @click="fnToRouter('Callings_20001')">
|
||||
<div class="card-title">{{ t('views.dashboard.overview.parallelUser') }}</div>
|
||||
<div class="card-content">
|
||||
<div class="trend-chart">
|
||||
<TrendChart :data="failedCallsData" color="#F44336" />
|
||||
</div>
|
||||
<div class="metric-info">
|
||||
<div class="metric-value">
|
||||
{{ failedCallsCount }}
|
||||
<a-icon :class="['trend-icon', failedCallsCountChange >= 0 ? 'up' : 'down']"
|
||||
:type="failedCallsCountChange >= 0 ? 'arrow-up' : 'arrow-down'" />
|
||||
</div>
|
||||
<div class="metric-change">{{ failedCallsCountChange >= 0 ? '+' : '' }}{{ failedCallsCountChange }} last
|
||||
10s</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
</a-row>
|
||||
|
||||
<div class="row-title" style="margin-top: 100px;"> {{ t('views.dashboard.overview.sysTitle') }} </div>
|
||||
|
||||
|
||||
|
||||
<a-row :gutter="[48, 48]">
|
||||
<a-col :xs="24" :sm="24" :lg="6">
|
||||
<a-card :bordered="false" class="metric-card">
|
||||
<div class="card-title">{{ t('views.dashboard.overview.resources.neCpu') }}</div>
|
||||
<div class="card-content">
|
||||
<div class="trend-chart">
|
||||
<TrendChart :data="networkCpuData" color="#FF9800" />
|
||||
</div>
|
||||
<div class="metric-info">
|
||||
<div class="metric-value">
|
||||
{{ currentNfCpuUsage }}%
|
||||
<a-icon :class="['trend-icon', nfCpuChange >= 0 ? 'up' : 'down']"
|
||||
:type="nfCpuChange >= 0 ? 'arrow-up' : 'arrow-down'" />
|
||||
</div>
|
||||
<div class="metric-change">{{ nfCpuChange >= 0 ? '+' : '' }}{{ nfCpuChange }}% last 10s</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :xs="24" :sm="24" :lg="6">
|
||||
<a-card :bordered="false" class="metric-card">
|
||||
<div class="card-title">{{ t('views.dashboard.overview.resources.sysCpu') }}</div>
|
||||
<div class="card-content">
|
||||
<div class="trend-chart">
|
||||
<TrendChart :data="systemCpuData" color="#9C27B0" />
|
||||
</div>
|
||||
<div class="metric-info">
|
||||
<div class="metric-value">
|
||||
{{ currentSysCpuUsage }}%
|
||||
<a-icon :class="['trend-icon', sysCpuChange >= 0 ? 'up' : 'down']"
|
||||
:type="sysCpuChange >= 0 ? 'arrow-up' : 'arrow-down'" />
|
||||
</div>
|
||||
<div class="metric-change">{{ sysCpuChange >= 0 ? '+' : '' }}{{ sysCpuChange }}% last 10s</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
|
||||
<a-col :xs="24" :sm="24" :lg="6">
|
||||
<a-card :bordered="false" class="metric-card">
|
||||
<div class="card-title">{{ t('views.dashboard.overview.resources.sysMem') }}</div>
|
||||
<div class="card-content">
|
||||
<div class="trend-chart">
|
||||
<TrendChart :data="systemMemData" color="#CDDC39" />
|
||||
</div>
|
||||
<div class="metric-info">
|
||||
<div class="metric-value">
|
||||
{{ currentSysMemUsage }}%
|
||||
<a-icon :class="['trend-icon', sysMemChange >= 0 ? 'up' : 'down']"
|
||||
:type="sysMemChange >= 0 ? 'arrow-up' : 'arrow-down'" />
|
||||
</div>
|
||||
<div class="metric-change">{{ sysMemChange >= 0 ? '+' : '' }}{{ sysMemChange }}% last 10s</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :xs="24" :sm="24" :lg="6">
|
||||
<a-card :bordered="false" class="metric-card">
|
||||
<div class="card-title">{{ t('views.dashboard.overview.resources.sysDisk') }}</div>
|
||||
<div class="card-content">
|
||||
<div class="trend-chart">
|
||||
<TrendChart :data="systemStorageData" color="#00BCD4" />
|
||||
</div>
|
||||
<div class="metric-info">
|
||||
<div class="metric-value">
|
||||
{{ currentSysDiskUsage }}%
|
||||
<a-icon :class="['trend-icon', sysDiskChange >= 0 ? 'up' : 'down']"
|
||||
:type="sysDiskChange >= 0 ? 'arrow-up' : 'arrow-down'" />
|
||||
</div>
|
||||
<div class="metric-change">{{ sysDiskChange >= 0 ? '+' : '' }}{{ sysDiskChange }}% last 10s</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
|
||||
|
||||
</a-row>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.row-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid #1890ff;
|
||||
}
|
||||
|
||||
.dashboard-cards {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
border-radius: 8px;
|
||||
height: 100%;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.trend-chart {
|
||||
flex: 1;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.metric-info {
|
||||
margin-left: 16px;
|
||||
text-align: right;
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.metric-change {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.trend-icon {
|
||||
margin-left: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.up {
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.down {
|
||||
color: #F44336;
|
||||
}
|
||||
|
||||
.right {
|
||||
color: #2196F3;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="trend-chart-container" ref="chartContainer"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, PropType } from 'vue';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { LineChart } from 'echarts/charts';
|
||||
import { GridComponent } from 'echarts/components';
|
||||
import { SVGRenderer } from 'echarts/renderers';
|
||||
|
||||
echarts.use([LineChart, GridComponent, SVGRenderer]);
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array as PropType<number[]>,
|
||||
required: true
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#4CAF50'
|
||||
}
|
||||
});
|
||||
|
||||
const chartContainer = ref<HTMLElement | null>(null);
|
||||
let chart: echarts.ECharts | null = null;
|
||||
|
||||
const initChart = () => {
|
||||
if (!chartContainer.value) return;
|
||||
|
||||
chart = echarts.init(chartContainer.value);
|
||||
|
||||
const option = {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
show: false,
|
||||
data: ['40m', '', '', '', '', '', 'now']
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
show: false
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: props.data,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: props.color,
|
||||
width: 2
|
||||
},
|
||||
areaStyle: {
|
||||
color: {
|
||||
type: 'linear',
|
||||
x: 0,
|
||||
y: 0,
|
||||
x2: 0,
|
||||
y2: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 0,
|
||||
color: props.color + '40' // 40% 透明度
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: props.color + '00' // 0% 透明度
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
animation: false
|
||||
};
|
||||
|
||||
chart.setOption(option);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initChart();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
chart?.resize();
|
||||
});
|
||||
});
|
||||
|
||||
watch(() => props.data, () => {
|
||||
chart?.setOption({
|
||||
series: [{ data: props.data }]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.trend-chart-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -128,19 +128,6 @@
|
||||
align-items: baseline;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
/* Subscriber Information部分的宽度调整 */
|
||||
.skim .inner .data .item:nth-child(1) {
|
||||
width: 38%; /* Total Subscriber Base - 稍微宽一点 */
|
||||
}
|
||||
|
||||
.skim .inner .data .item:nth-child(2) {
|
||||
width: 33%; /* VoLTE - 保持不变 */
|
||||
}
|
||||
|
||||
.skim .inner .data .item:nth-child(3) {
|
||||
width: 29%; /* VoIP - 稍微窄一点 */
|
||||
}
|
||||
.skim .inner .data .item div {
|
||||
font-size: 1.467rem;
|
||||
color: #fff;
|
||||
|
||||
@@ -35,20 +35,16 @@ export const graphState = reactive<Record<string, any>>({
|
||||
export const graphG6 = ref<any>(null);
|
||||
|
||||
/**图点击选择 */
|
||||
export const graphNodeClickID = ref<string>('UPF_001');
|
||||
export const graphNodeClickID = ref<string>('UPF');
|
||||
|
||||
/**图节点网元信息状态 */
|
||||
export const graphNodeState = computed(() =>{
|
||||
return graphState.data.nodes.map((item: any) => ({
|
||||
export const graphNodeState = computed(() =>
|
||||
graphState.data.nodes.map((item: any) => ({
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
neInfo: item.neInfo,
|
||||
neState: item.neState,
|
||||
neInfoList:item.neInfoList,
|
||||
neStateMap: item.neStateMap,
|
||||
}))
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
/**图节点网元状态数量 */
|
||||
@@ -72,69 +68,48 @@ export const graphNodeStateNum = computed(() => {
|
||||
export const neStateRequestMap = ref<Map<string, boolean>>(new Map());
|
||||
|
||||
/**neStateParse 网元状态 数据解析 */
|
||||
export function neStateParse(neType: string, data: Record<string, any>,neId: string) {
|
||||
// console.log('neStateParse',neType, data, neId);
|
||||
|
||||
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);
|
||||
//console.log('neStateParse',node);
|
||||
|
||||
if (!node) return;
|
||||
|
||||
// 初始化状态映射
|
||||
if (!node.neStateMap) node.neStateMap = {};
|
||||
|
||||
// 更新网元状态
|
||||
const newNeState :any = {
|
||||
...data, // 先展开data对象
|
||||
const newNeState = Object.assign(node.neState, data, {
|
||||
refreshTime: parseDateToStr(data.refreshTime, 'HH:mm:ss'),
|
||||
online: !!data.cpu,
|
||||
neId: neId
|
||||
};
|
||||
// 如果是001,更新节点状态。neInfo为主要的网元信息
|
||||
if (node.neInfo && node.neInfo.neId === neId) {
|
||||
Object.assign(node.neState, newNeState);
|
||||
}
|
||||
});
|
||||
|
||||
//console.log(node.neState)
|
||||
// 无论是否为主要网元,都更新状态映射
|
||||
node.neStateMap[neId] = {...newNeState};
|
||||
// 通过 ID 查询节点实例
|
||||
// 通过 ID 查询节点实例
|
||||
const item = graphG6.value.findById(node.id);
|
||||
if (item) {
|
||||
// 检查当前节点下所有网元的状态
|
||||
const allStates = Object.values(node.neStateMap);
|
||||
// 判断状态颜色
|
||||
let stateColor = '#52c41a'; // 默认绿色(所有网元都正常)
|
||||
if (allStates.some((state: any) => !state.online)) {
|
||||
// 如果有任何一个网元不正常
|
||||
stateColor = allStates.every((state: any) => !state.online) ? '#f5222d' : '#faad14'; // 红色(全部不正常)或黄色(部分不正常)
|
||||
}
|
||||
|
||||
// 图片类型不能填充
|
||||
if (node.type && node.type.startsWith('image')) {
|
||||
// 更新节点
|
||||
if (node.label !== newNeState.neType) {
|
||||
graphG6.value.updateItem(item, {
|
||||
label: newNeState.neType,
|
||||
});
|
||||
}
|
||||
// 设置状态
|
||||
graphG6.value.setItemState(item, 'top-right-dot', stateColor);
|
||||
} else {
|
||||
// 更新节点
|
||||
const stateColor = newNeState.online ? '#52c41a' : '#f5222d'; // 状态颜色
|
||||
// 图片类型不能填充
|
||||
if (node.type.startsWith('image')) {
|
||||
// 更新节点
|
||||
if (node.label !== newNeState.neName) {
|
||||
graphG6.value.updateItem(item, {
|
||||
label: newNeState.neType,
|
||||
style: {
|
||||
fill: stateColor, // 填充色
|
||||
stroke: stateColor, // 填充色
|
||||
},
|
||||
label: newNeState.neName,
|
||||
});
|
||||
// 设置状态
|
||||
graphG6.value.setItemState(item, 'stroke', newNeState.online);
|
||||
}
|
||||
|
||||
// 设置状态
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// 设置边状态
|
||||
@@ -192,6 +167,6 @@ export function topologyReset() {
|
||||
nodes: [],
|
||||
};
|
||||
graphG6.value = null;
|
||||
graphNodeClickID.value = 'UPF_001';
|
||||
graphNodeClickID.value = 'UPF';
|
||||
neStateRequestMap.value = new Map();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { parseSizeFromByte, parseSizeFromKbs } from '@/utils/parse-utils';
|
||||
import { parseSizeFromBits, parseSizeFromKbs } from '@/utils/parse-utils';
|
||||
import { ref } from 'vue';
|
||||
|
||||
type FDType = {
|
||||
@@ -23,7 +23,7 @@ export const upfFlowData = ref<FDType>({
|
||||
|
||||
/**UPF-流量数据 数据解析 */
|
||||
export function upfFlowParse(data: Record<string, string>) {
|
||||
upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup'], 'HH:mm:ss'));
|
||||
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);
|
||||
@@ -79,9 +79,9 @@ export function upfTFParse(day: string, data: Record<string, number>) {
|
||||
let { up, down } = data;
|
||||
upfTotalFlow.value[day] = {
|
||||
up: up,
|
||||
upFrom: parseSizeFromByte(up),
|
||||
upFrom: parseSizeFromBits(up),
|
||||
down: down,
|
||||
downFrom: parseSizeFromByte(down),
|
||||
downFrom: parseSizeFromBits(down),
|
||||
requestFlag: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -96,7 +96,11 @@ export function eventListParse(
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { RESULT_CODE_ERROR } from '@/constants/result-constants';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import {
|
||||
eventData,
|
||||
eventListParse,
|
||||
eventItemParseAndPush,
|
||||
userActivityReset,
|
||||
@@ -17,7 +16,10 @@ import { topologyReset, neStateParse, neStateRequestMap } from './useTopology';
|
||||
import PQueue from 'p-queue';
|
||||
|
||||
/**UPF-的Id */
|
||||
export const upfWhoId = ref<any>('001');
|
||||
export const upfWhoId = ref<any>('');
|
||||
|
||||
/**UPF-的RmUid */
|
||||
export const upfWhoRmUid = ref<any>('');
|
||||
|
||||
/**websocket连接 */
|
||||
export default function useWS() {
|
||||
@@ -31,6 +33,7 @@ export default function useWS() {
|
||||
|
||||
/**接收数据后回调 */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
//console.log(res);
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
@@ -39,8 +42,7 @@ export default function useWS() {
|
||||
// 网元状态
|
||||
if (requestId && requestId.startsWith('neState')) {
|
||||
const neType = requestId.split('_')[1];
|
||||
const neId = requestId.split('_')[2];
|
||||
neStateParse(neType, data, neId);
|
||||
neStateParse(neType, data);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,31 +52,28 @@ export default function useWS() {
|
||||
case 'amf_1010_001':
|
||||
if (Array.isArray(data.rows)) {
|
||||
eventListParse('amf_ue', data);
|
||||
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||
}
|
||||
break;
|
||||
// MME_UE会话事件
|
||||
case 'mme_1011_001':
|
||||
if (Array.isArray(data.rows)) {
|
||||
eventListParse('mme_ue', data);
|
||||
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||
}
|
||||
break;
|
||||
// IMS_CDR会话事件
|
||||
case 'ims_1005_001':
|
||||
if (Array.isArray(data.rows)) {
|
||||
eventListParse('ims_cdr', data);
|
||||
eventData.value.sort((a, b) => b.eTime - a.eTime);
|
||||
}
|
||||
break;
|
||||
//UPF-总流量数
|
||||
case `upf_${upfWhoId.value}_0`:
|
||||
case 'upf_001_0':
|
||||
upfTFParse('0', data);
|
||||
break;
|
||||
case `upf_${upfWhoId.value}_7`:
|
||||
case 'upf_001_7':
|
||||
upfTFParse('7', data);
|
||||
break;
|
||||
case `upf_${upfWhoId.value}_30`:
|
||||
case 'upf_001_30':
|
||||
upfTFParse('30', data);
|
||||
break;
|
||||
}
|
||||
@@ -84,7 +83,7 @@ export default function useWS() {
|
||||
}
|
||||
switch (data.groupId) {
|
||||
// kpiEvent 指标UPF
|
||||
case `10_UPF_${upfWhoId.value}`:
|
||||
case '10_UPF_' + upfWhoId.value:
|
||||
if (data.data) {
|
||||
upfFlowParse(data.data);
|
||||
}
|
||||
@@ -119,11 +118,11 @@ export default function useWS() {
|
||||
upfTotalFlow.value[day].requestFlag = true;
|
||||
|
||||
ws.send({
|
||||
requestId: `upf_${upfWhoId.value}_${day}`,
|
||||
requestId: `upf_001_${day}`,
|
||||
type: 'upf_tf',
|
||||
data: {
|
||||
neType: 'UPF',
|
||||
neId: upfWhoId.value,
|
||||
neId: '001',
|
||||
day: Number(day),
|
||||
},
|
||||
});
|
||||
@@ -194,7 +193,7 @@ export default function useWS() {
|
||||
* MME_UE会话事件(GroupID:1011_neId)
|
||||
* IMS_CDR会话事件(GroupID:1005_neId)
|
||||
*/
|
||||
subGroupID: `10_UPF_${neId},1010_001,1011_001,1005_001`,
|
||||
subGroupID: '10_UPF_' + neId + ',1010_001,1011_001,1005_001',
|
||||
},
|
||||
onmessage: wsMessage,
|
||||
onerror: (ev: any) => {
|
||||
|
||||
@@ -1,949 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, nextTick } 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 { listIMSSub } from '@/api/neData/ims_sub';
|
||||
import { listUDMAuth } from '@/api/neData/voip_auth';
|
||||
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 useNeInfoStore from '@/store/modules/neinfo';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { upfWhoId } from './hooks/useWS';
|
||||
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||
|
||||
const neInfoStore = useNeInfoStore();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const { wsSend, userActivitySend, upfTFSend, reSendUPF } = useWS();
|
||||
|
||||
// const viewportDom = ref<HTMLElement | null>(null);
|
||||
//
|
||||
// // 定义 resize 处理函数,确保能正确移除事件监听器
|
||||
// const handleResize = () => {
|
||||
// setTimeout(calculateScale, 100);
|
||||
// };
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// // 计算缩放比例的函数
|
||||
// const calculateScale = () => {
|
||||
// const container = document.querySelector('.dashboard-container') as HTMLElement;
|
||||
// const wrapper = document.querySelector('.dashboard-wrapper') as HTMLElement;
|
||||
//
|
||||
// if (!container || !wrapper) return;
|
||||
//
|
||||
// const containerWidth = 1400; // 设计宽度
|
||||
// const containerHeight = 900; // 设计高度
|
||||
//
|
||||
// // 获取可用空间(减去padding)
|
||||
// const availableWidth = wrapper.clientWidth - 40;
|
||||
// const availableHeight = wrapper.clientHeight - 40;
|
||||
//
|
||||
// // 计算缩放比例
|
||||
// const scaleX = availableWidth / containerWidth;
|
||||
// const scaleY = availableHeight / containerHeight;
|
||||
//
|
||||
// // 选择较小的比例,确保内容完全可见
|
||||
// const scale = Math.min(scaleX, scaleY);
|
||||
//
|
||||
//
|
||||
// container.style.transform = `scale(${scale})`;
|
||||
// container.style.transformOrigin = 'center center';
|
||||
// };
|
||||
|
||||
/**概览状态类型 */
|
||||
type SkimStateType = {
|
||||
/**UDM签约用户数量 */
|
||||
udmSubNum: number;
|
||||
/**SMF在线用户数 */
|
||||
smfUeNum: number;
|
||||
/**IMS在线用户数 */
|
||||
imsUeNum: number;
|
||||
/**5G基站数量 */
|
||||
gnbNum: number;
|
||||
/**5G在线用户数量 */
|
||||
gnbUeNum: number;
|
||||
/**4G基站数量 */
|
||||
enbNum: number;
|
||||
/**4G在线用户数量 */
|
||||
enbUeNum: number;
|
||||
/**IMS用户数量 */
|
||||
imsUserNum: number;
|
||||
/**VOIP用户数量 */
|
||||
voipUserNum: number;
|
||||
};
|
||||
|
||||
/**概览状态信息 */
|
||||
let skimState: SkimStateType = reactive({
|
||||
udmSubNum: 0,
|
||||
smfUeNum: 0,
|
||||
imsUeNum: 0,
|
||||
gnbNum: 0,
|
||||
gnbUeNum: 0,
|
||||
enbNum: 0,
|
||||
enbUeNum: 0,
|
||||
imsUserNum: 0,
|
||||
voipUserNum: 0,
|
||||
});
|
||||
|
||||
/**网元参数 */
|
||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**总览节点 */
|
||||
const viewportDom = ref<HTMLElement | null>(null);
|
||||
const { isFullscreen, toggle } = useFullscreen(viewportDom);
|
||||
|
||||
let initFlag = false;
|
||||
/**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 neInfoList = node.neInfoList || [];
|
||||
if (neInfoList.length === 0) continue;
|
||||
|
||||
for (const neInfo of neInfoList) {
|
||||
if (!neInfo.neType || !neInfo.neId) continue;
|
||||
|
||||
wsSend({
|
||||
requestId: `neState_${neInfo.neType}_${neInfo.neId}`,
|
||||
type: 'ne_state',
|
||||
data: {
|
||||
neType: neInfo.neType,
|
||||
neId: neInfo.neId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**获取概览信息 */
|
||||
async function fnGetSkim() {
|
||||
const neHandlers = new Map([
|
||||
// [
|
||||
// 'UDM',
|
||||
// {
|
||||
// request: (neId: string) =>
|
||||
// listUDMSub({ neId: neId, pageNum: 1, pageSize: 1 }),
|
||||
// process: (res: any) =>
|
||||
// res.code === RESULT_CODE_SUCCESS &&
|
||||
// (skimState.udmSubNum += res.total),
|
||||
// },
|
||||
// ],
|
||||
[
|
||||
'SMF',
|
||||
{
|
||||
request: (neId: string) => listUENumBySMF(neId),
|
||||
process: (res: any) =>
|
||||
res.code === RESULT_CODE_SUCCESS && (skimState.smfUeNum += res.data),
|
||||
},
|
||||
],
|
||||
[
|
||||
'IMS',
|
||||
{
|
||||
request: (neId: string) => listUENumByIMS(neId),
|
||||
process: (res: any) =>
|
||||
res.code === RESULT_CODE_SUCCESS && (skimState.imsUeNum += res.data),
|
||||
},
|
||||
],
|
||||
[
|
||||
'AMF',
|
||||
{
|
||||
request: (neId: string) => listBase5G({ neType: 'AMF', neId }),
|
||||
process: (res: any) => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.gnbNum += res.total;
|
||||
skimState.gnbUeNum += res.rows.reduce(
|
||||
(sum: number, item: any) => sum + item.ueNum,
|
||||
0
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'MME',
|
||||
{
|
||||
request: (neId: string) => listBase5G({ neType: 'MME', neId }),
|
||||
process: (res: any) => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.enbNum += res.total;
|
||||
skimState.enbUeNum += res.rows.reduce(
|
||||
(sum: number, item: any) => sum + item.ueNum,
|
||||
0
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
const requests = neCascaderOptions.value.flatMap(
|
||||
(ne: any) =>
|
||||
ne.children
|
||||
?.map((child: any) => {
|
||||
const handler = neHandlers.get(child.neType);
|
||||
return handler
|
||||
? {
|
||||
promise: handler.request(child.neId),
|
||||
process: handler.process,
|
||||
}
|
||||
: null;
|
||||
})
|
||||
.filter(Boolean) || []
|
||||
);
|
||||
|
||||
const results = await Promise.allSettled(requests.map(r => r.promise));
|
||||
|
||||
// 重置
|
||||
Object.assign(skimState, {
|
||||
// udmSubNum: 0,
|
||||
smfUeNum: 0,
|
||||
imsUeNum: 0,
|
||||
gnbNum: 0,
|
||||
gnbUeNum: 0,
|
||||
enbNum: 0,
|
||||
enbUeNum: 0,
|
||||
// imsUserNum: 0,
|
||||
// voipUserNum: 0,
|
||||
});
|
||||
results.forEach((result: any, index: any) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
requests[index].process(result.value);
|
||||
} else {
|
||||
requests[index].process(0);
|
||||
}
|
||||
});
|
||||
|
||||
// UDM
|
||||
listUDMSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.udmSubNum = res.total;
|
||||
}
|
||||
});
|
||||
|
||||
// IMS用户数
|
||||
listIMSSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.imsUserNum = res.total;
|
||||
}
|
||||
});
|
||||
|
||||
// VOIP用户数
|
||||
listUDMAuth({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.voipUserNum = res.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**初始数据函数 */
|
||||
function loadData() {
|
||||
fnGetNeState(); // 获取网元状态
|
||||
userActivitySend();
|
||||
upfTFSend('0');
|
||||
upfTFSend('7');
|
||||
upfTFSend('30');
|
||||
|
||||
clearInterval(interval10s.value);
|
||||
interval10s.value = setInterval(() => {
|
||||
if (!interval10s.value) return;
|
||||
if (upfTFActive.value === '0') {
|
||||
upfTFActive.value = '7';
|
||||
} else if (upfTFActive.value === '7') {
|
||||
upfTFActive.value = '30';
|
||||
} else if (upfTFActive.value === '30') {
|
||||
upfTFActive.value = '0';
|
||||
}
|
||||
upfTFSend('0');
|
||||
upfTFSend('7');
|
||||
upfTFSend('30');
|
||||
}, 10_000);
|
||||
|
||||
clearInterval(interval5s.value);
|
||||
interval5s.value = setInterval(() => {
|
||||
if (!interval5s.value || !initFlag) return;
|
||||
fnGetSkim(); // 获取概览信息
|
||||
fnGetNeState(); // 获取网元状态
|
||||
}, 10_000);
|
||||
}
|
||||
|
||||
/**栏目信息跳转 */
|
||||
function fnToRouter(name: string, query?: any) {
|
||||
router.push({ name, query });
|
||||
}
|
||||
|
||||
/**网元参数 */
|
||||
let neOtions = ref<Record<string, any>[]>([]);
|
||||
|
||||
// UPF实时流量下拉框选择
|
||||
function fnSelectNe(value: any, option: any) {
|
||||
upfWhoId.value = value;
|
||||
reSendUPF(value);
|
||||
// upfTotalFlow.value.map((item: any) => {
|
||||
// item.requestFlag = false;
|
||||
// });
|
||||
|
||||
for (var key in upfTotalFlow.value) {
|
||||
upfTotalFlow.value[key].requestFlag = false;
|
||||
}
|
||||
// loadData();
|
||||
}
|
||||
|
||||
let udmNeId = ref<string>('001');
|
||||
let udmOtions = ref<Record<string, any>[]>([]);
|
||||
let onlineOtions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**用户数量-选择UDM */
|
||||
function fnSelectUDM(e: any) {
|
||||
udmNeId.value = e.key;
|
||||
listUDMSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.udmSubNum = res.total;
|
||||
}
|
||||
});
|
||||
|
||||
// 更新IMS用户数
|
||||
listIMSSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.imsUserNum = res.total;
|
||||
}
|
||||
});
|
||||
|
||||
// 更新VOIP用户数
|
||||
listUDMAuth({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
skimState.voipUserNum = res.total;
|
||||
}
|
||||
});
|
||||
}
|
||||
/**资源控制-选择NE */
|
||||
function fnSelectNeRe(e: any) {
|
||||
graphNodeClickID.value = e.key;
|
||||
}
|
||||
//
|
||||
// 定义一个方法返回 views 容器
|
||||
const getPopupContainer = () => {
|
||||
// 使用 ref 或其他方式来引用你的 views 容器
|
||||
// 如果 views 容器直接在这个组件内部,你可以使用 ref
|
||||
// 但在这个例子中,我们假设它是通过类名来获取的
|
||||
// return document.querySelector('.dashboard-wrapper');
|
||||
return document.querySelector('.viewport');
|
||||
};
|
||||
import DashboardCards from './components/mfOverview/DashboardCards.vue';
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
|
||||
// // 使用自定义缩放方案
|
||||
// nextTick(() => {
|
||||
// // 确保DOM完全渲染
|
||||
// setTimeout(() => {
|
||||
// calculateScale();
|
||||
// }, 100);
|
||||
//
|
||||
// // 监听窗口大小变化
|
||||
// window.addEventListener('resize', handleResize);
|
||||
// });
|
||||
|
||||
neInfoStore
|
||||
.fnNelist()
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
if (res.data.length > 0) {
|
||||
// UPF
|
||||
let arr: Record<string, any>[] = [];
|
||||
res.data.forEach(i => {
|
||||
if (i.neType === 'UPF') {
|
||||
arr.push({ value: i.neId, label: i.neName, rmUid: i.rmUid });
|
||||
}
|
||||
});
|
||||
neOtions.value = arr;
|
||||
if (arr.length > 0) {
|
||||
//queryParams.neRealId = arr[0].value;
|
||||
fnSelectNe(arr[0].value, arr[0]);
|
||||
}
|
||||
//online Ne
|
||||
let onlineArr: Record<string, any>[] = [];
|
||||
|
||||
// UDM
|
||||
let arr1: Record<string, any>[] = [];
|
||||
res.data.forEach((v: any) => {
|
||||
if (
|
||||
v.status &&
|
||||
[
|
||||
'UDM',
|
||||
'UPF',
|
||||
'AUSF',
|
||||
'PCF',
|
||||
'SMF',
|
||||
'AMF',
|
||||
'OMC',
|
||||
'SMSC',
|
||||
'IMS',
|
||||
'MME',
|
||||
].includes(v.neType)
|
||||
) {
|
||||
onlineArr.push({
|
||||
value: v.neType + '_' + v.neId,
|
||||
label: v.neName,
|
||||
rmUid: v.rmUid,
|
||||
});
|
||||
}
|
||||
if (v.neType === 'UDM') {
|
||||
arr1.push({ value: v.neId, label: v.neName, rmUid: v.rmUid });
|
||||
}
|
||||
});
|
||||
udmOtions.value = arr1;
|
||||
onlineOtions.value = onlineArr;
|
||||
if (arr1.length > 0) {
|
||||
fnSelectUDM({ key: arr1[0].value });
|
||||
}
|
||||
|
||||
if (onlineArr.length > 0) {
|
||||
fnSelectNeRe({ key: onlineArr[0].value });
|
||||
}
|
||||
|
||||
// 过滤不可用的网元
|
||||
neCascaderOptions.value = neInfoStore.getNeCascaderOptions.filter(
|
||||
(item: any) => {
|
||||
return ['UDM', 'SMF', 'IMS', 'AMF', 'MME'].includes(item.value);
|
||||
}
|
||||
);
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
initFlag = true;
|
||||
fnGetSkim().then(() => {
|
||||
loadData();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(interval10s.value);
|
||||
interval10s.value = null;
|
||||
clearInterval(interval5s.value);
|
||||
interval5s.value = null;
|
||||
initFlag = false;
|
||||
// // 清理事件监听和样式
|
||||
// window.removeEventListener('resize', handleResize);
|
||||
|
||||
});
|
||||
</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" style="padding: 0.8rem 1.5rem">
|
||||
<h3 style="display: flex; align-items: center; justify-content: space-between; margin: 0 0 0.3rem 0; padding: 0; line-height: 1; height: 20px; font-size: 0.833rem">
|
||||
<span style="display: flex; align-items: center">
|
||||
<IdcardOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.skim.userTitle') }}
|
||||
</span>
|
||||
<a-dropdown
|
||||
:trigger="['click']"
|
||||
:get-Popup-Container="getPopupContainer"
|
||||
style="margin-left: 8px"
|
||||
>
|
||||
<div class="toDeep-text" style="padding: 2px 6px; font-size: 0.7rem; line-height: 1; height: 16px; display: inline-flex; align-items: center">
|
||||
{{ udmOtions.find(item => item.value === udmNeId)?.label || 'UDM' }}
|
||||
<DownOutlined style="margin-left: 4px; font-size: 9px" />
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="fnSelectUDM">
|
||||
<a-menu-item
|
||||
v-for="v in udmOtions"
|
||||
:key="v.value"
|
||||
:disabled="udmNeId === v.value"
|
||||
>
|
||||
{{ v.label }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</h3>
|
||||
<div class="data">
|
||||
<div
|
||||
class="item toRouter"
|
||||
:title="t('views.dashboard.overview.Users')"
|
||||
>
|
||||
<div @click="fnToRouter('Sub_2010')">
|
||||
<UserOutlined
|
||||
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||
/>
|
||||
{{ skimState.udmSubNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.users') }}
|
||||
<!-- <a-dropdown-->
|
||||
<!-- :trigger="['click']"-->
|
||||
<!-- :get-Popup-Container="getPopupContainer"-->
|
||||
<!-- >-->
|
||||
<!-- <div class="toDeep-text">-->
|
||||
<!-- {{ t('views.dashboard.overview.skim.users') }}-->
|
||||
<!-- <DownOutlined style="margin-left: 12px; font-size: 12px" />-->
|
||||
<!-- </div>-->
|
||||
<!-- <template #overlay>-->
|
||||
<!-- <a-menu @click="fnSelectUDM">-->
|
||||
<!-- <a-menu-item-->
|
||||
<!-- v-for="v in udmOtions"-->
|
||||
<!-- :key="v.value"-->
|
||||
<!-- :disabled="udmNeId === v.value"-->
|
||||
<!-- >-->
|
||||
<!-- {{ v.label }}-->
|
||||
<!-- </a-menu-item>-->
|
||||
<!-- </a-menu>-->
|
||||
<!-- </template>-->
|
||||
<!-- </a-dropdown>-->
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('ImsUDM_2012')"
|
||||
:title="t('views.dashboard.overview.IMSUsers')"
|
||||
style="margin: 0 12px"
|
||||
>
|
||||
<div>
|
||||
<UserOutlined
|
||||
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||
/>
|
||||
{{ skimState.imsUserNum }}
|
||||
</div>
|
||||
<span>
|
||||
{{t('views.dashboard.overview.skim.ims')}}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Voip_2011')"
|
||||
:title="t('views.dashboard.overview.VoIPUsers')"
|
||||
>
|
||||
<div>
|
||||
<UserOutlined
|
||||
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
|
||||
/>
|
||||
{{ skimState.voipUserNum }}
|
||||
</div>
|
||||
<span>
|
||||
{{t('views.dashboard.overview.skim.voip')}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="skim panel base" v-perms:has="['dashboard:overview:gnbBase']">
|
||||
<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('BaseStation_2096', { neType: 'AMF' })"
|
||||
:title="t('views.dashboard.overview.FivegNodeN')"
|
||||
>
|
||||
<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('BaseStation_2096', { neType: 'AMF' })"
|
||||
:title="t('views.dashboard.overview.Fiveusers')"
|
||||
>
|
||||
<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" v-perms:has="['dashboard:overview:enbBase']">
|
||||
<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('BaseStation_2096', { neType: 'MME' })"
|
||||
:title="t('views.dashboard.overview.FourgNodeN')"
|
||||
>
|
||||
<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('BaseStation_2096', { neType: 'MME' })"
|
||||
:title="t('views.dashboard.overview.Fourusers')"
|
||||
>
|
||||
<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>
|
||||
|
||||
<!-- Online Information -->
|
||||
<div class="skim panel base" v-perms:has="['dashboard:overview:onlineInfo']">
|
||||
<div class="inner">
|
||||
<h3>
|
||||
<IdcardOutlined style="color: #68d8fe" /> {{ t('views.dashboard.overview.skim.onlineinfo') }}
|
||||
</h3>
|
||||
<div class="data">
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Ims_2080')"
|
||||
:title="t('views.dashboard.overview.VoNR')"
|
||||
v-perms:has="['dashboard:overview:imsUeNum']"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<img :src="svgUserIMS" style="width: 18px; margin-right: 8px; height: 2rem" />
|
||||
{{ skimState.imsUeNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.imsUeNum') }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="item toRouter"
|
||||
@click="fnToRouter('Ue_2081')"
|
||||
:title="t('views.dashboard.overview.sessions')"
|
||||
v-perms:has="['dashboard:overview:smfUeNum']"
|
||||
>
|
||||
<div style="align-items: flex-start">
|
||||
<img :src="svgUserSMF" style="width: 18px; margin-right: 8px; height: 2rem" />
|
||||
{{ skimState.smfUeNum }}
|
||||
</div>
|
||||
<span>{{ t('views.dashboard.overview.skim.smfUeNum') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 用户行为 -->
|
||||
<div class="userActivity panel">
|
||||
<div class="inner">
|
||||
<h3>
|
||||
<WhatsAppOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.userActivity.title') }}
|
||||
</h3>
|
||||
<div class="chart">
|
||||
<UserActivity />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="userActivity panel">-->
|
||||
<!-- <div class="inner">-->
|
||||
<!-- <h3 style="display: flex; align-items: center">-->
|
||||
<!-- <DashboardOutlined style="color: #68d8fe" /> -->
|
||||
<!-- {{ t('views.dashboard.overview.resources.title') }}:-->
|
||||
<!-- <a-dropdown-->
|
||||
<!-- :trigger="['click']"-->
|
||||
<!-- :get-Popup-Container="getPopupContainer"-->
|
||||
<!-- >-->
|
||||
<!-- <div class="toDeep-text">-->
|
||||
<!-- {{ graphNodeClickID }}-->
|
||||
<!-- <DownOutlined style="margin-left: 12px; font-size: 12px" />-->
|
||||
<!-- </div>-->
|
||||
<!-- <template #overlay>-->
|
||||
<!-- <a-menu @click="fnSelectNeRe">-->
|
||||
<!-- <a-menu-item v-for="v in onlineOtions" :key="v.value">-->
|
||||
<!-- {{ v.label }}-->
|
||||
<!-- </a-menu-item>-->
|
||||
<!-- </a-menu>-->
|
||||
<!-- </template>-->
|
||||
<!-- </a-dropdown>-->
|
||||
<!-- </h3>-->
|
||||
<!-- <div class="chart">-->
|
||||
<!-- <NeResources />-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
<div class="column" style="flex: 4; margin: 1.333rem 0.833rem 0">
|
||||
<!-- 实时流量 -->
|
||||
<div class="upfFlow panel">
|
||||
<div class="inner">
|
||||
<h3
|
||||
class="toRouter"
|
||||
:title="t('views.dashboard.overview.UPFjump')"
|
||||
style="display: flex; align-items: center"
|
||||
>
|
||||
<AreaChartOutlined style="color: #68d8fe" />
|
||||
<span @click="fnToRouter('GoldTarget_2104')">{{
|
||||
t('views.dashboard.overview.upfFlow.title')
|
||||
}}</span>
|
||||
<a-select
|
||||
v-model:value="upfWhoId"
|
||||
:options="neOtions"
|
||||
:get-Popup-Container="getPopupContainer"
|
||||
class="toDeep"
|
||||
style="width: 100px; color: #fff; margin-left: auto"
|
||||
@change="fnSelectNe"
|
||||
/>
|
||||
</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.Networkjump')"
|
||||
>
|
||||
<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].upFrom }}</h4>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span>
|
||||
<ArrowDownOutlined style="color: #52c41a" />
|
||||
{{ t('views.dashboard.overview.upfFlowTotal.down') }}
|
||||
</span>
|
||||
<h4>{{ upfTotalFlow[upfTFActive].downFrom }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 告警统计 -->
|
||||
<div class="alarmType panel">
|
||||
<div class="inner">
|
||||
<h3
|
||||
class="toRouter"
|
||||
@click="fnToRouter('ActiveAlarm_2088')"
|
||||
:title="t('views.dashboard.overview.Alarmjump')"
|
||||
>
|
||||
<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 style="display: flex; align-items: center">
|
||||
<DashboardOutlined style="color: #68d8fe" />
|
||||
{{ t('views.dashboard.overview.resources.title') }}:
|
||||
<a-dropdown
|
||||
:trigger="['click']"
|
||||
:get-Popup-Container="getPopupContainer"
|
||||
>
|
||||
<div class="toDeep-text">
|
||||
{{ graphNodeClickID }}
|
||||
<DownOutlined style="margin-left: 12px; font-size: 12px" />
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu @click="fnSelectNeRe">
|
||||
<a-menu-item v-for="v in onlineOtions" :key="v.value">
|
||||
{{ v.label }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</h3>
|
||||
<div class="chart">
|
||||
<NeResources />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-page">
|
||||
<DashboardCards />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.cardClass {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@import url('./css/index.css');
|
||||
|
||||
/* Dashboard 页面专用包装器,不影响其他页面 */
|
||||
//.dashboard-wrapper {
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// background: #0b1023;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// padding: 20px;
|
||||
// overflow: hidden;
|
||||
// box-sizing: border-box;
|
||||
//}
|
||||
//
|
||||
//.dashboard-container {
|
||||
// width: 1400px;
|
||||
// height: 900px;
|
||||
// background: #0b1023;
|
||||
// position: relative;
|
||||
// overflow: visible;
|
||||
// flex-shrink: 0;
|
||||
//}
|
||||
|
||||
.toDeep {
|
||||
--editor-background-color: blue;
|
||||
}
|
||||
|
||||
.toDeep :deep(.ant-select-selector) {
|
||||
background-color: #101129;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.toDeep :deep(.ant-select-arrow) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.toDeep :deep(.ant-select-selection-item) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.toDeep-text {
|
||||
color: #4c9bfd !important;
|
||||
font-size: 0.844rem !important;
|
||||
position: relative !important;
|
||||
line-height: 2rem !important;
|
||||
white-space: nowrap !important;
|
||||
text-align: start !important;
|
||||
text-overflow: ellipsis !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import * as echarts from 'echarts/core';
|
||||
import {
|
||||
TitleComponent,
|
||||
TitleComponentOption,
|
||||
TooltipComponent,
|
||||
TooltipComponentOption,
|
||||
GridComponent,
|
||||
GridComponentOption,
|
||||
LegendComponent,
|
||||
LegendComponentOption,
|
||||
} from 'echarts/components';
|
||||
import {
|
||||
PieChart,
|
||||
PieSeriesOption,
|
||||
BarChart,
|
||||
BarSeriesOption,
|
||||
} from 'echarts/charts';
|
||||
import { LabelLayout } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
|
||||
import { markRaw, onMounted, ref } from 'vue';
|
||||
import { origGet, top3Sel } from '@/api/faultManage/actAlarm';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
echarts.use([
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
PieChart,
|
||||
BarChart,
|
||||
CanvasRenderer,
|
||||
LabelLayout,
|
||||
]);
|
||||
|
||||
type EChartsOption = echarts.ComposeOption<
|
||||
| TitleComponentOption
|
||||
| TooltipComponentOption
|
||||
| GridComponentOption
|
||||
| LegendComponentOption
|
||||
| PieSeriesOption
|
||||
| BarSeriesOption
|
||||
>;
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const alarmTypeBar = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图实例对象 */
|
||||
const alarmTypeBarChart = ref<any>(null);
|
||||
|
||||
/**告警类型数据 */
|
||||
const alarmTypeType = ref<any>([
|
||||
{
|
||||
value: 0,
|
||||
name: t('views.index.Critical'),
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
name: t('views.index.Major'),
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
name: t('views.index.Minor'),
|
||||
},
|
||||
{
|
||||
value: 0,
|
||||
name: t('views.index.Warning'),
|
||||
},
|
||||
// {
|
||||
// value: 0,
|
||||
// name: t('views.index.Event'),
|
||||
// },
|
||||
]);
|
||||
|
||||
/**告警类型Top数据 */
|
||||
const alarmTypeTypeTop = ref<any>([
|
||||
{ name: 'AMF', value: 0 },
|
||||
{ name: 'UDM', value: 0 },
|
||||
{ name: 'SMF', value: 0 },
|
||||
]);
|
||||
|
||||
//
|
||||
function initPicture() {
|
||||
Promise.allSettled([origGet(), top3Sel()])
|
||||
.then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
const res0 = resArr[0].value;
|
||||
if (res0.code === RESULT_CODE_SUCCESS && Array.isArray(res0.data)) {
|
||||
for (const item of res0.data) {
|
||||
let index = 0;
|
||||
switch (item.name) {
|
||||
case 'Critical':
|
||||
index = 0;
|
||||
break;
|
||||
case 'Major':
|
||||
index = 1;
|
||||
break;
|
||||
case 'Minor':
|
||||
index = 2;
|
||||
break;
|
||||
case 'Warning':
|
||||
index = 3;
|
||||
break;
|
||||
// case 'Event':
|
||||
// index = 4;
|
||||
// break;
|
||||
}
|
||||
alarmTypeType.value[index].value = Number(item.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
const res1 = resArr[1].value;
|
||||
if (res1.code === RESULT_CODE_SUCCESS && Array.isArray(res1.data)) {
|
||||
alarmTypeTypeTop.value = alarmTypeTypeTop.value
|
||||
.concat(res1.data)
|
||||
.sort((a: any, b: any) => {
|
||||
return b.value - a.value;
|
||||
})
|
||||
.slice(0, 3);
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
const optionData: EChartsOption = {
|
||||
title: [
|
||||
{
|
||||
text: 'Top3',
|
||||
left: 'center',
|
||||
top: '36%',
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
},
|
||||
],
|
||||
grid: [
|
||||
{ // 主图
|
||||
top: '5%',
|
||||
left: '20%',
|
||||
right: '10%',
|
||||
height: '35%'
|
||||
},
|
||||
{ // Top3
|
||||
top: '50%',
|
||||
left: '20%',
|
||||
right: '10%',
|
||||
height: '30%'
|
||||
}
|
||||
],
|
||||
tooltip: {
|
||||
|
||||
axisPointer: { type: 'shadow' },
|
||||
formatter: '{b} : {c}',
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
gridIndex: 0,
|
||||
show: false,
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
gridIndex: 1,
|
||||
show: false,
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
gridIndex: 0,
|
||||
data: alarmTypeType.value.map((item: any) => item.name),
|
||||
axisLabel: { color: '#fff', fontSize: 14,fontWeight: 'bold' },
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
inverse: true
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
gridIndex: 1,
|
||||
data: alarmTypeTypeTop.value.map((item: any) => item.name),
|
||||
axisLabel: { color: '#fff', fontSize: 14,fontWeight: 'bold' },
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false },
|
||||
inverse: true
|
||||
}
|
||||
],
|
||||
series: [
|
||||
// 四类型告警横向柱状图
|
||||
{
|
||||
type: 'bar',
|
||||
xAxisIndex: 0,
|
||||
yAxisIndex: 0,
|
||||
barWidth: 18,
|
||||
itemStyle: {
|
||||
borderRadius: [0, 8, 8, 0],
|
||||
color: function (params: any) {
|
||||
// 渐变色
|
||||
const colorArr = [
|
||||
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#f5222d' },
|
||||
{ offset: 1, color: '#fa8c16' }
|
||||
]),
|
||||
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#fa8c16' },
|
||||
{ offset: 1, color: '#fadb14' }
|
||||
]),
|
||||
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#fadb14' },
|
||||
{ offset: 1, color: '#1677ff' }
|
||||
]),
|
||||
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#1677ff' },
|
||||
{ offset: 1, color: '#00fcff' }
|
||||
])
|
||||
];
|
||||
return colorArr[params.dataIndex] || colorArr[3];
|
||||
}
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
color: '#fff', //淡蓝色
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
formatter: (params: any) => {
|
||||
if (!params.value) return '';
|
||||
return `${params.value}`;
|
||||
},
|
||||
},
|
||||
data: alarmTypeType.value.map((item: any) => item.value),
|
||||
zlevel: 2
|
||||
},
|
||||
// Top3横向柱状图
|
||||
{
|
||||
name: 'Top3',
|
||||
type: 'bar',
|
||||
xAxisIndex: 1,
|
||||
yAxisIndex: 1,
|
||||
barWidth: 18,
|
||||
itemStyle: {
|
||||
borderRadius: [0, 20, 20, 0], // 圆角(左上、右上、右下、左下)
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
|
||||
{ offset: 0, color: '#f0f5ff' },
|
||||
{ offset: 0.5, color: '#adc6ff' },
|
||||
{ offset: 1, color: '#2f54eb' },
|
||||
]), // 渐变
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
position: 'right',
|
||||
color: '#fff', //淡蓝色
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
formatter: '{c}'
|
||||
},
|
||||
data: alarmTypeTypeTop.value.map((item: any) => item.value),
|
||||
zlevel: 1
|
||||
}
|
||||
]
|
||||
};
|
||||
fnDesign(alarmTypeBar.value, optionData);
|
||||
});
|
||||
}
|
||||
|
||||
function fnDesign(container: HTMLElement | undefined, option: any) {
|
||||
if (!container) return;
|
||||
|
||||
alarmTypeBarChart.value = markRaw(echarts.init(container, 'light'));
|
||||
option && alarmTypeBarChart.value.setOption(option);
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (alarmTypeBarChart.value) {
|
||||
alarmTypeBarChart.value.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initPicture();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="alarmTypeBar" class="chart-container"></div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart-container {
|
||||
/* 设置图表容器大小和位置 */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,268 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { parseDuration, parseDateToStr } from '@/utils/date-utils';
|
||||
import { eventData, eventId } from '../../hooks/useUserActivity';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { onMounted, reactive } from 'vue';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCode: DictType[];
|
||||
/**CDR 呼叫类型 */
|
||||
cdrCallType: DictType[];
|
||||
/**UE 事件认证代码类型 */
|
||||
ueAauthCode: DictType[];
|
||||
/**UE 事件类型 */
|
||||
ueEventType: DictType[];
|
||||
/**UE 事件CM状态 */
|
||||
ueEventCmState: DictType[];
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCodeCause: DictType[];
|
||||
} = reactive({
|
||||
cdrSipCode: [],
|
||||
cdrCallType: [],
|
||||
ueAauthCode: [],
|
||||
ueEventType: [],
|
||||
ueEventCmState: [],
|
||||
cdrSipCodeCause: [],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('cdr_sip_code'),
|
||||
getDict('cdr_call_type'),
|
||||
getDict('ue_auth_code'),
|
||||
getDict('ue_event_type'),
|
||||
getDict('ue_event_cm_state'),
|
||||
getDict('cdr_sip_code_cause'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrSipCode = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.cdrCallType = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.ueAauthCode = resArr[2].value;
|
||||
}
|
||||
if (resArr[3].status === 'fulfilled') {
|
||||
dict.ueEventType = resArr[3].value;
|
||||
}
|
||||
if (resArr[4].status === 'fulfilled') {
|
||||
dict.ueEventCmState = resArr[4].value;
|
||||
}
|
||||
if (resArr[5].status === 'fulfilled') {
|
||||
dict.cdrSipCodeCause = resArr[5].value;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="activty">
|
||||
<template v-for="item in eventData" :key="item.eId">
|
||||
<!-- CDR事件IMS -->
|
||||
<div class="card-cdr" :class="{ active: item.eId === eventId }" v-if="item.eType === 'ims_cdr'">
|
||||
<div class="card-cdr-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||
<span>
|
||||
<DictTag :options="dict.cdrCallType" :value="item.data.callType" />
|
||||
</span>
|
||||
</div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="card-cdr-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.caller') }}:
|
||||
<span :title="item.data.callerParty">
|
||||
{{ item.data.callerParty }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.called') }}:
|
||||
<span :title="item.data.calledParty">
|
||||
{{ item.data.calledParty }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="item.data.callType !== 'sms'">
|
||||
{{ t('views.dashboard.overview.userActivity.duration') }}:
|
||||
<span>{{ parseDuration(item.data.callDuration) }}</span>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<span :title="item.data.releaseTime">
|
||||
{{
|
||||
typeof item.data.releaseTime === 'number'
|
||||
? parseDateToStr(+item.data.releaseTime * 1000)
|
||||
: parseDateToStr(item.data.releaseTime)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span v-if="item.data.callType !== 'sms'">
|
||||
<DictTag :options="dict.cdrSipCode" :value="item.data.cause" value-default="0" />
|
||||
<DictTag :options="dict.cdrSipCodeCause" :value="item.data.cause" value-default="0" />
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.overview.userActivity.resultOK') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.activty {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: 94%;
|
||||
color: #61a8ff;
|
||||
font-size: 0.75rem;
|
||||
|
||||
& .card-ue {
|
||||
border: 1px #61a8ff solid;
|
||||
border-radius: 4px;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-bottom: 0.3rem;
|
||||
line-height: 1rem;
|
||||
|
||||
& span {
|
||||
color: #68d8fe;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
&>div {
|
||||
width: 50%;
|
||||
white-space: nowrap;
|
||||
text-align: start;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&-w33 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
&>div {
|
||||
width: 33%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .card-cdr {
|
||||
border: 1px #61a8ff solid;
|
||||
border-radius: 4px;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-bottom: 0.3rem;
|
||||
line-height: 1rem;
|
||||
|
||||
& span {
|
||||
color: #68d8fe;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
&>div {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
text-align: start;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .active {
|
||||
color: #faad14;
|
||||
border: 1px #faad14 solid;
|
||||
animation: backInRight 0.3s alternate;
|
||||
|
||||
& span {
|
||||
color: #faad14;
|
||||
}
|
||||
}
|
||||
|
||||
/* 兼容当行显示字内容 */
|
||||
@media (max-width: 1720px) {
|
||||
& .card-cdr {
|
||||
&-item {
|
||||
display: block;
|
||||
|
||||
&>div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .card-ue {
|
||||
&-item {
|
||||
display: block;
|
||||
|
||||
&>div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 修改滚动条的样式 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
/* 设置滚动条宽度 */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: #101129;
|
||||
/* 设置滚动条轨道背景颜色 */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #28293f;
|
||||
/* 设置滚动条滑块颜色 */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #68d8fe;
|
||||
/* 设置鼠标悬停时滚动条滑块颜色 */
|
||||
}
|
||||
|
||||
@keyframes backInRight {
|
||||
0% {
|
||||
opacity: 0.7;
|
||||
-webkit-transform: translateX(2000px) scale(0.7);
|
||||
transform: translateX(2000px) scale(0.7);
|
||||
}
|
||||
|
||||
80% {
|
||||
opacity: 0.7;
|
||||
-webkit-transform: translateX(0) scale(0.7);
|
||||
transform: translateX(0) scale(0.7);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,352 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, nextTick, watch } from 'vue';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { GridComponent, GridComponentOption, TooltipComponent } from 'echarts/components';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { graphNodeClickID, graphNodeState } from '../../hooks/useTopology';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { markRaw } from 'vue';
|
||||
// 引入液体填充图表
|
||||
import 'echarts-liquidfill';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
echarts.use([GridComponent, TooltipComponent, CanvasRenderer]);
|
||||
|
||||
type EChartsOption = echarts.ComposeOption<GridComponentOption>;
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const neResourcesDom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图实例对象 */
|
||||
const neResourcesChart = ref<any>(null);
|
||||
|
||||
// 当前选中的网元ID
|
||||
const currentNeId = ref('');
|
||||
|
||||
// 资源数据
|
||||
const resourceData = ref({
|
||||
neCpu: 1,
|
||||
sysCpu: 1,
|
||||
sysMem: 1,
|
||||
sysDisk: 1
|
||||
});
|
||||
|
||||
// 获取颜色
|
||||
function getColorByValue(value: number) {
|
||||
if (value >= 70) {
|
||||
return ['#f5222d', '#ff7875']; // 红色
|
||||
} else if (value >= 30) {
|
||||
return ['#2f54eb', '#597ef7']; // 蓝色
|
||||
} else {
|
||||
return ['#52c41a', '#95de64']; // 绿色
|
||||
}
|
||||
}
|
||||
|
||||
/**图数据 */
|
||||
const optionData: any = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{b}: {c}%'
|
||||
},
|
||||
grid: {
|
||||
top: '10%',
|
||||
bottom: '5%',
|
||||
left: '5%',
|
||||
right: '5%',
|
||||
containLabel: true
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'liquidFill',
|
||||
radius: '50%',
|
||||
center: ['15%', '35%'],
|
||||
data: [resourceData.value.neCpu / 100],
|
||||
name: t('views.dashboard.overview.resources.neCpu'),
|
||||
color: getColorByValue(resourceData.value.neCpu),
|
||||
backgroundStyle: {
|
||||
color: 'rgba(10, 60, 160, 0.1)'
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.neCpu')}\n${resourceData.value.neCpu}%`;
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
show: true,
|
||||
borderDistance: 2,
|
||||
itemStyle: {
|
||||
borderColor: '#0a3ca0',
|
||||
borderWidth: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'liquidFill',
|
||||
radius: '50%',
|
||||
center: ['85%', '35%'],
|
||||
data: [resourceData.value.sysCpu / 100],
|
||||
name: t('views.dashboard.overview.resources.sysCpu'),
|
||||
color: getColorByValue(resourceData.value.sysCpu),
|
||||
backgroundStyle: {
|
||||
color: 'rgba(10, 60, 160, 0.1)'
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.sysCpu')}\n${resourceData.value.sysCpu}%`;
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
show: true,
|
||||
borderDistance: 2,
|
||||
itemStyle: {
|
||||
borderColor: '#0a3ca0',
|
||||
borderWidth: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'liquidFill',
|
||||
radius: '50%',
|
||||
center: ['35%', '65%'],
|
||||
data: [resourceData.value.sysMem / 100],
|
||||
name: t('views.dashboard.overview.resources.sysMem'),
|
||||
color: getColorByValue(resourceData.value.sysMem),
|
||||
backgroundStyle: {
|
||||
color: 'rgba(10, 60, 160, 0.1)'
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.sysMem')}\n${resourceData.value.sysMem}%`;
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
show: true,
|
||||
borderDistance: 2,
|
||||
itemStyle: {
|
||||
borderColor: '#0a3ca0',
|
||||
borderWidth: 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'liquidFill',
|
||||
radius: '50%',
|
||||
center: ['65%', '65%'],
|
||||
data: [resourceData.value.sysDisk / 100],
|
||||
name: t('views.dashboard.overview.resources.sysDisk'),
|
||||
color: getColorByValue(resourceData.value.sysDisk),
|
||||
backgroundStyle: {
|
||||
color: 'rgba(10, 60, 160, 0.1)'
|
||||
},
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.sysDisk')}\n${resourceData.value.sysDisk}%`;
|
||||
},
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: '#fff'
|
||||
}
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
show: true,
|
||||
borderDistance: 2,
|
||||
itemStyle: {
|
||||
borderColor: '#0a3ca0',
|
||||
borderWidth: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/**图数据渲染 */
|
||||
function handleRanderChart(
|
||||
container: HTMLElement | undefined,
|
||||
option: any
|
||||
) {
|
||||
if (!container) return;
|
||||
neResourcesChart.value = markRaw(echarts.init(container));
|
||||
option && neResourcesChart.value.setOption(option);
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (neResourcesChart.value) {
|
||||
neResourcesChart.value.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
function fnChangeData(data: any[], itemID: string) {
|
||||
const neType = itemID.split('_')[0];
|
||||
const neID = itemID.split('_')[1];
|
||||
currentNeId.value = neID;
|
||||
|
||||
let info = data.find((item: any) => item.id === neType);
|
||||
if (!info || !info.neStateMap[neID]?.online) return;
|
||||
|
||||
let sysCpuUsage = 0;
|
||||
let nfCpuUsage = 0;
|
||||
if (info.neStateMap[neID].cpu) {
|
||||
nfCpuUsage = info.neStateMap[neID].cpu.nfCpuUsage;
|
||||
const nfCpu = +(info.neStateMap[neID].cpu.nfCpuUsage / 100);
|
||||
nfCpuUsage = +nfCpu.toFixed(2);
|
||||
if (nfCpuUsage > 100) {
|
||||
nfCpuUsage = 100;
|
||||
}
|
||||
|
||||
sysCpuUsage = info.neStateMap[neID].cpu.sysCpuUsage;
|
||||
let sysCpu = +(info.neStateMap[neID].cpu.sysCpuUsage / 100);
|
||||
sysCpuUsage = +sysCpu.toFixed(2);
|
||||
if (sysCpuUsage > 100) {
|
||||
sysCpuUsage = 100;
|
||||
}
|
||||
}
|
||||
|
||||
let sysMemUsage = 0;
|
||||
if (info.neStateMap[neID].mem) {
|
||||
const men = info.neStateMap[neID].mem.sysMemUsage;
|
||||
sysMemUsage = +(men / 100).toFixed(2);
|
||||
if (sysMemUsage > 100) {
|
||||
sysMemUsage = 100;
|
||||
}
|
||||
}
|
||||
|
||||
let sysDiskUsage = 0;
|
||||
if (info.neStateMap[neID].disk && Array.isArray(info.neStateMap[neID].disk.partitionInfo)) {
|
||||
let disks: any[] = info.neStateMap[neID].disk.partitionInfo;
|
||||
disks = disks.sort((a, b) => +b.used - +a.used);
|
||||
if (disks.length > 0) {
|
||||
const { total, used } = disks[0];
|
||||
sysDiskUsage = +((used / total) * 100).toFixed(2);
|
||||
}
|
||||
}
|
||||
|
||||
resourceData.value = {
|
||||
neCpu: nfCpuUsage,
|
||||
sysCpu: sysCpuUsage,
|
||||
sysMem: sysMemUsage,
|
||||
sysDisk: sysDiskUsage
|
||||
};
|
||||
|
||||
// 更新图表数据
|
||||
neResourcesChart.value.setOption({
|
||||
series: [
|
||||
{
|
||||
data: [resourceData.value.neCpu / 100],
|
||||
color: getColorByValue(resourceData.value.neCpu),
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.neCpu')}\n${resourceData.value.neCpu}%`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
data: [resourceData.value.sysCpu / 100],
|
||||
color: getColorByValue(resourceData.value.sysCpu),
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.sysCpu')}\n${resourceData.value.sysCpu}%`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
data: [resourceData.value.sysMem / 100],
|
||||
color: getColorByValue(resourceData.value.sysMem),
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.sysMem')}\n${resourceData.value.sysMem}%`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
data: [resourceData.value.sysDisk / 100],
|
||||
color: getColorByValue(resourceData.value.sysDisk),
|
||||
label: {
|
||||
normal: {
|
||||
formatter: () => {
|
||||
return `${t('views.dashboard.overview.resources.sysDisk')}\n${resourceData.value.sysDisk}%`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
graphNodeState,
|
||||
v => {
|
||||
fnChangeData(v, graphNodeClickID.value);
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(graphNodeClickID, v => {
|
||||
fnChangeData(graphNodeState.value, v);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
handleRanderChart(neResourcesDom.value, optionData);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="resource-panel">
|
||||
<div ref="neResourcesDom" class="chart"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.resource-panel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: -32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.panel-title {
|
||||
font-size: 14px;
|
||||
color: #00fcff;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid rgba(10, 115, 255, 0.3);
|
||||
}
|
||||
|
||||
.chart {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,337 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { getGraphData } from '@/api/monitor/topology';
|
||||
import { Graph, GraphData, Tooltip } from '@antv/g6';
|
||||
import { parseBasePath } from '@/plugins/file-static-url';
|
||||
import { edgeLineAnimateState } from '@/views/monitor/topologyBuild/hooks/registerEdge';
|
||||
import { nodeImageAnimateState } from '@/views/monitor/topologyBuild/hooks/registerNode';
|
||||
import {
|
||||
graphG6,
|
||||
graphState,
|
||||
graphNodeClickID,
|
||||
notNeNodes,
|
||||
} from '../../hooks/useTopology';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const graphG6Dom = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图节点展示 */
|
||||
const graphNodeTooltip = new Tooltip({
|
||||
offsetX: 10,
|
||||
offsetY: 20,
|
||||
getContent(evt) {
|
||||
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
|
||||
const { id, label, neState, neInfoList, neStateMap }: any = evt.item?.getModel();
|
||||
//console.log(neInfoList,neState,neInfoList);
|
||||
if (notNeNodes.includes(id)) {
|
||||
return `<div><span>${label || id}</span></div>`;
|
||||
}
|
||||
if (!neState) {
|
||||
return `<div><span>${label || id}</span></div>`;
|
||||
}
|
||||
|
||||
// 获取同类型网元列表
|
||||
const sameTypeNes = neInfoList || [];
|
||||
|
||||
// 如果没有网元或只有一个网元,显示原来的信息
|
||||
if (sameTypeNes.length <= 1) {
|
||||
return `
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 200px;
|
||||
"
|
||||
>
|
||||
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||
${
|
||||
neState.online
|
||||
? t('views.monitor.topology.normalcy')
|
||||
: t('views.monitor.topology.exceptions')
|
||||
}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.refreshTime')}:</strong><span>
|
||||
${neState.refreshTime ?? '--'}
|
||||
</span></div>
|
||||
<div>========================</div>
|
||||
<div><strong>ID:</strong><span>${neState.neId}</span></div>
|
||||
<div><strong>${t('views.monitor.topology.name')}:</strong><span>
|
||||
${neState.neName ?? '--'}
|
||||
</span></div>
|
||||
<div><strong>IP:</strong><span>${neState.neIP ?? '--'}</span></div>
|
||||
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
|
||||
${neState.version ?? '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.serialNum')}:</strong><span>
|
||||
${neState.sn ?? '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
|
||||
${neState.expire ?? '--'}
|
||||
</span></div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 如果有多个网元,聚合显示
|
||||
let content = `
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 300px;
|
||||
"
|
||||
>
|
||||
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||
${
|
||||
neState.online
|
||||
? t('views.monitor.topology.normalcy')
|
||||
: t('views.monitor.topology.exceptions')
|
||||
}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.refreshTime')}:</strong><span>
|
||||
${neState.refreshTime ?? '--'}
|
||||
</span></div>
|
||||
<div>========================</div>`;
|
||||
|
||||
// 为每个同类型网元添加信息
|
||||
sameTypeNes.forEach((ne: any, index: number) => {
|
||||
// 获取该网元的状态信息
|
||||
const neStateInfo = neStateMap?.[ne.neId] ||
|
||||
(ne.neId === neState.neId ? neState : {});
|
||||
|
||||
content += `
|
||||
<div style="margin-top: 8px;"><strong>${t('views.monitor.topology.name')}:${ne.neName || id + '_' + ne.neId}</strong></div>
|
||||
<div><strong>ID:</strong><span>${ne.neId || '--'}</span></div>
|
||||
<div><strong>IP:</strong><span>${neStateInfo.neIP || ne.neIP || '--'}</span></div>
|
||||
<div><strong>${t('views.monitor.topology.version')}:</strong><span>
|
||||
${neStateInfo.version || ne.version || '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.serialNum')}:</strong><span>
|
||||
${neStateInfo.sn || ne.sn || '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.expiryDate')}:</strong><span>
|
||||
${neStateInfo.expire || ne.expire || '--'}
|
||||
</span></div>
|
||||
<div><strong>${t('views.monitor.topology.state')}:</strong><span>
|
||||
${
|
||||
neStateInfo.online !== undefined
|
||||
? (neStateInfo.online
|
||||
? t('views.monitor.topology.normalcy')
|
||||
: t('views.monitor.topology.exceptions'))
|
||||
: 'undefined'
|
||||
}
|
||||
</span></div>
|
||||
${index < sameTypeNes.length - 1 ? '<div>------------------------</div>' : ''}
|
||||
`;
|
||||
});
|
||||
|
||||
content += '</div>';
|
||||
return content;
|
||||
},
|
||||
itemTypes: ['node'],
|
||||
});
|
||||
|
||||
|
||||
|
||||
/**图数据渲染 */
|
||||
function handleRanderGraph(
|
||||
container: HTMLElement | undefined,
|
||||
data: GraphData
|
||||
) {
|
||||
if (!container) return;
|
||||
const { clientHeight, clientWidth } = container;
|
||||
|
||||
edgeLineAnimateState();
|
||||
nodeImageAnimateState();
|
||||
|
||||
const graph = new Graph({
|
||||
container: container,
|
||||
width: clientWidth,
|
||||
height: clientHeight - 36,
|
||||
fitCenter: true,
|
||||
fitView: true,
|
||||
fitViewPadding: [20],
|
||||
autoPaint: true,
|
||||
modes: {
|
||||
// default: ['drag-canvas', 'zoom-canvas'],
|
||||
},
|
||||
groupByTypes: false,
|
||||
nodeStateStyles: {
|
||||
selected: {
|
||||
fill: 'transparent',
|
||||
},
|
||||
},
|
||||
plugins: [graphNodeTooltip],
|
||||
animate: true, // 是否使用动画过度,默认为 false
|
||||
animateCfg: {
|
||||
duration: 500, // Number,一次动画的时长
|
||||
easing: 'linearEasing', // String,动画函数
|
||||
},
|
||||
});
|
||||
graph.data(data);
|
||||
graph.render();
|
||||
|
||||
|
||||
graphG6.value = graph;
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(function (entries) {
|
||||
// 当元素大小发生变化时触发回调函数
|
||||
entries.forEach(function (entry) {
|
||||
if (!graphG6.value) {
|
||||
return;
|
||||
}
|
||||
graphG6.value.changeSize(
|
||||
entry.contentRect.width,
|
||||
entry.contentRect.height - 20
|
||||
);
|
||||
graphG6.value.fitCenter();
|
||||
});
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图组数据渲染到画布
|
||||
* @param reload 是否重载数据
|
||||
*/
|
||||
function fnGraphDataLoad(reload: boolean = false) {
|
||||
Promise.all([
|
||||
getGraphData(graphState.group),
|
||||
listAllNeInfo({
|
||||
bandStatus: false,
|
||||
}),
|
||||
])
|
||||
.then(resArr => {
|
||||
const graphRes = resArr[0];
|
||||
const neRes = resArr[1];
|
||||
if (
|
||||
graphRes.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(graphRes.data.nodes) &&
|
||||
graphRes.data.nodes.length > 0 &&
|
||||
neRes.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(neRes.data) &&
|
||||
neRes.data.length > 0
|
||||
) {
|
||||
return {
|
||||
graphData: graphRes.data,
|
||||
neList: neRes.data,
|
||||
};
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.monitor.topology.noData'),
|
||||
duration: 5,
|
||||
});
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
if (!res) return;
|
||||
const { combos, edges, nodes } = res.graphData;
|
||||
|
||||
// 按网元类型分组
|
||||
const neTypeMap = new Map();
|
||||
res.neList.forEach(ne => {
|
||||
if (!ne.neType) return;
|
||||
if (!neTypeMap.has(ne.neType)) {
|
||||
neTypeMap.set(ne.neType, []);
|
||||
}
|
||||
neTypeMap.get(ne.neType).push(ne);
|
||||
});
|
||||
|
||||
// 节点过滤
|
||||
const nf: Record<string, any>[] = nodes.filter(
|
||||
(node: Record<string, any>) => {
|
||||
Reflect.set(node, 'neState', { online: false });
|
||||
Reflect.set(node, 'neStateMap', {}); // 初始化状态映射
|
||||
|
||||
// 图片路径处理
|
||||
if (node.img) node.img = parseBasePath(node.img);
|
||||
if (node.icon.show && node.icon?.img){
|
||||
node.icon.img = parseBasePath(node.icon.img);
|
||||
}
|
||||
|
||||
// 遍历是否有网元数据
|
||||
const nodeID: string = node.id;
|
||||
|
||||
// 处理非网元节点
|
||||
if (notNeNodes.includes(nodeID)) {
|
||||
return true;
|
||||
}
|
||||
//(neTypeMap.get(nodeID),nodeID,node.neState)
|
||||
// 处理网元节点
|
||||
if (neTypeMap.has(nodeID)) {
|
||||
// all NeInfo
|
||||
Reflect.set(node, 'neInfoList', neTypeMap.get(nodeID));
|
||||
|
||||
Reflect.set(node, 'neInfo', neTypeMap.get(nodeID)[0] || {});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// 边过滤
|
||||
const ef: Record<string, any>[] = edges.filter(
|
||||
(edge: Record<string, any>) => {
|
||||
const edgeSource: string = edge.source;
|
||||
const edgeTarget: string = edge.target;
|
||||
const hasNeS = nf.some(n => n.id === edgeSource);
|
||||
const hasNeT = nf.some(n => n.id === edgeTarget);
|
||||
if (hasNeS && hasNeT) {
|
||||
return true;
|
||||
}
|
||||
if (hasNeS && notNeNodes.includes(edgeTarget)) {
|
||||
return true;
|
||||
}
|
||||
if (hasNeT && notNeNodes.includes(edgeSource)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
// 分组过滤
|
||||
combos.forEach((combo: Record<string, any>) => {
|
||||
const comboChildren: Record<string, any>[] = combo.children;
|
||||
combo.children = comboChildren.filter(c => nf.some(n => n.id === c.id));
|
||||
return combo;
|
||||
});
|
||||
|
||||
// 图数据
|
||||
graphState.data = { combos, edges: ef, nodes: nf };
|
||||
})
|
||||
.finally(() => {
|
||||
if (graphState.data.length < 0) return;
|
||||
// 重载数据
|
||||
if (reload) {
|
||||
graphG6.value.read(graphState.data);
|
||||
} else {
|
||||
handleRanderGraph(graphG6Dom.value, graphState.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fnGraphDataLoad(false);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="graphG6Dom" class="chart"></div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,291 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { listKPIData } from '@/api/perfManage/goldTarget';
|
||||
import * as echarts from 'echarts/core';
|
||||
import {
|
||||
TooltipComponent,
|
||||
TooltipComponentOption,
|
||||
GridComponent,
|
||||
GridComponentOption,
|
||||
LegendComponent,
|
||||
LegendComponentOption,
|
||||
} from 'echarts/components';
|
||||
import { LineChart, LineSeriesOption } from 'echarts/charts';
|
||||
import { UniversalTransition } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { markRaw } from 'vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { upfFlowData, upfFlowParse } from '../../hooks/useUPFTotalFlow';
|
||||
import { upfWhoId } from '../../hooks/useWS';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
echarts.use([
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
LegendComponent,
|
||||
LineChart,
|
||||
CanvasRenderer,
|
||||
UniversalTransition,
|
||||
]);
|
||||
|
||||
type EChartsOption = echarts.ComposeOption<
|
||||
| TooltipComponentOption
|
||||
| GridComponentOption
|
||||
| LegendComponentOption
|
||||
| LineSeriesOption
|
||||
>;
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const upfFlow = ref<HTMLElement | undefined>(undefined);
|
||||
|
||||
/**图实例对象 */
|
||||
const upfFlowChart = ref<any>(null);
|
||||
|
||||
function fnDesign(container: HTMLElement | undefined, option: EChartsOption) {
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
if (!upfFlowChart.value) {
|
||||
upfFlowChart.value = markRaw(echarts.init(container, 'light'));
|
||||
}
|
||||
|
||||
option && upfFlowChart.value.setOption(option);
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (upfFlowChart.value) {
|
||||
upfFlowChart.value.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
//渲染速率图
|
||||
function handleRanderChart() {
|
||||
const { lineXTime, lineYUp, lineYDown } = upfFlowData.value;
|
||||
|
||||
var yAxisSeries: any = [
|
||||
{
|
||||
name: t('views.dashboard.overview.upfFlow.up'),
|
||||
type: 'line',
|
||||
color: 'rgba(250, 219, 20)',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(250, 219, 20, .5)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(250, 219, 20, 0.5)',
|
||||
},
|
||||
]),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10,
|
||||
},
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
formatter: '{b}',
|
||||
data: lineYUp,
|
||||
},
|
||||
{
|
||||
name: t('views.dashboard.overview.upfFlow.down'),
|
||||
type: 'line',
|
||||
color: 'rgba(92, 123, 217)',
|
||||
smooth: true,
|
||||
areaStyle: {
|
||||
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(92, 123, 217, .5)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(92, 123, 217, 0.5)',
|
||||
},
|
||||
]),
|
||||
shadowColor: 'rgba(0, 0, 0, 0.1)',
|
||||
shadowBlur: 10,
|
||||
},
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
formatter: '{b}',
|
||||
data: lineYDown,
|
||||
},
|
||||
];
|
||||
|
||||
const optionData: EChartsOption = {
|
||||
tooltip: {
|
||||
show: true, //是否显示提示框组件
|
||||
trigger: 'axis',
|
||||
//formatter:'{a0}:{c0}<br>{a1}:{c1}'
|
||||
formatter: function (param: any) {
|
||||
var tip = '';
|
||||
if (param !== null && param.length > 0) {
|
||||
tip += param[0].name + '<br />';
|
||||
for (var i = 0; i < param.length; i++) {
|
||||
tip +=
|
||||
param[i].marker +
|
||||
param[i].seriesName +
|
||||
': ' +
|
||||
param[i].value +
|
||||
'<br />';
|
||||
}
|
||||
}
|
||||
return tip;
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
data: yAxisSeries.map((s: any) => s.name),
|
||||
textStyle: {
|
||||
fontSize: 12,
|
||||
color: 'rgb(0,253,255,0.6)',
|
||||
},
|
||||
left: 'center', // 设置图例居中
|
||||
},
|
||||
grid: {
|
||||
top: '14%',
|
||||
left: '4%',
|
||||
right: '4%',
|
||||
bottom: '16%',
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: lineXTime,
|
||||
axisLabel: {
|
||||
formatter: function (params: any) {
|
||||
return params;
|
||||
},
|
||||
fontSize: 14,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: 'rgb(0,253,255,0.6)',
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
name: '(Mbps)',
|
||||
nameTextStyle: {
|
||||
fontSize: 12, // 设置文字距离x轴的距离
|
||||
padding: [0, -10, 0, 0], // 设置名称在x轴方向上的偏移
|
||||
},
|
||||
type: 'value',
|
||||
// splitNumber: 4,
|
||||
min: 0,
|
||||
//max: 300,
|
||||
axisLabel: {
|
||||
formatter: '{value}',
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: 'rgb(23,255,243,0.3)',
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: 'rgb(0,253,255,0.6)',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
series: yAxisSeries,
|
||||
};
|
||||
fnDesign(upfFlow.value, optionData);
|
||||
}
|
||||
|
||||
/**查询初始UPF数据 */
|
||||
function fnGetInitData() {
|
||||
// 查询5分钟前的
|
||||
const nowDate = new Date().getTime();
|
||||
|
||||
listKPIData({
|
||||
neType: 'UPF',
|
||||
neId: upfWhoId.value,
|
||||
startTime: nowDate - 5 * 60 * 1000,
|
||||
endTime: nowDate,
|
||||
|
||||
interval: 5, // 5秒
|
||||
sortField: 'timeGroup',
|
||||
sortOrder: 'asc',
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
|
||||
for (const item of res.data) {
|
||||
upfFlowParse(item);
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
handleRanderChart();
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => upfFlowData.value,
|
||||
v => {
|
||||
if (upfFlowChart.value == null) return;
|
||||
upfFlowChart.value.setOption({
|
||||
xAxis: {
|
||||
data: v.lineXTime,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: v.lineYUp,
|
||||
},
|
||||
{
|
||||
data: v.lineYDown,
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
fnGetInitData();
|
||||
|
||||
// setInterval(() => {
|
||||
// upfFlowData.value.lineXTime.push(parseDateToStr(new Date()));
|
||||
// const upN3 = parseSizeFromKbs(+145452, 5);
|
||||
// upfFlowData.value.lineYUp.push(upN3[0]);
|
||||
// const downN6 = parseSizeFromKbs(+232343, 5);
|
||||
// upfFlowData.value.lineYDown.push(downN6[0]);
|
||||
|
||||
// upfFlowChart.value.setOption({
|
||||
// xAxis: {
|
||||
// data: upfFlowData.value.lineXTime,
|
||||
// },
|
||||
// series: [
|
||||
// {
|
||||
// data: upfFlowData.value.lineYUp,
|
||||
// },
|
||||
// {
|
||||
// data: upfFlowData.value.lineYDown,
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// }, 5000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="upfFlow" class="chart-container"></div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.chart-container {
|
||||
/* 设置图表容器大小和位置 */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,324 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { parseDuration, parseDateToStr } from '@/utils/date-utils';
|
||||
import { eventData, eventId } from '../../hooks/useUserActivity';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { onMounted, reactive } from 'vue';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCode: DictType[];
|
||||
/**CDR 呼叫类型 */
|
||||
cdrCallType: DictType[];
|
||||
/**UE 事件认证代码类型 */
|
||||
ueAauthCode: DictType[];
|
||||
/**UE 事件类型 */
|
||||
ueEventType: DictType[];
|
||||
/**UE 事件CM状态 */
|
||||
ueEventCmState: DictType[];
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCodeCause: DictType[];
|
||||
} = reactive({
|
||||
cdrSipCode: [],
|
||||
cdrCallType: [],
|
||||
ueAauthCode: [],
|
||||
ueEventType: [],
|
||||
ueEventCmState: [],
|
||||
cdrSipCodeCause: [],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('cdr_sip_code'),
|
||||
getDict('cdr_call_type'),
|
||||
getDict('ue_auth_code'),
|
||||
getDict('ue_event_type'),
|
||||
getDict('ue_event_cm_state'),
|
||||
getDict('cdr_sip_code_cause'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrSipCode = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.cdrCallType = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.ueAauthCode = resArr[2].value;
|
||||
}
|
||||
if (resArr[3].status === 'fulfilled') {
|
||||
dict.ueEventType = resArr[3].value;
|
||||
}
|
||||
if (resArr[4].status === 'fulfilled') {
|
||||
dict.ueEventCmState = resArr[4].value;
|
||||
}
|
||||
if (resArr[5].status === 'fulfilled') {
|
||||
dict.cdrSipCodeCause = resArr[5].value;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="activty">
|
||||
<template v-for="item in eventData" :key="item.eId">
|
||||
<!-- UE事件AMF -->
|
||||
<div
|
||||
class="card-ue"
|
||||
:class="{ active: item.eId === eventId }"
|
||||
v-if="item.eType === 'amf_ue'"
|
||||
>
|
||||
<div class="card-ue-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||
<span>
|
||||
<DictTag :options="dict.ueEventType" :value="item.type" />
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||
<div>
|
||||
GNB ID: <span>{{ item.data.gNBID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
Cell ID: <span>{{ item.data.cellID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<template v-if="item.data?.time">
|
||||
{{ parseDateToStr(item.data.time) }}
|
||||
</template>
|
||||
<template v-else-if="item.data?.timestamp">
|
||||
{{ parseDateToStr(+item.data.timestamp * 1000) }}
|
||||
</template>
|
||||
<template v-else> - </template>
|
||||
</div>
|
||||
<div v-if="item.type === 'auth-result'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<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>
|
||||
<!-- 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></div>
|
||||
</div>
|
||||
|
||||
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||
<div>
|
||||
ENB ID: <span>{{ item.data.eNBID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
Cell ID: <span>{{ item.data.cellID }}</span>
|
||||
</div>
|
||||
<div>
|
||||
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<template v-if="item.data?.time">
|
||||
{{ parseDateToStr(item.data.time) }}
|
||||
</template>
|
||||
<template v-else-if="item.data?.timestamp">
|
||||
{{
|
||||
typeof item.data?.timestamp === 'number'
|
||||
? parseDateToStr(+item.data?.timestamp * 1000)
|
||||
: parseDateToStr(item.data?.timestamp)
|
||||
}}
|
||||
</template>
|
||||
<template v-else> - </template>
|
||||
</div>
|
||||
<div v-if="item.type === 'auth-result'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<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>
|
||||