11 Commits

Author SHA1 Message Date
TsMask
2b2a09ff38 Merge branch 'main-v2' into lichang 2025-07-01 10:15:22 +08:00
TsMask
a9b266d49f Merge branch 'main-v2' into lichang 2025-06-26 17:30:28 +08:00
TsMask
4690a874af Merge branch 'main-v2' into lichang 2025-06-16 19:41:00 +08:00
TsMask
859615fecc feat: 菜单根据切换核心网显示 2025-06-13 19:48:09 +08:00
TsMask
4167428c10 chore: 更新依赖项版本 2025-06-13 14:24:36 +08:00
TsMask
11137c03a3 feat: 选择核心网控件 2025-06-13 14:21:56 +08:00
TsMask
d623b7ea36 Merge branch 'main-v2' into lichang 2025-06-12 19:40:54 +08:00
TsMask
4e4b4bc2b7 feat: 顶部右侧内容移除冗余代码,封装为组件 2025-06-12 10:43:47 +08:00
TsMask
4d171b0d06 Merge branch 'main-v2' into lichang 2025-06-12 10:42:27 +08:00
TsMask
52573c7678 Merge branch 'main-v2' into lichang 2025-06-12 10:17:18 +08:00
TsMask
9bddfceda5 fix: 网元参数配置list类型值空白空格bug补充占位符 2025-06-09 18:25:48 +08:00
193 changed files with 3032 additions and 19704 deletions

View File

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

View File

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

View File

@@ -1,106 +1,5 @@
# 版本发布日志
## 2.2510.4-20251024
- 新增 同步语音仪表盘修改useNeInfoStore导入地址、补充接口导入、修改ims请求参数、修改响应码
- 新增 基站状态补充randId列
## 2.2510.3-20251018
- 新增 备份网元日志文件数据查看
- 修复 更新根网管节点处理逻辑支持无OMC情况
- 新增 网元配置选择传入neId查询相应版本配置数据
- 修复 网元配置备份修改表单校验规则,添加备注字段必填项
## 2.2510.2-20251011
- 新增 UDM-auth数据导出按钮权限定义
- 新增 SMSC-CDR添加结果原因说明
- 优化 移除CDR/UE/网元版本/授权列表显示ID列
- 修复 MML回车undefined问题关闭搜索匹配
## 2.2509.4-20250926
- 优化 UDM数据显示创建时间列
- 优化 中英文补充
- 新增 添加系统备份功能包括导入导出OMC的API和界面支持
- 新增 日志备份文件查看kpi文件记录
- 优化 根据搜索条件导出,去除最大记录限制
- 新增 添加活动告警自动刷新功能并优化代码结构
- 修复 奇安信浏览器时区格式错误
- 新增 添加网元配置快速PLMN修改框
- 新增 ue和ne模块权限按钮
- 新增 监控模块按钮权限
- 修复 UDM数据导出提示信息导出总数
- 新增 网元许可快速上传功能
- 新增 网元信息快速OAM功能
## 2.2508.4-20250829
- 新增 网元授权显示用户数/基站数
- 优化 网元当前版本显示是否有包存在
- 优化 cdr/ue导出操作后锁定按钮事件防重发
- 优化 网元可选静态可选项添加PGWC
- 优化 参数配置添加校验过滤长度~和-定义范围
- 修复 UPF流量统计数据同时获取刷新
- 修复 UPF流量统计字节数据格式化显示不一致问题
- 新增 指标Title管理页面功能
- 新增 增加多个导出操作的超时时间至180秒
## 2.2508.2-20250815
- 优化 给config.js加随机数避免缓存
- 新增 第三方登录认证功能和管理页,第三方用户不可删除和修改密码
- 优化 系统操作日志详情json格式化显示
- 优化 网元信息更新禁止修改neType neId
- 修复 用户数累加问题修复,液体图中浮标显示错误修复
- 修复 基站数修复以及资源模块下拉框修复
- 修复 告警事件时间显示不是时间格式字符串
- 修复 透明柱状图自适应,数值标签定位
- 修复 告警数据参数调整
## 2.2508.1-20250808
- 修复 UDM鉴权更新ki长度提示错误无法发送请求
- 优化 MML命令为空判断不发送MML日志列表显示
- 优化 关闭网元reload操作只有amf smf upf udm四个网元支持改用mml发送
- 优化 网元概览隐藏容量字段显示CBC创建时间格式显示错误
- 优化 系统用户账号简单4位长度
- 修复 看板2用户数量显示不出修改广播帮助的数量提示
- 优化 性能栏目相关页面主动呼叫改正在通话,网元切换清除缓存数据
## 2.2507.4-20250801
- 新增 cbc界面
- 修复 数据处理修复,显示优化
- 修复 告警模块优化,底部边距,数据采集说明
## 2.2507.3-20250725
- 优化 将UDM鉴权导出按钮隐藏
- 修复 自定义指标数值格式处理,导出表格修复
- 修复 仪表盘2用户事件显示不正常
- 修复 自定义指标数值格式化保留3位小数
- 优化 参数配置AMF导入Index字段存在更新不存在默认新增
## 2.2507.2-20250718
- 优化 变更nssf/n3iwf接口调用
- 优化 调整告警类型参数值,参数配置列表项不记录勾选状态
- 优化 调整UE数据返回参数
- 修复 基站状态拓扑图显示失败
- 修复 基站状态记录和smscCDR导出改为get行为
- 新增 mt版本移动过来的的告警/仪表/性能大屏页面
- 移除 删除无用文件
- 修复 指标页面数据获取异常接口调整
## 2.2507.1-20250705
- 修复 缓存管理列表key查询URL路径错误
- 优化 SMF-Date显示图表日期显示不完整改日期格式
- 修复 跟踪任务编号的传入获取信息不一致
- 修复 参数配置可见visible属性判断
## 2.2506.4-20250627
- 修复 UDM-IMS数据批量新增/批量删除命令调整

View File

@@ -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>

View File

@@ -14,9 +14,9 @@
"dependencies": {
"@ant-design/icons-vue": "7.0.1",
"@antv/g6": "4.8.25",
"@codemirror/lang-javascript": "6.2.3",
"@codemirror/lang-javascript": "6.2.4",
"@codemirror/lang-yaml": "6.1.2",
"@codemirror/merge": "6.10.0",
"@codemirror/merge": "6.10.2",
"@codemirror/theme-one-dark": "6.1.2",
"@tato30/vue-pdf": "1.11.3",
"@vueuse/core": "13.0.0",
@@ -24,23 +24,21 @@
"@xterm/xterm": "5.5.0",
"ant-design-vue": "4.2.6",
"antdv-pro-layout": "4.2.0",
"antdv-pro-modal": "4.0.8",
"antdv-pro-modal": "4.0.9",
"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",
"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",
"pinia": "3.0.3",
"vue": "3.5.16",
"vue-i18n": "11.1.5",
"vue-router": "4.5.0",
"vue3-smooth-dnd": "0.0.6",
"xlsx": "0.18.5"
@@ -51,12 +49,12 @@
"@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",
"@vitejs/plugin-vue": "5.2.4",
"less": "4.3.0",
"typescript": "5.8.2",
"unplugin-vue-components": "0.28.0",
"vite": "6.3.3",
"vite": "6.3.5",
"vite-plugin-compression": "0.5.1",
"vue-tsc": "2.2.8"
"vue-tsc": "2.2.10"
}
}

View File

@@ -15,7 +15,7 @@
const protocol = window.location.protocol
let wsprotocol = "ws:"
const hostname = window.location.hostname
let host = `${hostname}:33080`;
let host = `${hostname}:33030`;
if (protocol === 'https:') {
host = `${hostname}:33443`;
wsprotocol = "wss:"
@@ -25,4 +25,4 @@
sessionStorage.setItem('baseUrl', `${protocol}//${host}`);
// websocket Address
sessionStorage.setItem('wsUrl', `${wsprotocol}//${host}`);
})();
})();

View File

@@ -1,2 +1,2 @@
imsi,msisdn,sess_rules,pcc_rules,hdr_enrich,rfsp,sar,qos_audio,qos_video
001012082101039,1234,internet|ims_sig,internet|ims_sig,321321,255,321312,32131,32131
#imsi,msisdn,sess_rules,pcc_rules,hdr_enrich,rfsp,sar,qos_audio,qos_video,online,offline
460996650000580,62357000580,internet|ims_sig,internet|ims_sig,dnn,1,def_sar,qos_audio,qos_video,0,0

View File

@@ -1,2 +1,2 @@
001011100001157,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111
001011100001158,1234567890ABCDEF1234567890ABCDEF,0,8000,11111111111111111111111111111111
460996650000580,1234567890ABCDEF1234567890ABCDEF,0,8000
460996650000581,1234567890ABCDEF1234567890ABCDEF,0,8000

View File

@@ -1,2 +1,2 @@
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,-
460996650000580,62357000580,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&content&ims,1,64,24,65,def_eps,1,010200000000,-
460996650000581,62357000581,def_ambr,def_nssai,def_arfb,def_sar,0,3,def_snssai,1-000001&content&ims,1,64,24,65,def_eps,1,010200000000,-

View File

@@ -1,5 +1,4 @@
import { request } from '@/plugins/http-fetch';
import { sessionGet } from '@/utils/cache-session-utils';
/**
* 登录方法
@@ -88,69 +87,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,
});
}

View File

@@ -1,75 +0,0 @@
import { request } from '@/plugins/http-fetch';
/**
* CBC列表
* @param query 查询参数
* @returns object
*/
export function listCBC(query: Record<string, any>) {
return request({
url: `/neData/cbc/message/list`,
method: 'GET',
params: query,
timeout: 30_000,
});
}
/**
* CBC签约用户新增
* @param data 签约对象
* @returns object
*/
export function addCBC(data: Record<string, any>) {
return request({
url: `/neData/cbc/message?neId=${data.neId}`,
method: 'POST',
data: data,
timeout: 180_000,
});
}
export function updateCBCStatus(data:any) {
return request({
url: `/neData/cbc/message/${data.id}/${data.status}?neId=${data.neId}`,
method: 'PUT',
timeout: 180_000,
});
}
export function updateCBC(data:any) {
return request({
url: `/neData/cbc/message/${data.id}?neId=${data.neId}`,
method: 'PUT',
data,
timeout: 180_000,
});
}
/**
* CBC删除
* @param data 签约对象
* @returns object
*/
export function delCBC(neId: string, id: string) {
return request({
url: `/neData/cbc/message/${id}?neId=${neId}`,
method: 'DELETE',
timeout: 180_000,
});
}

View File

@@ -0,0 +1,35 @@
import {
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
RESULT_MSG_ERROR,
} from '@/constants/result-constants';
import { language, request } from '@/plugins/http-fetch';
/**
* 更新网元配置重新载入
* @param neType 网元类型
* @param neId 网元ID
* @returns
*/
export async function updateNeConfigReload(neType: string, neId: string) {
// 发起请求
const result = await request({
url: `/api/rest/operationManagement/v1/elementType/${neType}/objectType/mml?ne_id=${neId}`,
method: 'POST',
data: { mml: ['reload'] },
timeout: 180_000,
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
const v = result.data.data[0];
const str = v.toLowerCase();
if (str.indexOf('ok') !== -1) {
delete result.data;
} else if (str.indexOf('success') !== -1) {
delete result.data;
} else {
return { code: RESULT_CODE_ERROR, msg: RESULT_MSG_ERROR[language] };
}
}
return result;
}

86
src/api/core/coreInfo.ts Normal file
View File

@@ -0,0 +1,86 @@
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
import { sessionGet } from '@/utils/cache-session-utils';
import { request } from '@/plugins/http-fetch';
/**
* 查询核心网列表
* @param query 查询参数
* @returns object
*/
export function listCoreInfo(query: Record<string, any>) {
return request({
url: '/core/info/list',
method: 'GET',
params: query,
timeout: 60_000,
});
}
/**
* 查询核心网列表全部无分页
* @param query 查询参数
* @returns object
*/
export function listAllCoreInfo(query: Record<string, any>) {
return request({
url: '/core/info/list/all',
method: 'GET',
params: query,
timeout: 60_000,
});
}
/**
* 查询核心网信息详细
* @param id 信息ID
* @returns object
*/
export function getCoreInfo(id: string | number) {
return request({
url: `/core/info/${id}`,
method: 'GET',
});
}
/**
* 核心网信息新增
* @param data 核心网对象
* @returns object
*/
export function addCoreInfo(data: Record<string, any>) {
return request({
url: `/core/info`,
method: 'POST',
data: data,
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
timeout: 30_000,
});
}
/**
* 核心网信息修改
* @param data 核心网对象
* @returns object
*/
export function updateCoreInfo(data: Record<string, any>) {
return request({
url: `/core/info`,
method: 'PUT',
data: data,
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
timeout: 30_000,
});
}
/**
* 核心网信息删除
* @param id 信息ID
* @returns object
*/
export function delCoreInfo(id: string | number) {
return request({
url: `/core/info/${id}`,
method: 'DELETE',
timeout: 60_000,
});
}

View File

@@ -12,7 +12,7 @@ export async function getActiveAlarmTotal() {
url: `/neData/alarm/list`,
method: 'GET',
params: {
alarmStatus: 'Active',
alarmStatus: '1',
sortField: 'event_time',
sortOrder: 'desc',
pageNum: 1,
@@ -127,7 +127,7 @@ export function exportAlarm(params: Record<string, any>) {
method: 'GET',
params: params,
responseType: 'blob',
timeout: 180_000,
timeout: 60_000,
});
}
@@ -194,13 +194,33 @@ export async function exportAll(query: Record<string, any>) {
* @returns bolb
*/
export async function origGet() {
return await request({
url: `/neData/alarm/count/severity`,
let totalSQL = `select count(*) as value,orig_severity as name from alarm WHERE alarm_status='1' and orig_severity!='Event' group by orig_severity`;
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
method: 'GET',
params: {
alarmStatus: 'Active',
SQL: totalSQL,
},
timeout: 30_000,
});
////
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
const itemData = result.data.data;
if (Array.isArray(itemData)) {
const v = itemData[0]['alarm'];
if (Array.isArray(v)) {
result.data = v;
}
if (v === null) {
result.data = [];
}
}
}
return result;
}
/**
@@ -208,13 +228,34 @@ export async function origGet() {
* @param filterFlag 查询参数
* @returns object
*/
export async function top3Sel() {
return await request({
url: `/neData/alarm/count/ne`,
export async function top3Sel(filterFlag?: string) {
let filter = ` WHERE alarm_status='1'and orig_severity='${filterFlag}'`;
if (!filterFlag) filter = "WHERE alarm_status='1'";
let top3SQL = `select count(*) as value,ne_type as name from alarm ${filter} and orig_severity!='Event' group by ne_type ORDER BY value desc limit 0,3 `;
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/select/omc_db/alarm`,
method: 'GET',
params: {
alarmStatus: 'Active',
top: 3,
SQL: top3SQL,
},
timeout: 30_000,
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
const itemData = result.data.data;
if (Array.isArray(itemData)) {
const v = itemData[0]['alarm'];
if (Array.isArray(v)) {
result.data = v;
}
if (v === null) {
result.data = [];
}
}
}
return result;
}

View File

@@ -0,0 +1,55 @@
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
import { sessionGet } from '@/utils/cache-session-utils';
import { request } from '@/plugins/http-fetch';
/**
* 获取下拉框数据
* @returns object
*/
export function getBakFile() {
return request({
url: '/lm/table/list',
method: 'GET',
});
}
/**
* 获取对应类型的文件列表
* @param query 查询参数
* @returns object
*/
export function getBakFileList(query: Record<string, any>) {
return request({
url: '/lm/file/list',
method: 'GET',
params: query,
});
}
/**
* 下载远端文件
* @param query 查询参数
* @returns object
*/
export function downFile(query: Record<string, any>) {
return request({
url: `/lm/file/${query.fileName}`,
method: 'GET',
params: query,
responseType: 'blob',
timeout: 180_000,
});
}
/**
* 删除远端获取文件
* @param query 查询参数
* @returns object
*/
export function delFile(query: Record<string, any>) {
return request({
url: `/lm/file/${query.fileName}`,
method: 'DELETE',
params: query,
});
}

68
src/api/logManage/mml.ts Normal file
View File

@@ -0,0 +1,68 @@
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 listMML(query: Record<string, any>) {
let totalSQL = 'select count(*) as total from mml_log where 1=1 ';
let rowsSQL = 'select * from mml_log where 1=1 ';
// 查询
let querySQL = '';
if (query.accountName) {
querySQL += ` and user like '%${query.accountName}%' `;
}
if (query.beginTime) {
querySQL += ` and log_time >= '${query.beginTime}' `;
}
if (query.endTime) {
querySQL += ` and log_time <= '${query.endTime}' `;
}
// 排序
let sortSql = ' order by log_time ';
if (query.sortOrder === 'asc') {
sortSql += ' asc ';
} else {
sortSql += ' desc ';
}
// 分页
const pageNum = (query.pageNum - 1) * query.pageSize;
const limtSql = ` limit ${pageNum},${query.pageSize} `;
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/select/omc_db/mml_log`,
method: 'GET',
params: {
totalSQL: totalSQL + querySQL,
rowsSQL: rowsSQL + querySQL + sortSql + limtSql,
},
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
const data = {
data: { total: 0, rows: [] as any },
code: result.code,
msg: result.msg,
};
result.data.data.forEach((item: any) => {
const itemData = item['mml_log'];
if (Array.isArray(itemData)) {
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
data.data.total = itemData[0]['total'];
} else {
data.data.rows = itemData.map(v => parseObjLineToHump(v));
}
}
});
return data;
}
return result;
}

View 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 listSecurityLog(query: Record<string, any>) {
let totalSQL = 'select count(*) as total from security_log where 1=1 ';
let rowsSQL = 'select * from security_log where 1=1 ';
// 查询
let querySQL = '';
if (query.accountName) {
querySQL += ` and account_name like '%${query.accountName}%' `;
}
if (query.opType) {
querySQL += ` and op_type = '${query.opType}' `;
}
if (query.beginTime) {
querySQL += ` and op_time >= '${query.beginTime}' `;
}
if (query.endTime) {
querySQL += ` and op_time <= '${query.endTime}' `;
}
// 排序
let sortSql = ' order by op_time ';
if (query.sortOrder === 'asc') {
sortSql += ' asc ';
} else {
sortSql += ' desc ';
}
// 分页
const pageNum = (query.pageNum - 1) * query.pageSize;
const limtSql = ` limit ${pageNum},${query.pageSize} `;
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/select/omc_db/security_log`,
method: 'GET',
params: {
totalSQL: totalSQL + querySQL,
rowsSQL: rowsSQL + querySQL + sortSql + limtSql,
},
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS) {
const data = {
data: { total: 0, rows: [] as any },
code: result.code,
msg: result.msg,
};
result.data.data.forEach((item: any) => {
const itemData = item['security_log'];
if (Array.isArray(itemData)) {
if (itemData.length === 1 && itemData[0]['total'] >= 0) {
data.data.total = itemData[0]['total'];
} else {
data.data.rows = itemData.map(v => parseObjLineToHump(v));
}
}
});
return data;
}
return result;
}

View File

@@ -1,4 +1,6 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
/**
* 查询网元可用cmd命令
@@ -6,17 +8,22 @@ import { request } from '@/plugins/http-fetch';
* @returns object
*/
export async function getMMLByNE(neType: string) {
return request({
url: '/tool/mml/system/list',
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/mml_system`,
method: 'GET',
params: {
neType: neType,
status: 'Active',
pageNum: 1,
pageSize: 1000,
SQL: `select * from mml_system where ne_type = '${neType}' and status = 'Active'`,
},
timeout: 60_000,
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
let data = result.data.data[0];
return Object.assign(result, {
data: parseObjLineToHump(data['mml_system']),
});
}
return result;
}
/**
@@ -33,15 +40,16 @@ export async function sendMMlByNE(
objectType: string,
cmdArr: string[]
) {
return request({
url: '/tool/mml/command',
// 发起请求
const result = await request({
url: `/api/rest/operationManagement/v1/elementType/${neType}/objectType/${objectType}?ne_id=${neId}`,
method: 'POST',
data: {
neType: neType,
neId: neId,
type: objectType,
command: cmdArr,
},
data: { mml: cmdArr },
timeout: 180_000,
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
result.data = result.data.data;
}
return result;
}

View File

@@ -1,21 +1,28 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
/**
* 查询UDM可用cmd命令
* @returns object
*/
export async function getMMLByUDM() {
return request({
url: '/tool/mml/subscriber/list',
// 发起请求
const result = await request({
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/mml_subscriber`,
method: 'GET',
params: {
neType: 'UDM',
status: 'Active',
pageNum: 1,
pageSize: 1000,
SQL: `select * from mml_subscriber where ne_type = 'UDM' and status = 'Active'`,
},
timeout: 60_000,
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
let data = result.data.data[0];
return Object.assign(result, {
data: parseObjLineToHump(data['mml_subscriber']),
});
}
return result;
}
/**
@@ -25,15 +32,16 @@ export async function getMMLByUDM() {
* @returns
*/
export async function sendMMlByUDM(neId: string, cmdArr: string[]) {
return request({
url: '/tool/mml/command',
// 发起请求
const result = await request({
url: `/api/rest/operationManagement/v1/elementType/UDM/objectType/mml?ne_id=${neId}`,
method: 'POST',
data: {
neType: 'UDM',
neId: neId,
type: 'General',
command: cmdArr,
},
data: { mml: cmdArr },
timeout: 180_000,
});
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
result.data = result.data.data;
}
return result;
}

View File

@@ -29,7 +29,7 @@ export function listCacheName() {
*/
export function listCacheKey(cacheName: string) {
return request({
url: `/monitor/cache/keys`,
url: `/monitor/cache//keys`,
method: 'GET',
params: { cacheName },
});

View File

@@ -6,11 +6,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,
});
}

View File

@@ -73,12 +73,12 @@ export function delNeInfo(infoIds: string | number) {
/**
* 查询网元列表全部无分页
* @param query 查询参数 neType neId bandStatus bandHost
* @param query 查询参数 coreUid, neUid bandStatus bandHost
* @returns object
*/
export function listAllNeInfo(query: Record<string, any>) {
return request({
url: '/ne/info/listAll',
url: '/ne/info/list/all',
method: 'GET',
params: query,
timeout: 60_000,
@@ -87,57 +87,57 @@ export function listAllNeInfo(query: Record<string, any>) {
/**
* 查询网元状态
* @param neType 网元类型
* @param neId 网元ID
* @param coreUid 核心网ID
* @param neUid 网元ID
* @returns object
*/
export function stateNeInfo(neType: string, neId: string) {
export function stateNeInfo(coreUid: string, neUid: string) {
return request({
url: '/ne/info/state',
method: 'GET',
params: { neType, neId },
params: { coreUid, neUid },
});
}
/**
* 查询网元信息
* @param neType 网元类型
* @param neId 网元ID
* @param coreUid 核心网ID
* @param neUid 网元ID
* @returns object
*/
export function getNeInfoByTypeAndID(neType: string, neId: string) {
export function getNeInfoByTypeAndID(coreUid: string, neUid: string) {
return request({
url: '/ne/info/byTypeAndID',
url: '/ne/info/nf',
method: 'GET',
params: { neType, neId },
params: { coreUid, neUid },
});
}
/**
* 网元端OAM配置文件读取
* @param neType 网元类型
* @param neId 网元ID
* @param coreUid 核心网ID
* @param neUid 网元ID
* @returns object
*/
export function getOAMFile(neType: string, neId: string) {
export function getOAMFile(coreUid: string, neUid: string) {
return request({
url: '/ne/info/oamFile',
url: '/ne/info/file/oam',
method: 'GET',
params: { neType, neId },
params: { coreUid, neUid },
});
}
/**
* 网元端配置文件写入
* @param neType 网元类型
* @param neId 网元ID
* @param coreUid 核心网ID
* @param neUid 网元ID
* @param content 用json对象
* @param sync 同步到网元
* @returns object
*/
export function saveOAMFile(data: Record<string, any>) {
return request({
url: `/ne/info/oamFile`,
url: `/ne/info/file/oam`,
method: 'PUT',
data: data,
timeout: 60_000,
@@ -150,7 +150,7 @@ export function saveOAMFile(data: Record<string, any>) {
*/
export function getPara5GFilee() {
return request({
url: '/ne/info/para5GFile',
url: '/ne/info/file/para5g',
method: 'GET',
});
}
@@ -158,12 +158,12 @@ export function getPara5GFilee() {
/**
* 网元端公共配置文件写入
* @param content txt内容为字符串 其他文件格式都用json对象
* @param syncNe 同步到网元端 NeType@ NeId
* @param syncNe 同步到网元端 coreUid@ neUid
* @returns object
*/
export function savePara5GFile(data: Record<string, any>) {
return request({
url: `/ne/info/para5GFile`,
url: `/ne/info/file/para5g`,
method: 'PUT',
data: data,
timeout: 60_000,
@@ -172,7 +172,7 @@ export function savePara5GFile(data: Record<string, any>) {
/**
* 网元服务操作
* @param data 对象 {neType,neId,action}
* @param data 对象 {coreUid, neUid, action}
* @returns object
*/
export function serviceNeAction(data: Record<string, any>) {

View File

@@ -29,16 +29,16 @@ export function delAMFDataUE(ueIds: string | number) {
/**
* AMF-UE会话列表导出
* @param query 查询列表条件
* @param data 查询列表条件
* @returns object
*/
export function exportAMFDataUE(query: Record<string, any>) {
export function exportAMFDataUE(data: Record<string, any>) {
return request({
url: '/neData/amf/ue/export',
method: 'GET',
params: query,
method: 'POST',
data,
responseType: 'blob',
timeout: 180_000,
timeout: 60_000,
});
}

View File

@@ -36,33 +36,3 @@ export function updateBackupFTP(data: Record<string, any>) {
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,
});
}

View File

@@ -29,16 +29,16 @@ export function delIMSDataCDR(cdrIds: string | number) {
/**
* IMS-CDR会话列表导出
* @param query 查询列表条件
* @param data 查询列表条件
* @returns object
*/
export function exportIMSDataCDR(query: Record<string, any>) {
export function exportIMSDataCDR(data: Record<string, any>) {
return request({
url: '/neData/ims/cdr/export',
method: 'GET',
params: query,
method: 'POST',
data,
responseType: 'blob',
timeout: 180_000,
timeout: 60_000,
});
}

View File

@@ -29,16 +29,16 @@ export function delMMEDataUE(ueIds: string | number) {
/**
* MME-UE会话列表导出
* @param query 查询列表条件
* @param data 查询列表条件
* @returns object
*/
export function exportMMEDataUE(query: Record<string, any>) {
export function exportMMEDataUE(data: Record<string, any>) {
return request({
url: '/neData/mme/ue/export',
method: 'GET',
params: query,
method: 'POST',
data,
responseType: 'blob',
timeout: 180_000,
timeout: 60_000,
});
}

View File

@@ -16,15 +16,15 @@ export function listNBState(query: Record<string, any>) {
/**
* 历史记录列表导出
* @param query 查询列表条件
* @param data 查询列表条件
* @returns object
*/
export function exportNBState(query: Record<string, any>) {
export function exportNBState(data: Record<string, any>) {
return request({
url: '/neData/nb-state/export',
method: 'GET',
params: query,
method: 'POST',
data,
responseType: 'blob',
timeout: 180_000,
timeout: 60_000,
});
}

View File

@@ -64,7 +64,7 @@ export function exportPCFRule(data: Record<string, any>) {
method: 'GET',
params: data,
responseType: 'blob',
timeout: 180_000,
timeout: 60_000,
});
}

View File

@@ -29,15 +29,15 @@ export function delSGWCDataCDR(cdrIds: string | number) {
/**
* SGWC-CDR会话列表导出
* @param query 查询列表条件
* @param data 查询列表条件
* @returns object
*/
export function exportSGWCDataCDR(query: Record<string, any>) {
export function exportSGWCDataCDR(data: Record<string, any>) {
return request({
url: '/neData/sgwc/cdr/export',
method: 'GET',
params: query,
method: 'POST',
data,
responseType: 'blob',
timeout: 180_000,
timeout: 60_000,
});
}

View File

@@ -29,16 +29,16 @@ export function delSMFDataCDR(cdrIds: string | number) {
/**
* SMF-CDR会话列表导出
* @param query 查询列表条件
* @param data 查询列表条件
* @returns object
*/
export function exportSMFDataCDR(query: Record<string, any>) {
export function exportSMFDataCDR(data: Record<string, any>) {
return request({
url: '/neData/smf/cdr/export',
method: 'GET',
params: query,
method: 'POST',
data,
responseType: 'blob',
timeout: 180_000,
timeout: 60_000,
});
}

View File

@@ -29,15 +29,15 @@ export function delSMSCDataCDR(cdrIds: string | number) {
/**
* SMSC-CDR会话列表导出
* @param query 查询列表条件
* @param data 查询列表条件
* @returns object
*/
export function exportSMSCDataCDR(query: Record<string, any>) {
export function exportSMSCDataCDR(data: Record<string, any>) {
return request({
url: '/neData/smsc/cdr/export',
method: 'GET',
params: query,
method: 'POST',
data,
responseType: 'blob',
timeout: 180_000,
timeout: 60_000,
});
}

View File

@@ -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,
});
}

View File

@@ -3,14 +3,36 @@ import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
/**
* N3IWF-在线订阅用户列表信息
* @param query 查询参数 {imsi}
* 查询列表
* @param query 查询参数
* @returns object
*/
export function listN3IWFSubList(query: Record<string, any>) {
return request({
url: '/neData/n3iwf/sub/list',
export async function listN3iwf(query: Record<string, any>) {
const result = await request({
url: '/api/rest/ueManagement/v1/elementType/n3iwf/objectType/ueInfo',
method: 'GET',
params: query,
});
const data = {
data: { total: 0, rows: [] as any },
code: result.code,
msg: result.msg,
};
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
const rows = parseObjLineToHump(result.data.data);
data.data.total = rows.length;
data.data.rows = rows;
}
// 模拟数据
// data.rows = [
// {
// "activeTime": "2023-11-29 06:35:43",
// "imsi": "460302072701181",
// "nai": "0460302072701181@nai.epc.mnc030.mcc460.3gppnetwork.org",
// "regState": 1
// }
// ]
return data;
}

View File

@@ -3,24 +3,24 @@ import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
/**
* NSSF-在线订阅用户列表信息
* @param query 查询参数 {imsi}
* 查询列表
* @returns object
*/
export function listNSSFSubList() {
return request({
url: '/neData/nssf/sub/list',
method: 'GET',
});
}
/**
* NSSF-可用AMF列表信息
* @returns object
*/
export function listNSSFAmfList() {
return request({
url: '/neData/nssf/amf/list',
export async function listNSSF() {
const result = await request({
url: '/api/rest/ueManagement/v1/elementType/nssf/objectType/subscriptions',
method: 'GET',
});
let data = {
data: { total: 0, rows: [] as any },
code: result.code,
msg: result.msg,
};
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
const rows = parseObjLineToHump(result.data.data);
data.data.total = rows.length;
data.data.rows = rows;
}
return data;
}

43
src/api/neUser/nssfAmf.ts Normal file
View File

@@ -0,0 +1,43 @@
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { request } from '@/plugins/http-fetch';
import { parseObjLineToHump } from '@/utils/parse-utils';
/**
* 查询列表
* @returns object
*/
export async function listNSSFAMF() {
const result = await request({
url: '/api/rest/ueManagement/v1/elementType/nssf/objectType/availableAMFs',
method: 'GET',
});
const data = {
data: { total: 0, rows: [] as any },
code: result.code,
msg: result.msg,
};
// 解析数据
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
const rows = parseObjLineToHump(result.data.data);
data.data.total = rows.length;
data.data.rows = rows;
}
// let testData = {
// total:0,
// rows: [
// {
// nfId: '001',
// amfSetId: '001',
// },
// {
// nfId: '002',
// amfSetId: '[001,002]',
// },
// ],
// code: 1,
// msg:'',
// };
//return testData;
return data;
}

View File

@@ -8,7 +8,7 @@ import { request } from '@/plugins/http-fetch';
export async function listCustomData(query: Record<string, any>) {
// 发起请求
const result = await request({
url: `/neData/kpic/data`,
url: `/pm/kpiC/report`,
method: 'GET',
params: query,
timeout: 60_000,

View File

@@ -1,16 +1,30 @@
import { request } from '@/plugins/http-fetch';
/**
* 查询自定义指标
* 查询自定义指标
* @param query 查询参数
* @returns object
*/
export async function listCustom(query?: Record<string, any>) {
return await request({
url: `/neData/kpic/title/list`,
// 发起请求
const result = await request({
url: `/pm/kpiC/title/totalList`,
method: 'GET',
params: query,
});
return result;
}
/**
* 查询自定义指标详细
* @param id 网元ID
* @returns object
*/
export async function getCustom(id: string | number) {
return request({
url: `/pm/kpiC/title/${id}`,
method: 'GET',
});
}
/**
@@ -20,7 +34,7 @@ export async function listCustom(query?: Record<string, any>) {
*/
export function addCustom(data: Record<string, any>) {
return request({
url: `/neData/kpic/title`,
url: `/pm/kpiC/title`,
method: 'POST',
data: data,
});
@@ -33,7 +47,7 @@ export function addCustom(data: Record<string, any>) {
*/
export function updateCustom(data: Record<string, any>) {
return request({
url: `/neData/kpic/title`,
url: `/pm/kpiC/title/${data.id}`,
method: 'PUT',
data: data,
});
@@ -45,7 +59,7 @@ export function updateCustom(data: Record<string, any>) {
*/
export async function delCustom(data: Record<string, any>) {
return request({
url: `/neData/kpic/title?id=${data.id}`,
url: `/pm/kpiC/title/${data.id}`,
method: 'DELETE',
});
}

View File

@@ -33,28 +33,3 @@ export async function getKPITitle(neType: string) {
// 解析数据//
return result;
}
//忙时呼叫
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,
});
}
//CCT指标
export async function getCctHour(query: Record<string, any>) {
return request({
url: '/neData/ims/cdr/cct-hour',
method: 'GET',
params: query,
});
}

View File

@@ -1,51 +0,0 @@
import { request } from '@/plugins/http-fetch';
/**
* 查询统计指标
* @param query 查询参数
* @returns object
*/
export async function listKPITitle(query?: Record<string, any>) {
return await request({
url: `/neData/kpi/title/list`,
method: 'GET',
params: query,
});
}
/**
* 新增统计指标
* @param data 网元对象
* @returns object
*/
export function addKPITitle(data: Record<string, any>) {
return request({
url: `/neData/kpi/title`,
method: 'POST',
data: data,
});
}
/**
* 修改统计指标
* @param data 网元对象
* @returns object
*/
export function updateKPITitle(data: Record<string, any>) {
return request({
url: `/neData/kpi/title`,
method: 'PUT',
data: data,
});
}
/**
* 删除统计指标
* @returns object
*/
export async function delKPITitle(id: string) {
return request({
url: `/neData/kpi/title?id=${id}`,
method: 'DELETE',
});
}

View File

@@ -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',
});
}

View File

@@ -1,25 +0,0 @@
import { request } from '@/plugins/http-fetch';
// 更新网元配置重新载入
export function updateNeConfigReload(neType: string, neId: string) {
return request({
url: '/tool/mml/command',
method: 'POST',
data: {
neType: neType,
neId: neId,
type: 'General',
command: ['reload'],
},
timeout: 180_000,
});
}
// MMML日志列表
export function mmlLogList(query: Record<string, any>) {
return request({
url: '/tool/mml/log/list',
method: 'GET',
params: query,
});
}

View File

@@ -4,6 +4,9 @@ export const CACHE_SESSION_CRYPTO_API = 'cache:session:cryptoApi';
/**会话缓存-网络请求 */
export const CACHE_SESSION_FATCH = 'cache:session:fatch';
/**会话缓存-当前选中核心网 */
export const CACHE_SESSION_CORE = 'cache:session:core';
/**本地缓存-布局设置 */
export const CACHE_LOCAL_PROCONFIG = 'cache:local:proConfig';

View File

@@ -22,12 +22,6 @@ export const NE_TYPE_LIST = [
'CHF',
'HLR',
'SGWC',
'PGWC',
'IP-SM-GW',
'MMTel-AS',
'I-CSCF',
'P-CSCF',
'S-CSCF',
];
/**

View File

@@ -261,16 +261,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,54 +357,6 @@ export default {
description: "No data yet, try refreshing",
},
},
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:'Number of Broadcasts',
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",
@@ -420,13 +368,10 @@ export default {
imsUeNum: "VoNR/VoLTE",
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',
},
upfFlow:{
title: "UPF Throughput",
@@ -456,7 +401,6 @@ export default {
},
userActivity: {
title: "User Activity",
imsTitle: "IMS Activity",
type: "Type",
duration: "Duration",
caller: "Caller",
@@ -485,7 +429,7 @@ export default {
resultOk: "Success",
resultFail: "Fail",
delTip: "Confirm deletion of the data item numbered [{msg}]?",
exportTip: "Do you confirm to export the current query conditions of the CDR data?",
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',
@@ -499,14 +443,6 @@ export default {
sgwcVolumeGPRSUplink: 'GPRS Uplink',
sgwcVolumeGPRSDownlink: 'GPRS Downlink',
},
chart:{
charttitle:'Data Usage Report',
uplink:'Uplink(Byte)',
downlink:'Downlink(Byte)',
datausage:'Data Usage',
totaluplink:'Total Uplink',
totaldownlink:'Total Downlink',
},
ue: {
eventType: "Event Type",
realTimeDataStart: "Turn on real-time data",
@@ -517,7 +453,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: {
@@ -539,8 +475,6 @@ export default {
port: 'Port',
portTip: "Network element port default:33030",
capability: 'Capability',
ueNumber: 'UE Number',
nbNumber: 'Radio Number',
serialNum: 'Serial Number',
expiryDate: 'Expiry Date',
normalcy: 'Normal',
@@ -590,22 +524,6 @@ export default {
kpiTimerPlease: 'Please enter the reporting period (in seconds)',
omcIP: 'OMC IP',
},
quickOam: {
title: 'Quick OAM Configuration',
selectNe: 'Select NE',
omcIP: 'OMC IP',
oamPort: 'OAM Port',
progress:'Progress',
processing:'Progressing',
result:'Result',
success:'Success',
default:'Default',
allsuccess:'All NE have been configured successfully!',
},
quickUpload: {
title: 'Quick License Upload',
selectNe: 'Select NE',
},
backConf: {
export: 'Config Export',
import: 'Config Import',
@@ -717,10 +635,6 @@ export default {
uploadFile: "Upload License",
uploadChangeOk: 'Network Element renewed license successfully and is being calibrated in the background!',
uploadChangeFail: "Some network elements failed to update the license, please check whether the service terminal environment is available!",
quickUpload: {
title: 'Quick License Upload',
selectNe: 'Select NE',
},
},
neConfig: {
treeTitle: "Navigation Configuration",
@@ -761,7 +675,7 @@ export default {
toIpPleace: "Please input the remote backup server IP address",
toPort: "Service Port",
username: "UserName",
usernamePleace: 'Please enter the service login username',
usernamePleace: 'Please enter the service login username',
password: "Password",
dir: "Save Dir",
dirPleace: 'Please enter the service address target file directory',
@@ -846,7 +760,7 @@ export default {
passwordPlease: "Please enter your password correctly",
addTitle: 'Add VOIP subscriber',
delTip: 'Confirm that you want to delete the information of VOIP user as [{num}]?',
exportTip: "Confirm exporting all VOIP user data based on the current search criteria?",
exportTip: "Confirm exporting xlsx table files based on search criteria?",
},
udmVolteIMS: {
startMSISDN: 'Starting MSISDN',
@@ -855,7 +769,7 @@ export default {
vniTip: 'Example: ims.mnc000.mcc000.3gppnetwork.org',
vniPlease: 'Please enter VNI correctly',
delTip: 'Are you sure you want to delete the information of IMS signing as [{num}]?',
exportTip: "Confirm exporting all VoLTE user data based on the current search criteria?",
exportTip: "Confirm exporting xlsx table files based on search criteria?",
},
pcfSub: {
exportTip: 'Confirm exporting all user policy control information data?',
@@ -875,7 +789,6 @@ export default {
list: "List",
topology: "Topology",
nbName: "RAN Node Name",
nbId: "RAN Node ID",
ueNum: "UE Number",
topologyTitle: "Radio State Graph",
name: "Name",
@@ -907,7 +820,7 @@ export default {
neTypePlease: 'Query network element Object',
neType: 'UDM Object',
export: 'Export',
exportConfirm: 'Confirm exporting all Authentication user data based on the current search criteria?',
exportConfirm: 'Are you sure to export all authentication user data?',
checkExport : 'Check Export',
checkExportConfirm: 'Confirm exporting the checked authenticated user data?',
import: 'Import',
@@ -936,7 +849,7 @@ export default {
subInfo:' Subscription Info',
neType: 'UDM Object',
export: 'Export',
exportConfirm: 'Confirm exporting all Subscribers user data based on the current search criteria?',
exportConfirm: 'Are you sure to export all signed user data?',
checkExport : 'Check Export',
checkExportConfirm: 'Are you sure to export the data of the checked subscribers?',
import: 'Import',
@@ -1140,21 +1053,6 @@ export default {
"chooseShowMetrics":"Select the metric you want to display",
"chooseMetrics":"Select an indicator",
},
voiceOverView:{
"voiceTitle":"Voice Calls Dashboard",
"tips":"Data and voice statistics per minute",
"ne":"NE",
"now":"now",
"last":"last",
"calls":"Calls",
"activeCall":"active calls",
"callMOMT":"calls MO/MT",
"failedcall":"failed calls",
"registration":"Registrations",
"activeregistration":"active registrations",
"registrationsuccess":"registration success",
"failedregistration":"failed registrations total",
},
},
traceManage: {
pcap: {
@@ -1362,19 +1260,10 @@ export default {
deleteTipErr: "Failed to delete file",
sysloginLog:'System Login Log',
sysOperateLog:'System Operation Log',
neLog:'NE Log',
cdrIMS:'CDR IMS Voice',
cdrSMF:'CDR SMF Data',
cdrSMSC:'CDR SMSC SMS',
cdrSGWC:'CDR SGWC Roaming Data',
kpiIMS:'KPI IMS',
kpiAMF:'KPI AMF',
kpiUDM:'KPI UDM',
kpiSMF:'KPI SMF',
kpiPCF:'KPI PCF',
kpiUPF:'KPI UPF',
kpiMME:'KPI MME',
kpiSMSC:'KPI SMSC',
cdrIMS:'CDR Voice',
cdrSMF:'CDR Data',
cdrSMSC:'CDR SMS',
cdrSGWC:'CDR Roaming Data',
}
},
monitor: {
@@ -1748,12 +1637,11 @@ export default {
userName: 'Nick Name',
permission: 'Role',
className: 'Department',
userType: 'User Type',
loginIp: 'Login Address',
loginTime: 'Login Time',
status: 'Status',
userNameTip:'Please enter the correct username format no less than 4 digits',
passwdTip:'Please enter the correct password format no less than 6 digits',
userNameTip:'The account number can only contain strings of uppercase letters, lowercase letters and numbers with a minimum length of 6 digits',
passwdTip:'Please enter the correct password format',
nickNameTip:'Nicknames no less than 2 digits',
emailTip:'Please enter the correct email address',
phoneTip:'Please enter the correct phone number',
@@ -1811,55 +1699,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',
@@ -1919,10 +1758,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',

View File

@@ -264,13 +264,9 @@ export default {
registerBtn: '注册账号',
loginBtn: '登录',
loginSuccess: '登录成功',
otherMethod: '其他方式',
backBtn: '返回',
backBtnLogin: '返回登录',
authorizedNotfound: '授权无效',
authorizedFailed: '授权失败',
authorizedSuccess: '授权成功',
redirectHome: '{i} 秒后跳转主页',
loginMethod: '其他登录方式',
loginMethodWX: '微信扫一扫登录',
loginMethodQQ: 'QQ扫码登录',
},
register: {
registerBtn: '注册',
@@ -361,54 +357,6 @@ export default {
description: "暂无数据,尝试刷新看看",
},
},
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: "核心网系统看板",
@@ -419,14 +367,11 @@ export default {
userTitle:'用户信息',
imsUeNum: "IMS 会话数",
smfUeNum: "Data 会话数",
gnbBase: "5G 基站在线数",
gnbSumBase: "5G 基站总数",
gnbBase: "5G 基站数",
gnbUeNum:'5G 用户数',
enbBase: "4G 基站在线数",
enbSumBase: "4G 基站总数",
enbBase: "4G 基站数",
enbUeNum:'4G 用户数',
baseTitle:'在线信息',
nodeBInfo: '基站信息',
},
upfFlow:{
title: "用户面吞吐量",
@@ -456,7 +401,6 @@ export default {
},
userActivity: {
title: "用户活动",
imsTitle: "IMS 活动",
type: "类型",
duration: "时长",
caller: "主叫",
@@ -485,7 +429,7 @@ export default {
resultOk: "成功",
resultFail: "失败",
delTip: "确认删除编号为【{msg}】的数据项?",
exportTip: "确认导出当前查询条件的话单数据吗?",
exportTip: "确认导出当前查询条件的话单数据吗?(导出最大支持一万条)",
chargingID: '计费ID',
smfSubscriptionIDData: '订阅 ID 数据',
smfSubscriptionIDType: '订阅 ID 类型',
@@ -499,14 +443,6 @@ export default {
sgwcVolumeGPRSUplink: 'GPRS 上行链路',
sgwcVolumeGPRSDownlink: 'GPRS 下行链路',
},
chart:{
charttitle:'数据使用报告',
uplink:'上行流量(B)',
downlink:'下行流量(B)',
datausage:'流量使用',
totaluplink:'总上行流量',
totaldownlink:'总下行流量',
},
ue: {
eventType: "事件类型",
realTimeDataStart: "开启实时数据",
@@ -517,7 +453,7 @@ export default {
result: "结果",
resultOk: "成功",
delTip: "确认删除编号为【{msg}】的数据项?",
exportTip: "确认导出当前查询条件的事件数据吗?",
exportTip: "确认导出当前查询条件的事件数据吗?(导出最大支持一万条)",
},
},
ne: {
@@ -539,8 +475,6 @@ export default {
port: '服务端口',
portTip: "网元服务端口,默认:33030",
capability: '容量',
ueNumber: '用户数',
nbNumber: '基站数',
serialNum: '序列号',
expiryDate: '许可证到期日期',
normalcy: '正常',
@@ -590,22 +524,6 @@ export default {
kpiTimerPlease: '请输入上报周期(单位秒)',
omcIP: 'OMC IP',
},
quickOam: {
title: '快速OAM配置',
selectNe: '选择网元',
omcIP: 'OMC IP地址',
oamPort: 'OAM端口',
progress:'配置进度',
processing:'正在处理',
result:'操作结果',
success:'成功',
default:'失败',
allsuccess:'所有网元配置成功!',
},
quickUpload: {
title: '快速许可证上传',
selectNe: '选择网元',
},
backConf: {
export: '配置导出',
import: '配置导入',
@@ -717,10 +635,6 @@ export default {
uploadFile: "上传许可证",
uploadChangeOk: '网元更新许可证成功,正在后台校验!',
uploadChangeFail: "部分网元更新许可证失败,请检查服务终端环境是否可用!",
quickUpload: {
title: '快速许可证上传',
selectNe: '选择网元',
},
},
neConfig: {
treeTitle: "配置导航",
@@ -761,7 +675,7 @@ export default {
toIpPleace: "请输入远程备份服务器 IP 地址",
toPort: "服务端口",
username: "登录用户名",
usernamePleace: '请输入服务登录用户名',
usernamePleace: '请输入服务登录用户名',
password: "登录密码",
dir: "保存目录",
dirPleace: '请输入服务地址目标文件目录',
@@ -846,7 +760,7 @@ export default {
passwordPlease: "请正确输入密码",
addTitle: '新增VOIP用户',
delTip: '确认要删除VOIP用户为【{num}】的信息吗?',
exportTip: "确认根据当前搜索条件导出全部VoIP用户数据吗",
exportTip: "确认根据搜索条件导出xlsx表格文件吗?",
},
udmVolteIMS: {
startMSISDN: '起始MSISDN',
@@ -855,7 +769,7 @@ export default {
vniTip: '示例ims.mnc000.mcc000.3gppnetwork.org',
vniPlease: '请正确输入VNI',
delTip: '确认要删除IMS签约为【{num}】的信息吗?',
exportTip: "确认根据当前搜索条件导出全部VoLTE用户数据吗",
exportTip: "确认根据搜索条件导出xlsx表格文件吗?",
},
pcfSub: {
exportTip: '确认导出全部用户策略控制信息数据吗?',
@@ -875,7 +789,6 @@ export default {
list: "列表",
topology: "拓扑图",
nbName: "设备名称",
nbId: "设备ID",
ueNum: "在线用户数",
topologyTitle: "基站状态关系图",
name: "基站名称",
@@ -907,7 +820,7 @@ export default {
neTypePlease: '查询网元类型',
neType: 'UDM网元对象',
export: '导出',
exportConfirm: '确认根据当前搜索条件导出全部鉴权用户数据吗?',
exportConfirm: '确认导出全部鉴权用户数据吗?',
checkExport : '勾选导出',
checkExportConfirm: '确认导出已勾选的鉴权用户数据吗?',
import: '导入',
@@ -938,7 +851,7 @@ export default {
export: '导出',
exportConfirm: '确认导出全部签约用户数据吗?',
checkExport : '勾选导出',
checkExportConfirm: '确认根据当前搜索条件导出全部签约用户数据吗?',
checkExportConfirm: '确认导出已勾选的签约用户数据吗?',
import: '导入',
importFail: '失败记录',
loadDataConfirm: '确认要重新加载数据吗?',
@@ -1140,21 +1053,6 @@ export default {
"chooseShowMetrics":"选择需要显示的指标",
"chooseMetrics":"选择指标",
},
voiceOverView:{
"voiceTitle":"语音通话仪表盘",
"tips":"每分钟数据语音统计",
"ne":"网元",
"now":"现在",
"last":"过去",
"calls":"呼叫",
"activeCall":"正在通话",
"callMOMT":"呼叫 主叫接通率/被叫接通率",
"failedcall":"失败呼叫",
"registration":"注册",
"activeregistration":"主动注册",
"registrationsuccess":"注册成功率",
"failedregistration":"失败注册",
},
},
traceManage: {
pcap: {
@@ -1362,19 +1260,10 @@ export default {
deleteTipErr: "文件删除失败",
sysloginLog:'系统登录日志',
sysOperateLog:'系统操作日志',
neLog:'网元日志',
cdrIMS:'话单 IMS 语音',
cdrSMF:'话单 SMF 数据',
cdrSMSC:'话单 SMSC 短信',
cdrSGWC:'话单 SGWC 漫游数据',
kpiIMS:'KPI IMS',
kpiAMF:'KPI AMF',
kpiUDM:'KPI UDM',
kpiSMF:'KPI SMF',
kpiPCF:'KPI PCF',
kpiUPF:'KPI UPF',
kpiMME:'KPI MME',
kpiSMSC:'KPI SMSC',
cdrIMS:'语音话单',
cdrSMF:'数据话单',
cdrSMSC:'短信话单',
cdrSGWC:'漫游数据话单',
}
},
monitor: {
@@ -1746,14 +1635,13 @@ export default {
userNum: '用户编号',
account: '登录账号',
userName: '用户昵称',
permission: '用户角色',
permission: '用户权限',
className: '部门名称',
userType: '用户类型',
loginIp: '登录地址',
loginTime: '登录时间',
status: '用户状态',
userNameTip:'请输入正确的用户名格式不少于4位',
passwdTip:'请输入正确的密码格式不少于6位',
userNameTip:'账号只能包含大写字母、小写字母和数字的字符串长度至少为6位',
passwdTip:'请输入正确的密码格式',
nickNameTip:'昵称不少于2位',
emailTip:'请输入正确的邮箱地址',
phoneTip:'请输入正确的手机号码',
@@ -1811,55 +1699,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: '提交保存',
@@ -1919,10 +1758,6 @@ export default {
home: '系统首页',
homeTip:'确认要提交当前界面为系统界面吗?',
homeSet:'系统首页设置',
backup: '系统备份',
backupInstruction: '系统备份将会对当前系统上运行的网元信息记录及配置文件进行备份,可进行系统的恢复操作!',
backupExportTip: '确认要导出系统备份吗?',
backupImportTip: '确认要导入系统备份吗?',
},
role:{
allScopeOptions:'全部数据权限',

View File

@@ -5,8 +5,9 @@ import {
clearMenuItem,
MenuDataItem,
} from 'antdv-pro-layout';
import RightContent from './components/RightContent.vue';
import HeaderContentRight from './components/HeaderContentRight/HeaderContentRight.vue';
import Tabs from './components/Tabs.vue';
import CoreSelect from './components/CoreSelect.vue';
import GlobalMask from '@/components/GlobalMask/index.vue';
import ForcePasswdChange from '@/components/ForcePasswdChange/index.vue';
import { scriptUrl } from '@/assets/js/icon_font_8d5l8fzk5b87iudi';
@@ -21,7 +22,8 @@ import {
import { useRouter } from 'vue-router';
import useLayoutStore from '@/store/modules/layout';
import useAppStore from '@/store/modules/app';
import useNeListStore from '@/store/modules/ne_list';
import useCoreStore from '@/store/modules/core';
import useNeStore from '@/store/modules/ne';
import useRouterStore from '@/store/modules/router';
import useTabsStore from '@/store/modules/tabs';
import useI18n from '@/hooks/useI18n';
@@ -34,7 +36,6 @@ import { parseUrlPath } from '@/plugins/file-static-url';
const { proConfig, waterMarkContent } = useLayoutStore();
const { t, currentLocale } = useI18n();
const routerStore = useRouterStore();
const neListStore = useNeListStore();
const tabsStore = useTabsStore();
const appStore = useAppStore();
const router = useRouter();
@@ -68,19 +69,6 @@ watch(
);
// 动态路由添加到菜单面板
// 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;
// }
// }
//
// const { menuData } = getMenuData(clearMenuItem(router.getRoutes()));
//
const menuData = computed(() => {
const rootRoute = router.getRoutes().find(r => r.name === 'Root');
if (rootRoute) {
@@ -92,9 +80,12 @@ const menuData = computed(() => {
rootRoute.children = children;
}
}
const neTypes = neListStore.getNeSelectOtions.map(v => v.value);
let routes = clearMenuItem(router.getRoutes());
routes = routerStore.clearMenuItemByNeList(routes, neTypes);
const coreUid = useCoreStore().getCurrentCoreUid;
const neTypes = useNeStore()
.fnNeSelectOtions(coreUid)
.map(v => v.value);
routes = routerStore.clearMenuItemByNeList(routes, coreUid, neTypes);
const { menuData } = getMenuData(routes);
return menuData;
});
@@ -151,10 +142,13 @@ tabsStore.clear();
// LOGO地址
const logoUrl = computed(() => {
let url =
appStore.logoType === 'brand'
? parseUrlPath(appStore.filePathBrand)
: parseUrlPath(appStore.filePathIcon);
let url = parseUrlPath(appStore.filePathIcon);
if (appStore.logoType === 'brand') {
url = parseUrlPath(appStore.filePathBrand);
}
if (layoutState.collapsed) {
url = parseUrlPath(appStore.filePathIcon);
}
if (url.indexOf('{language}') === -1) {
return url;
@@ -217,7 +211,6 @@ onMounted(() => {
let serverTimeInterval: any = null;
let serverTime = 0;
let serverTimeZone = getTimezoneOffset();
let activeAlarmRefresh = 0;
// 获取服务器时间
function fnGetServerTime() {
@@ -231,7 +224,6 @@ function fnGetServerTime() {
const serverTimeDom = document.getElementById('serverTimeDom');
serverTimeInterval = setInterval(() => {
serverTime += 1000;
activeAlarmRefresh += 1;
if (serverTimeDom) {
serverTimeDom.innerText = parseDateUTCToStr(
serverTime,
@@ -241,10 +233,6 @@ function fnGetServerTime() {
clearInterval(serverTimeInterval);
serverTimeInterval = null;
}
if (activeAlarmRefresh === 5) {
useAlarmStore().fnGetActiveAlarmInfo();
activeAlarmRefresh = 0;
}
}, 1000);
}
});
@@ -281,6 +269,7 @@ onUnmounted(() => {
v-model:openKeys="layoutState.openKeys"
:menu-data="menuData"
:breadcrumb="{ routes: breadcrumb }"
:siderWidth="248"
v-bind="proConfig"
:iconfont-url="scriptUrl"
:locale="fnLocale"
@@ -300,13 +289,17 @@ onUnmounted(() => {
:alt="appStore.appName"
:title="appStore.appName"
/>
<h1 class="app-name" :title="appStore.appName">
<h1
class="app-name"
:title="appStore.appName"
v-show="!layoutState.collapsed"
>
<span class="marquee app-name_scrollable">
{{ appStore.appName }}
</span>
</h1>
</template>
<template v-if="appStore.logoType === 'brand'">
<template v-else-if="appStore.logoType === 'brand'">
<img
class="logo-brand"
:src="logoUrl"
@@ -320,9 +313,14 @@ onUnmounted(() => {
<!--插槽-渲染顶部内容区域仅布局side有效-->
<template #headerContentRender></template>
<!--插槽-渲染顶部内容区域仅布局side有效-->
<template #menuHeaderExtraRender>
<CoreSelect></CoreSelect>
</template>
<!--插槽-渲染顶部内容右端区域-->
<template #headerContentRightRender>
<RightContent />
<HeaderContentRight />
</template>
<!--插槽-导航标签项-->

View File

@@ -0,0 +1,78 @@
<script lang="ts" setup>
import useCoreStore from '@/store/modules/core';
import useI18n from '@/hooks/useI18n';
import { ref } from 'vue';
const coreStore = useCoreStore();
const { t } = useI18n();
/**当前选中 */
const coreValue = ref(
coreStore.current?.value == 'all' ? 'Global' : coreStore.current?.value
);
/**选择列表数据 */
const coreOtions = ref(coreStore.getSelectOtions);
/**选择过滤名称 */
const coreName = ref();
/**选择 */
function handleSelect(v: any, item: any) {
if (v === 'all') {
coreValue.value = 'Global';
} else {
coreValue.value = v;
}
coreStore.setCurrent(item);
}
/**搜索过滤 */
function handleSearchFilter(e: any) {
const label = e.target.value;
coreOtions.value = coreStore.getSelectOtions.filter((item: any) => {
return item.label.indexOf(label) !== -1;
});
}
/**内容挂载DOM */
function getPopupContainer(): any {
return document.querySelector('.ant-pro-sider');
}
</script>
<template>
<a-select
v-model:value="coreValue"
:options="coreOtions"
style="width: 100%"
:getPopupContainer="getPopupContainer"
@change="handleSelect"
>
<template #option="{ label }">
<span>{{ label }}</span>
</template>
<template #dropdownRender="{ menuNode: menu }">
<strong>Core</strong>
<a-button
type="default"
:block="true"
@click="handleSelect('all', { label: 'Global', value: 'all' })"
>
<template #icon> <HomeOutlined /> </template>
Global
</a-button>
<a-input-search
placeholder="Search Core Name"
style="width: 100%; margin: 4px 0"
v-model:value="coreName"
@change="handleSearchFilter"
>
<template #enterButton>
<ReloadOutlined />
</template>
</a-input-search>
<a-divider style="margin: 4px 0" />
<component :is="menu" />
</template>
</a-select>
</template>
<style lang="css" scoped></style>

View File

@@ -0,0 +1,120 @@
<script setup lang="ts">
import svgLight from '@/assets/svg/light.svg';
import svgDark from '@/assets/svg/dark.svg';
import { viewTransitionTheme } from 'antdv-pro-layout';
import { useRouter } from 'vue-router';
import { useFullscreen } from '@vueuse/core';
import { hasPermissions } from '@/plugins/auth-user';
import useI18n from '@/hooks/useI18n';
import useLayoutStore from '@/store/modules/layout';
import useAppStore from '@/store/modules/app';
import useAlarmStore from '@/store/modules/alarm';
import LockScreen from './components/LockScreen.vue';
import UserProfile from './components/UserProfile.vue';
const { isFullscreen, toggle } = useFullscreen();
const { t, changeLocale, optionsLocale, currentLocale } = useI18n();
const layoutStore = useLayoutStore();
const appStore = useAppStore();
const router = useRouter();
/**告警数按钮提示跳转 */
function fnClickAlarm() {
router.push({ name: 'ActiveAlarm_2088' });
}
/**改变主题色 */
function fnClickTheme(e: any) {
viewTransitionTheme(isDarkMode => {
layoutStore.changeConf('theme', isDarkMode ? 'light' : 'dark');
}, e);
}
/**改变多语言 */
function fnChangeLocale(e: any) {
changeLocale(e.key);
}
</script>
<template>
<a-space :size="12" align="center">
<!-- 告警气泡 -->
<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>
<!-- 锁屏操作 -->
<LockScreen></LockScreen>
<!-- 全屏操作 -->
<a-tooltip placement="bottomRight">
<template #title>{{ t('loayouts.rightContent.fullscreen') }}</template>
<a-button type="text" style="color: inherit" @click="toggle">
<template #icon>
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</template>
</a-button>
</a-tooltip>
<!-- 明暗主题操作 -->
<a-tooltip placement="bottomRight">
<template #title>{{ t('loayouts.rightContent.theme') }}</template>
<a-button type="text" @click="fnClickTheme">
<template #icon>
<img
v-if="layoutStore.proConfig.theme === 'dark'"
:src="svgDark"
class="theme-icon"
/>
<img v-else :src="svgLight" class="theme-icon" />
</template>
</a-button>
</a-tooltip>
<!-- 多语言操作 -->
<a-dropdown
placement="bottomRight"
trigger="click"
v-if="appStore.i18nOpen && hasPermissions(['system:setting:i18n'])"
>
<a-button type="text" style="color: inherit">
<template #icon> <TranslationOutlined /> </template>
</a-button>
<template #overlay>
<a-menu @click="fnChangeLocale">
<a-menu-item
v-for="opt in optionsLocale"
:key="opt.value"
:disabled="opt.value == currentLocale"
>
{{ opt.label }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<!-- 用户信息操作 -->
<UserProfile></UserProfile>
</a-space>
</template>
<style lang="css" scoped>
.theme-icon {
width: 18px;
height: 18px;
margin-bottom: 4px;
color: inherit;
}
</style>

View File

@@ -0,0 +1,71 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ProModal } from 'antdv-pro-modal';
import useMaskStore from '@/store/modules/mask';
import useI18n from '@/hooks/useI18n';
const maskStore = useMaskStore();
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
/**锁屏确认 */
const lockConfirm = ref<boolean>(false);
/**锁屏密码 */
const lockPasswd = ref<string>('');
/**锁屏按钮提示 */
function fnClickLock() {
lockConfirm.value = true;
lockPasswd.value = '';
maskStore.lockPasswd = '';
}
/**锁屏确认跳转锁屏页面 */
function fnClickLockToPage() {
lockConfirm.value = false;
maskStore.lockPasswd = lockPasswd.value;
maskStore.handleMaskType('lock');
router.push({ name: 'LockScreen', query: { redirect: route.path } });
}
</script>
<template>
<span v-perms:has="['system:setting:lock']">
<a-tooltip placement="bottomRight">
<template #title>{{ t('loayouts.rightContent.lock') }}</template>
<a-button type="text" style="color: inherit" @click="fnClickLock()">
<template #icon>
<LockOutlined />
</template>
</a-button>
<ProModal
:drag="true"
:center-y="true"
:width="400"
:minHeight="200"
:mask-closable="false"
v-model:open="lockConfirm"
:title="t('loayouts.rightContent.lockTip')"
@ok="fnClickLockToPage()"
>
<a-space>
{{ t('loayouts.rightContent.lockPasswd') }}
<a-input-password
v-model:value="lockPasswd"
:placeholder="t('common.inputPlease')"
>
<template #prefix>
<a-tooltip
:title="t('loayouts.rightContent.lockPasswdTip')"
placement="topLeft"
>
<UnlockOutlined />
</a-tooltip>
</template>
</a-input-password>
</a-space>
</ProModal>
</a-tooltip>
</span>
</template>

View File

@@ -0,0 +1,83 @@
<script lang="ts" setup>
import { useRouter } from 'vue-router';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import useUserStore from '@/store/modules/user';
import useI18n from '@/hooks/useI18n';
const userStore = useUserStore();
const router = useRouter();
const { t } = useI18n();
/**头像展开项点击 */
function fnClick({ key }: MenuInfo) {
switch (key) {
case 'settings':
router.push({ name: 'Settings' });
break;
case 'profile':
router.push({ name: 'Profile' });
break;
case 'logout':
userStore.fnLogOut().finally(() => router.push({ name: 'Login' }));
break;
}
}
</script>
<template>
<a-dropdown placement="bottomRight" trigger="click">
<div class="user">
<a-avatar
shape="circle"
size="default"
:src="userStore.getAvatar"
:alt="userStore.userName"
></a-avatar>
<span class="nick">
{{ userStore.nickName }}
</span>
</div>
<template #overlay>
<a-menu @click="fnClick">
<!-- <a-menu-item key="profile">
<template #icon>
<UserOutlined />
</template>
<span>{{ t('loayouts.rightContent.profile') }}</span>
</a-menu-item> -->
<a-menu-item key="settings">
<template #icon>
<SettingOutlined />
</template>
<span>{{ t('loayouts.rightContent.settings') }}</span>
</a-menu-item>
<a-menu-divider />
<a-menu-item key="logout">
<template #icon>
<LogoutOutlined />
</template>
<span>{{ t('loayouts.rightContent.logout') }}</span>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
<style lang="less" scoped>
.user {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
cursor: pointer;
.nick {
padding-left: 8px;
padding-right: 16px;
font-size: 16px;
max-width: 164px;
white-space: nowrap;
text-align: start;
text-overflow: ellipsis;
overflow: hidden;
}
}
</style>

View File

@@ -1,246 +0,0 @@
<script setup lang="ts">
import svgLight from '@/assets/svg/light.svg';
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 { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useFullscreen } from '@vueuse/core';
import { hasPermissions } from '@/plugins/auth-user';
import useI18n from '@/hooks/useI18n';
import useLayoutStore from '@/store/modules/layout';
import useAppStore from '@/store/modules/app';
import useUserStore from '@/store/modules/user';
import useAlarmStore from '@/store/modules/alarm';
import useMaskStore from '@/store/modules/mask';
const { isFullscreen, toggle } = useFullscreen();
const { t, changeLocale, optionsLocale } = useI18n();
const layoutStore = useLayoutStore();
const maskStore = useMaskStore();
const userStore = useUserStore();
const appStore = useAppStore();
const route = useRoute();
const router = useRouter();
/**头像展开项点击 */
function fnClick({ key }: MenuInfo) {
switch (key) {
case 'settings':
router.push({ name: 'Settings' });
break;
case 'profile':
router.push({ name: 'Profile' });
break;
case 'logout':
userStore.fnLogOut().finally(() => router.push({ name: 'Login' }));
break;
}
}
/**锁屏确认 */
const lockConfirm = ref<boolean>(false);
/**锁屏密码 */
const lockPasswd = ref<string>('');
/**锁屏按钮提示 */
function fnClickLock() {
lockConfirm.value = true;
lockPasswd.value = '';
maskStore.lockPasswd = '';
}
/**锁屏确认跳转锁屏页面 */
function fnClickLockToPage() {
lockConfirm.value = false;
maskStore.lockPasswd = lockPasswd.value;
maskStore.handleMaskType('lock');
router.push({ name: 'LockScreen', query: { redirect: route.path } });
}
/**告警数按钮提示跳转 */
function fnClickAlarm() {
router.push({ name: 'ActiveAlarm_2088' });
}
/**改变主题色 */
function fnClickTheme(e: any) {
viewTransitionTheme(isDarkMode => {
layoutStore.changeConf('theme', isDarkMode ? 'light' : 'dark');
}, e);
}
/**改变多语言 */
function fnChangeLocale(e: any) {
changeLocale(e.key);
}
</script>
<template>
<a-space :size="12" align="center">
<a-tooltip placement="bottomRight">
<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 v-perms:has="['system:setting:lock']">
<a-tooltip placement="bottomRight">
<template #title>{{ t('loayouts.rightContent.lock') }}</template>
<a-button type="text" style="color: inherit" @click="fnClickLock()">
<template #icon>
<LockOutlined />
</template>
</a-button>
<ProModal
:drag="true"
:center-y="true"
:width="400"
:minHeight="200"
:mask-closable="false"
v-model:open="lockConfirm"
:title="t('loayouts.rightContent.lockTip')"
@ok="fnClickLockToPage()"
>
<a-space>
{{ t('loayouts.rightContent.lockPasswd') }}
<a-input-password
v-model:value="lockPasswd"
:placeholder="t('common.inputPlease')"
>
<template #prefix>
<a-tooltip
:title="t('loayouts.rightContent.lockPasswdTip')"
placement="topLeft"
>
<UnlockOutlined />
</a-tooltip>
</template>
</a-input-password>
</a-space>
</ProModal>
</a-tooltip>
</span>
<a-tooltip placement="bottomRight">
<template #title>{{ t('loayouts.rightContent.fullscreen') }}</template>
<a-button type="text" style="color: inherit" @click="toggle">
<template #icon>
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</template>
</a-button>
</a-tooltip>
<a-tooltip placement="bottomRight">
<template #title>{{ t('loayouts.rightContent.theme') }}</template>
<a-button type="text" @click="fnClickTheme">
<template #icon>
<img
v-if="layoutStore.proConfig.theme === 'dark'"
:src="svgDark"
class="theme-icon"
/>
<img v-else :src="svgLight" class="theme-icon" />
</template>
</a-button>
</a-tooltip>
<a-dropdown
placement="bottomRight"
trigger="click"
v-if="appStore.i18nOpen && hasPermissions(['system:setting:i18n'])"
>
<a-button type="text" style="color: inherit">
<template #icon> <TranslationOutlined /> </template>
</a-button>
<template #overlay>
<a-menu @click="fnChangeLocale">
<a-menu-item v-for="opt in optionsLocale" :key="opt.value">
{{ opt.label }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<a-dropdown placement="bottomRight" trigger="click">
<div class="user">
<a-avatar
shape="circle"
size="default"
:src="userStore.getAvatar"
:alt="userStore.userName"
></a-avatar>
<span class="nick">
{{ userStore.nickName }}
</span>
</div>
<template #overlay>
<a-menu @click="fnClick">
<!-- <a-menu-item key="profile">
<template #icon>
<UserOutlined />
</template>
<span>{{ t('loayouts.rightContent.profile') }}</span>
</a-menu-item> -->
<a-menu-item key="settings">
<template #icon>
<SettingOutlined />
</template>
<span>{{ t('loayouts.rightContent.settings') }}</span>
</a-menu-item>
<a-menu-divider />
<a-menu-item key="logout">
<template #icon>
<LogoutOutlined />
</template>
<span>{{ t('loayouts.rightContent.logout') }}</span>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-space>
</template>
<style lang="less" scoped>
.user {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
cursor: pointer;
.nick {
padding-left: 8px;
padding-right: 16px;
font-size: 16px;
max-width: 164px;
white-space: nowrap;
text-align: start;
text-overflow: ellipsis;
overflow: hidden;
}
}
.theme-icon {
width: 18px;
height: 18px;
margin-bottom: 4px;
color: inherit;
}
</style>

View File

@@ -10,7 +10,8 @@ import { getAccessToken } from '@/plugins/auth-token';
import { validHttp } from '@/utils/regular-utils';
import useUserStore from '@/store/modules/user';
import useAppStore from '@/store/modules/app';
import useNeListStore from '@/store/modules/ne_list';
import useCoreStore from '@/store/modules/core';
import useNeStore from '@/store/modules/ne';
import useRouterStore from '@/store/modules/router';
// NProgress Configuration
@@ -49,7 +50,7 @@ router.afterEach((to, from, failure) => {
/**无Token可访问页面地址白名单 */
const WHITE_LIST: string[] = [
'/login',
'/login/oauth2',
'/auth-redirect',
'/help',
'/register',
'/quick-start',
@@ -66,14 +67,14 @@ router.beforeEach(async (to, from, next) => {
await appStore.fnSysConf();
}
// // 需要系统引导跳转
// if (appStore.bootloader && to.path !== '/quick-start') {
// next({ name: 'QuickStart' });
// }
// // 不重复引导
// if (!appStore.bootloader && to.path === '/quick-start') {
// next({ name: 'Index' });
// }
// 需要系统引导跳转
if (appStore.bootloader && to.path !== '/quick-start') {
next({ name: 'QuickStart' });
}
// 不重复引导
if (!appStore.bootloader && to.path === '/quick-start') {
next({ name: 'Index' });
}
let token = getAccessToken();
@@ -103,8 +104,11 @@ router.beforeEach(async (to, from, next) => {
const user = useUserStore();
if (user.roles && user.roles.length === 0) {
try {
// 获取核心网列表
await useCoreStore().fnCorelist();
// 获取网元信息
await useNeListStore().fnNelist();
await useNeStore().fnNelist();
// 获取用户信息
await user.fnGetInfo();
// 获取路由信息
@@ -128,7 +132,7 @@ router.beforeEach(async (to, from, next) => {
} else if (
to.meta.neType &&
to.meta.neType.length > 0 &&
!useNeListStore().fnHasNe(to.meta.neType)
!useNeStore().fnHasNe(to.meta.neType)
) {
next({ name: 'NotPermission' });
} else {

View File

@@ -58,7 +58,7 @@ export const constantRoutes: RouteRecordRaw[] = [
{
path: '/trace-task-hlr',
name: 'TraceTaskHLR', // 跟踪任务HLR
meta: { title: 'router.traceTaskHLR', neType: ['UDM', 'HLR'] },
meta: { title: 'router.traceTaskHLR', neType: ['HLR'] },
component: () => import('@/views/traceManage/task-hlr/index.vue'),
},
{
@@ -74,16 +74,11 @@ export const constantRoutes: RouteRecordRaw[] = [
component: () => import('@/views/tool/lockScreen/index.vue'),
},
{
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',

View File

@@ -22,7 +22,7 @@ type AppStore = {
/**版本号 */
version: string;
/**系统引导使用 */
// bootloader: boolean;
bootloader: boolean;
/**服务版本 */
serverVersion: string;
// 用户登录认证
@@ -59,7 +59,7 @@ const useAppStore = defineStore('app', {
appVersion: import.meta.env.VITE_APP_VERSION,
version: '-',
// bootloader: false,
bootloader: false,
serverVersion: '-',
loginAuth: true,
cryptoApi: true,
@@ -91,12 +91,12 @@ const useAppStore = defineStore('app', {
if (res.code === RESULT_CODE_SUCCESS && res.data) {
this.version = res.data.version;
this.serverVersion = res.data.serverVersion;
// this.bootloader = res.data.bootloader === 'true';
// // 引导时
// if (this.bootloader) {
// delAccessToken();
// delRefreshToken();
// }
this.bootloader = res.data.bootloader === 'true';
// 引导时
if (this.bootloader) {
delAccessToken();
delRefreshToken();
}
this.loginAuth = res.data.loginAuth !== 'false';
this.cryptoApi = res.data.cryptoApi !== 'false';
sessionSet(CACHE_SESSION_CRYPTO_API, res.data.cryptoApi);

77
src/store/modules/core.ts Normal file
View File

@@ -0,0 +1,77 @@
import {
RESULT_CODE_SUCCESS,
RESULT_MSG_SUCCESS,
} from '@/constants/result-constants';
import { defineStore } from 'pinia';
import { listAllCoreInfo } from '@/api/core/coreInfo';
import { localGetJSON, localSetJSON } from '@/utils/cache-local-utils';
import { CACHE_SESSION_CORE } from '@/constants/cache-keys-constants';
/**核心网信息类型 */
type Core = {
/**核心网列表 */
coreList: Record<string, any>[];
/**当前选择 */
current: Record<string, any>;
/**选择器单级父类型 */
coreSelectOtions: Record<string, any>[];
};
const useCoreStore = defineStore('core', {
state: (): Core => ({
coreList: [],
current: localGetJSON(CACHE_SESSION_CORE) || {
label: 'Global',
value: 'all',
},
coreSelectOtions: [],
}),
getters: {
/**当前核心网Uid */
getCurrentCoreUid(): string {
return this.current.value;
},
/**选择器 */
getSelectOtions(): Record<string, any>[] {
return this.coreSelectOtions;
},
},
actions: {
setCurrent(value: Record<string, any> = {}) {
this.current = value;
localSetJSON(CACHE_SESSION_CORE, value);
},
// 刷新核心网列表
async fnCorelistRefresh() {
this.coreList = [];
return await this.fnCorelist();
},
// 获取核心网列表
async fnCorelist() {
// 有数据不请求
if (this.coreList.length > 0) {
return {
code: RESULT_CODE_SUCCESS,
msg: RESULT_MSG_SUCCESS['en_US'],
data: this.coreList,
};
}
const res = await listAllCoreInfo({});
if (res.code === RESULT_CODE_SUCCESS) {
// 原始列表
this.coreList = JSON.parse(JSON.stringify(res.data));
// 转选择器单级父类型
this.coreSelectOtions = res.data.map((item: any) => {
return {
label: item.name,
value: item.coreUid,
};
});
}
return res;
},
},
});
export default useCoreStore;

View File

@@ -70,9 +70,9 @@ const proRender = (render: any) => (render === false ? false : undefined);
const proConfigLocal: LayoutStore['proConfig'] = localGetJSON(
CACHE_LOCAL_PROCONFIG
) || {
layout: 'mix',
layout: 'side',
theme: 'light',
menuTheme: 'light',
menuTheme: 'dark',
fixSiderbar: true,
fixedHeader: true,
splitMenus: true,

View File

@@ -11,16 +11,16 @@ type NeList = {
/**网元列表 */
neList: Record<string, any>[];
/**级联options树结构 */
neCascaderOptions: Record<string, any>[];
neCascaderOptions: Map<string, Record<string, any>[]>;
/**选择器单级父类型 */
neSelectOtions: Record<string, any>[];
neSelectOtions: Map<string, Record<string, any>[]>;
};
const useNeListStore = defineStore('ne_list', {
const useNeStore = defineStore('ne_list', {
state: (): NeList => ({
neList: [],
neCascaderOptions: [],
neSelectOtions: [],
neCascaderOptions: new Map(),
neSelectOtions: new Map(),
}),
getters: {
/**
@@ -72,33 +72,71 @@ const useNeListStore = defineStore('ne_list', {
// 原始列表
this.neList = JSON.parse(JSON.stringify(res.data));
// 转级联数据
const options = parseDataToOptions(
res.data,
'neType',
'neName',
'neId'
'neUid'
);
this.neCascaderOptions = options;
// 转选择器单级父类型
this.neSelectOtions = options.map(item => {
return {
for (const item of options) {
const k = item.coreUid;
// 转级联数据
let cascaderMap = this.neCascaderOptions.get(k);
if (cascaderMap) {
cascaderMap.push(item);
} else {
cascaderMap = [item];
}
this.neCascaderOptions.set(k, cascaderMap);
// 转选择器单级父类型
let selectMap = this.neSelectOtions.get(k);
const selectItem = {
label: item.label,
value: item.value,
};
});
if (selectMap) {
selectMap.push(selectItem);
} else {
selectMap = [selectItem];
}
this.neSelectOtions.set(k, selectMap);
}
}
return res;
},
/**
* options树结构
* @param coreUid uid
*/
fnNeCascaderOptions(coreUid: string) {
const m = this.neCascaderOptions.get(coreUid);
if (!m) {
return [];
}
return m;
},
/**
*
* @param coreUid uid
*/
fnNeSelectOtions(coreUid: string) {
const m = this.neSelectOtions.get(coreUid);
if (!m) {
return [];
}
return m;
},
/**
*
* @param coreUid uid
* @param metaNeType ['udm', 'ims', 'udm+ims', 'SGWC']
* @returns boolean
*/
fnHasNe(metaNeType: string[]) {
fnHasNe(coreUid: string, metaNeType: string[]) {
if (this.neList.length > 0) {
const neTypes = this.neSelectOtions.map(item => item.value);
const neTypes = this.fnNeSelectOtions(coreUid).map(item => item.value);
let match = false; // 匹配
for (const netype of metaNeType) {
if (netype.indexOf('+') > -1) {
@@ -120,4 +158,4 @@ const useNeListStore = defineStore('ne_list', {
},
});
export default useNeListStore;
export default useNeStore;

View File

@@ -58,16 +58,28 @@ const useRouterStore = defineStore('router', {
/**
* 根据网元类型过滤菜单
* @param routes 经过clearMenuItem(router.getRoutes())处理
* @param coreUid 核心网元uid
* @param neTypes 网元类型
* @returns 过滤后的菜单
*/
clearMenuItemByNeList(
routes: RouteRecord[] | RouteRecordRaw[],
coreUid: string,
neTypes: string[]
): RouteRecordRaw[] {
return routes
.map((item: RouteRecord | RouteRecordRaw) => {
const finalItem = { ...item };
// 过滤核心网菜单
if (coreUid.length < 8 && finalItem.meta?.core == true) {
// 全局 网元菜单
return null;
}
if (coreUid.length == 8 && finalItem.meta?.core == false) {
// 核心网 非网元菜单
return null;
}
// 过滤网元类型
if (
Array.isArray(finalItem.meta?.neType) &&
@@ -96,6 +108,7 @@ const useRouterStore = defineStore('router', {
if (finalItem.children && finalItem.children.length > 0) {
const children = this.clearMenuItemByNeList(
finalItem.children,
coreUid,
neTypes
);
// 如果子菜单都被过滤掉了,就不显示

View File

@@ -13,7 +13,7 @@ import { parseUrlPath } from '@/plugins/file-static-url';
/**用户信息类型 */
type UserInfo = {
/**是否强制修改密码 */
/**用户ID */
forcePasswdChange: boolean;
/**用户ID */
userId: string;
@@ -33,8 +33,8 @@ type UserInfo = {
email: string;
/**用户性别 */
sex: string | undefined;
/**用户类型 */
userType: string;
/**其他信息 */
profile: Record<string, any>;
};
const useUserStore = defineStore('user', {
@@ -49,7 +49,7 @@ const useUserStore = defineStore('user', {
phone: '',
email: '',
sex: undefined,
userType: 'System',
profile: {},
}),
getters: {
/**
@@ -132,7 +132,6 @@ const useUserStore = defineStore('user', {
this.phone = user.phone;
this.email = user.email;
this.sex = user.sex;
this.userType = user.userType;
// 验证返回的roles是否是一个非空数组
if (Array.isArray(roles) && roles.length > 0) {

View File

@@ -9,6 +9,11 @@ declare module 'vue-router' {
permissions?: string[];
/**角色 */
roles?: string[];
/**租户标识 */
tenant?: boolean;
/**核心网标识 */
core?: boolean;
/**网元类型信息 */
neType?: string[];
}

View File

@@ -17,35 +17,6 @@ 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';
// 映射时区偏移量与IANA时区名称关联
export const offsetToIanaMap: Record<string, string> = {
'+0000': 'UTC',
'+0100': 'Europe/London',
'+0200': 'Europe/Paris',
'+0300': 'Europe/Moscow',
'+0400': 'Asia/Dubai',
'+0500': 'Asia/Karachi',
'+0600': 'Asia/Almaty',
'+0700': 'Asia/Bangkok',
'+0800': 'Asia/Shanghai',
'+0900': 'Asia/Tokyo',
'+1000': 'Australia/Sydney',
'+1100': 'Pacific/Noumea',
'+1200': 'Pacific/Fiji',
'-0100': 'America/Noronha',
'-0200': 'Atlantic/Azores',
'-0300': 'America/Argentina/Buenos_Aires',
'-0400': 'America/New_York',
'-0500': 'America/New_York',
'-0600': 'America/Chicago',
'-0700': 'America/Denver',
'-0800': 'America/Los_Angeles',
'-0900': 'Pacific/Honolulu',
'-1000': 'Pacific/Honolulu',
'-1100': 'Pacific/Pago_Pago',
'-1200': 'Pacific/Kwajalein',
};
/**
* 格式时间字符串
* @param dateStr 时间字符串
@@ -82,10 +53,9 @@ export function parseDateUTCToStr(
date: string | number | Date,
offset: string = '+0000'
): string {
const ianaTimezone = offsetToIanaMap[offset] || 'UTC';
return dayjs
.utc(date) // 将时间戳按 UTC 时间
.tz(ianaTimezone) // 使用时区偏移
.tz(offset) // 使用时区偏移
.format(`YYYY-MM-DD HH:mm:ss [UTC${offset}]`); // 格式化时间
}

View File

@@ -177,6 +177,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

View File

@@ -20,9 +20,8 @@ export const regExpPort =
* 有效账号格式
*
* 账号只能包含大写字母、小写字母和数字的字符串长度至少为6位
* /^[A-Za-z0-9]{6,}$/
*/
export const regExpUserName = /^.{4,}$/;
export const regExpUserName = /^[A-Za-z0-9]{6,}$/;
/**
* 有效密码格式

View File

@@ -90,7 +90,7 @@ function fnFinish() {
:rules="[
{
required: true,
min: 5,
min: 6,
max: 26,
message: t('views.account.settings.oldPasswordTip'),
},

View File

@@ -6,10 +6,8 @@ import ResetPasswd from './components/reset-passwd.vue';
import StyleLayout from './components/style-layout.vue';
import { useRoute } from 'vue-router';
import useAppStore from '@/store/modules/app';
import useUserStore from '@/store/modules/user';
import useI18n from '@/hooks/useI18n';
const appStore = useAppStore();
const userStore = useUserStore();
const route = useRoute();
const { t } = useI18n();
@@ -45,7 +43,6 @@ onActivated(() => {
<a-tab-pane
key="reset-passwd"
:tab="t('views.account.settings.resetPasswd')"
v-if="userStore.userType === 'System'"
>
<ResetPasswd></ResetPasswd>
</a-tab-pane>

File diff suppressed because it is too large Load Diff

View File

@@ -324,6 +324,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'">
@@ -345,7 +351,6 @@ onBeforeUnmount(() => {
:title="`${t('views.index.mark')} - ${serverState.neName || 'OMC'}`"
style="margin-top: 16px"
size="small"
v-if="false"
>
<a-descriptions
bordered
@@ -364,9 +369,9 @@ onBeforeUnmount(() => {
<a-descriptions-item :label="t('views.index.version')">
{{ serverState.version }}
</a-descriptions-item>
<!-- <a-descriptions-item :label="t('views.index.capability')">
<a-descriptions-item :label="t('views.index.capability')">
{{ serverState.capability }}
</a-descriptions-item> -->
</a-descriptions-item>
<!-- <a-descriptions-item :label="t('views.index.cpuUse')">
{{ serverState.cpuUse }}
</a-descriptions-item>

View File

@@ -11,7 +11,7 @@ import {
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import useDictStore from '@/store/modules/dict';
import useNeListStore from '@/store/modules/ne_list';
import useNeStore from '@/store/modules/ne';
import { listAMFDataUE, delAMFDataUE, exportAMFDataUE } from '@/api/neData/amf';
import { parseDateToStr } from '@/utils/date-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket';
@@ -22,7 +22,7 @@ import { useClipboard } from '@vueuse/core';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n();
const { getDict } = useDictStore();
const neListStore = useNeListStore();
const neListStore = useNeStore();
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
/**网元可选 */
@@ -134,12 +134,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',
@@ -174,7 +174,7 @@ let tableColumns: ColumnsType = [
if (record?.time) {
return record.time;
}
return parseDateToStr(record.timestamp);
return parseDateToStr(+record.timestamp * 1000);
},
},
{
@@ -339,11 +339,9 @@ function fnExportList() {
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.pageSize = 10000;
exportAMFDataUE(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
@@ -687,16 +685,16 @@ onBeforeUnmount(() => {
<DictTag :options="dict.ueEventType" :value="record.eventType" />
</template>
<template v-if="column.key === 'result'">
<span v-if="record.eventType === 'Auth'">
<span v-if="record.eventType === 'auth-result'">
<DictTag
:options="dict.ueAauthCode"
:value="record.eventJSON.result"
/>
</span>
<span v-if="record.eventType === 'Detach'">
<span v-if="record.eventType === 'detach'">
<span>{{ t('views.dashboard.ue.resultOk') }}</span>
</span>
<span v-if="record.eventType === 'CM'">
<span v-if="record.eventType === 'cm-state'">
<DictTag
:options="dict.ueEventCmState"
:value="record.eventJSON.result"
@@ -768,16 +766,16 @@ onBeforeUnmount(() => {
</div>
<div>
<span>{{ t('views.dashboard.ue.result') }}: </span>
<span v-if="record.eventType === 'Auth'">
<span v-if="record.eventType === 'auth-result'">
<DictTag
:options="dict.ueAauthCode"
:value="record.eventJSON.result"
/>
</span>
<span v-if="record.eventType === 'Detach'">
<span v-if="record.eventType === 'detach'">
{{ t('views.dashboard.ue.resultOk') }}
</span>
<span v-if="record.eventType === 'CM'">
<span v-if="record.eventType === 'cm-state'">
<DictTag
:options="dict.ueEventCmState"
:value="record.eventJSON.result"

View File

@@ -11,7 +11,7 @@ import {
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import useDictStore from '@/store/modules/dict';
import useNeListStore from '@/store/modules/ne_list';
import useNeStore from '@/store/modules/ne';
import {
delIMSDataCDR,
exportIMSDataCDR,
@@ -26,7 +26,7 @@ import dayjs, { type Dayjs } from 'dayjs';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n();
const { getDict } = useDictStore();
const neListStore = useNeListStore();
const neListStore = useNeStore();
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
@@ -141,12 +141,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',
@@ -290,11 +290,14 @@ function fnTableSelectedRowKeys(keys: (string | number)[]) {
type ModalStateType = {
/**确定按钮 loading */
confirmLoading: boolean;
/**最大ID值 */
maxId: number;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
confirmLoading: false,
maxId: 0,
});
/**
@@ -396,6 +399,11 @@ function fnGetList(pageNum?: number) {
return item;
});
// 取最大值ID用作实时累加
if (total > 0) {
modalState.maxId = Number(rows[0].id);
}
}
tableState.loading = false;
});
@@ -408,11 +416,9 @@ function fnExportList() {
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.pageSize = 10000;
exportIMSDataCDR(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
@@ -484,18 +490,15 @@ function wsMessage(res: Record<string, any>) {
// cdrEvent CDR会话事件
if (data.groupId === `1005_${queryParams.neId}`) {
const cdrEvent = data.data;
let cdrJSON = {};
try {
cdrJSON = JSON.parse(cdrEvent.cdrJSON);
} catch (e) {}
queue.add(async () => {
modalState.maxId += 1;
tableState.data.unshift({
id: cdrEvent.id,
id: modalState.maxId,
neType: cdrEvent.neType,
neName: cdrEvent.neName,
rmUID: cdrEvent.rmUID,
timestamp: cdrEvent.timestamp,
cdrJSON: cdrJSON,
cdrJSON: cdrEvent.CDR,
});
tablePagination.total += 1;
if (tableState.data.length > 100) {

View File

@@ -12,7 +12,7 @@ import {
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import useDictStore from '@/store/modules/dict';
import useNeListStore from '@/store/modules/ne_list';
import useNeStore from '@/store/modules/ne';
import { listMMEDataUE, delMMEDataUE, exportMMEDataUE } from '@/api/neData/mme';
import { parseDateToStr } from '@/utils/date-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket';
@@ -22,7 +22,7 @@ import { useClipboard } from '@vueuse/core';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n();
const { getDict } = useDictStore();
const neListStore = useNeListStore();
const neListStore = useNeStore();
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
@@ -135,12 +135,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',
@@ -175,7 +175,7 @@ let tableColumns: ColumnsType = [
if (record?.time) {
return record.time;
}
return parseDateToStr(record.timestamp);
return parseDateToStr(+record.timestamp * 1000);
},
},
{
@@ -341,11 +341,9 @@ function fnExportList() {
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.pageSize = 10000;
exportMMEDataUE(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
@@ -462,7 +460,7 @@ onMounted(() => {
if (resArr[1].status === 'fulfilled') {
const ueEventType: any[] = JSON.parse(JSON.stringify(resArr[1].value));
dict.ueEventType = ueEventType.map(item => {
if (item.value === 'CM') {
if (item.value === 'cm-state') {
item.label = item.label.replace('CM', 'ECM');
}
return item;
@@ -695,16 +693,16 @@ onBeforeUnmount(() => {
<DictTag :options="dict.ueEventType" :value="record.eventType" />
</template>
<template v-if="column.key === 'result'">
<span v-if="record.eventType === 'Auth'">
<span v-if="record.eventType === 'auth-result'">
<DictTag
:options="dict.ueAauthCode"
:value="record.eventJSON.result"
/>
</span>
<span v-if="record.eventType === 'Detach'">
<span v-if="record.eventType === 'detach'">
<span>{{ t('views.dashboard.ue.resultOk') }}</span>
</span>
<span v-if="record.eventType === 'CM'">
<span v-if="record.eventType === 'cm-state'">
<DictTag
:options="dict.ueEventCmState"
:value="record.eventJSON.result"
@@ -776,16 +774,16 @@ onBeforeUnmount(() => {
</div>
<div>
<span>{{ t('views.dashboard.ue.result') }}: </span>
<span v-if="record.eventType === 'Auth'">
<span v-if="record.eventType === 'auth-result'">
<DictTag
:options="dict.ueAauthCode"
:value="record.eventJSON.result"
/>
</span>
<span v-if="record.eventType === 'Detach'">
<span v-if="record.eventType === 'detach'">
{{ t('views.dashboard.ue.resultOk') }}
</span>
<span v-if="record.eventType === 'CM'">
<span v-if="record.eventType === 'cm-state'">
<DictTag
:options="dict.ueEventCmState"
:value="record.eventJSON.result"

View File

@@ -78,9 +78,9 @@ const alarmTypeType = ref<any>([
/**告警类型Top数据 */
const alarmTypeTypeTop = ref<any>([
{ neType: 'AMF', total: 0 },
{ neType: 'UDM', total: 0 },
{ neType: 'SMF', total: 0 },
{ name: 'AMF', value: 0 },
{ name: 'UDM', value: 0 },
{ name: 'SMF', value: 0 },
]);
//
@@ -92,7 +92,7 @@ function initPicture() {
if (res0.code === RESULT_CODE_SUCCESS && Array.isArray(res0.data)) {
for (const item of res0.data) {
let index = 0;
switch (item.severity) {
switch (item.name) {
case 'Critical':
index = 0;
break;
@@ -109,7 +109,7 @@ function initPicture() {
// index = 4;
// break;
}
alarmTypeType.value[index].value = Number(item.total);
alarmTypeType.value[index].value = Number(item.value);
}
}
}
@@ -119,7 +119,7 @@ function initPicture() {
alarmTypeTypeTop.value = alarmTypeTypeTop.value
.concat(res1.data)
.sort((a: any, b: any) => {
return b.total - a.total;
return b.value - a.value;
})
.slice(0, 3);
}
@@ -210,7 +210,7 @@ function initPicture() {
{ offset: 1, color: '#2f54eb' },
]), // 渐变
},
data: alarmTypeTypeTop.value.map((item: any) => item.total),
data: alarmTypeTypeTop.value,
},
],
// 柱状图设置
@@ -237,7 +237,7 @@ function initPicture() {
show: false,
},
inverse: true,
data: alarmTypeTypeTop.value.map((item: any) => item.neType),
data: alarmTypeTypeTop.value.map((item: any) => item.name),
axisLabel: {
color: '#A7D6F4',
fontSize: 14,

View File

@@ -152,35 +152,38 @@ onMounted(() => {
<div></div>
</div>
<div class="card-ue-w33" v-if="item.type === 'Auth'">
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
<div>
GNB ID: <span>{{ item.data.nbId }}</span>
GNB ID: <span>{{ item.data.gNBID }}</span>
</div>
<div>
Cell ID: <span>{{ item.data.cellId }}</span>
Cell ID: <span>{{ item.data.cellID }}</span>
</div>
<div>
TAC ID: <span>{{ item.data.tac }}</span>
TAC ID: <span>{{ item.data.tacID }}</span>
</div>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<template v-if="item.data?.recordTime">
{{ parseDateToStr(item.data.recordTime) }}
<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'">
<div v-if="item.type === 'auth-result'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span>
<DictTag :options="dict.ueAauthCode" :value="item.data.result" />
</span>
</div>
<div v-if="item.type === 'Detach'">
<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'">
<div class="card-ue-w33" v-if="item.type === 'cm-state'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span>
<DictTag :options="dict.ueEventCmState" :value="item.data.result" />
@@ -196,7 +199,7 @@ onMounted(() => {
<div class="card-ue-item">
<div>
{{ t('views.dashboard.overview.userActivity.type') }}:&nbsp;
<span v-if="item.type === 'CM'">
<span v-if="item.type === 'cm-state'">
{{
dict.ueEventType
.find(s => s.value === item.type)
@@ -213,35 +216,42 @@ onMounted(() => {
<div></div>
</div>
<div class="card-ue-w33" v-if="item.type === 'Auth'">
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
<div>
ENB ID: <span>{{ item.data.nbId }}</span>
ENB ID: <span>{{ item.data.eNBID }}</span>
</div>
<div>
Cell ID: <span>{{ item.data.cellId }}</span>
Cell ID: <span>{{ item.data.cellID }}</span>
</div>
<div>
TAC ID: <span>{{ item.data.tac }}</span>
TAC ID: <span>{{ item.data.tacID }}</span>
</div>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<template v-if="item.data?.recordTime">
{{ parseDateToStr(item.data.recordTime) }}
<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'">
<div v-if="item.type === 'auth-result'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span>
<DictTag :options="dict.ueAauthCode" :value="item.data.result" />
</span>
</div>
<div v-if="item.type === 'Detach'">
<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'">
<div class="card-ue-w33" v-if="item.type === 'cm-state'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span>
<DictTag :options="dict.ueEventCmState" :value="item.data.result" />

View File

@@ -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 = {
@@ -81,9 +81,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,
};
}

View File

@@ -19,6 +19,9 @@ import PQueue from 'p-queue';
/**UPF-的Id */
export const upfWhoId = ref<any>('');
/**UPF-的RmUid */
export const upfWhoRmUid = ref<any>('');
/**websocket连接 */
export default function useWS() {
const ws = new WS();
@@ -68,13 +71,13 @@ export default function useWS() {
}
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 +87,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 +122,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 +197,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) => {

View File

@@ -27,13 +27,13 @@ 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 useNeListStore from '@/store/modules/ne_list';
import useNeStore from '@/store/modules/ne';
import { message } from 'ant-design-vue';
import { upfWhoId } from './hooks/useWS';
const router = useRouter();
const appStore = useAppStore();
const neListStore = useNeListStore();
const neListStore = useNeStore();
const { t } = useI18n();
const { wsSend, userActivitySend, upfTFSend, reSendUPF } = useWS();
@@ -229,15 +229,15 @@ function loadData() {
interval10s.value = setInterval(() => {
if (!interval10s.value || !initFlag) return;
if (upfTFActive.value === '0') {
upfTFSend('7');
upfTFActive.value = '7';
} else if (upfTFActive.value === '7') {
upfTFSend('30');
upfTFActive.value = '30';
} else if (upfTFActive.value === '30') {
upfTFSend('0');
upfTFActive.value = '0';
}
upfTFSend('0');
upfTFSend('7');
upfTFSend('30');
}, 10_000);
clearInterval(interval5s.value);
@@ -679,6 +679,7 @@ onBeforeUnmount(() => {
.toDeep :deep(.ant-select-selection-item) {
color: #fff;
}
.toDeep-text {
color: #4c9bfd !important;
font-size: 0.844rem !important;

View File

@@ -1,392 +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>([
{ neType: 'AMF', total: 0 },
{ neType: 'UDM', total: 0 },
{ neType: 'SMF', total: 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;
console.log(res0)
switch (item.severity) {
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.total);
console.log(item)
console.log(alarmTypeType.value[0])
}
}
}
if (resArr[1].status === 'fulfilled') {
const res1 = resArr[1].value;
if (res1.code === RESULT_CODE_SUCCESS && Array.isArray(res1.data)) {
console.log(res1.data)
alarmTypeTypeTop.value = res1.data
.sort((a: any, b: any) => b.value - a.value)
.slice(0, 3);
}
console.log(alarmTypeTypeTop.value)
}
})
.then(() => {
// 计算最大值用于标准化
const maxAlarmValue = Math.max(...alarmTypeType.value.map((item: any) => item.value || 0), 1);
const maxTopValue = Math.max(...alarmTypeTypeTop.value.map((item: any) => item.total || 0), 1);
const optionData: EChartsOption = {
title: [
{
text: 'Top3',
left: 'center',
top: '48%',
textStyle: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
},
},
],
grid: [
{ // 主图
top: '5%',
left: '20%',
right: '10%',
height: '38%'
},
{ // Top3
top: '58%',
left: '20%',
right: '10%',
height: '32%'
}
],
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: function(params: any) {
if (Array.isArray(params) && params.length > 0) {
const param = params[0];
// 获取真实值
let realValue = 0;
if (param.seriesIndex === 0 || param.seriesIndex === 1) {
// 告警类型背景或实际值
realValue = alarmTypeType.value[param.dataIndex]?.value || 0;
} else if (param.seriesIndex === 2 || param.seriesIndex === 3) {
// Top3背景或实际值
realValue = alarmTypeTypeTop.value[param.dataIndex]?.total || 0;
}
return `${param.name}: ${realValue}`;
}
return '';
},
},
legend: {
show: false
},
xAxis: [
{
type: 'value',
gridIndex: 0,
show: false,
max: maxAlarmValue
},
{
type: 'value',
gridIndex: 1,
show: false,
max: maxTopValue
}
],
yAxis: [
{
type: 'category',
gridIndex: 0,
data: alarmTypeType.value.map((item: any) => item.name),
axisLabel: {
color: '#fff',
fontSize: 12,
fontWeight: 'bold',
interval: 0 // 确保显示所有标签
},
axisLine: { show: false },
axisTick: { show: false },
inverse: true
},
{
type: 'category',
gridIndex: 1,
data: alarmTypeTypeTop.value.map((item: any) => item.neType),
axisLabel: {
color: '#fff',
fontSize: 12,
fontWeight: 'bold',
interval: 0 // 确保显示所有标签
},
axisLine: { show: false },
axisTick: { show: false },
inverse: true
}
],
series: [
// 告警类型透明背景柱状图
{
type: 'bar',
xAxisIndex: 0,
yAxisIndex: 0,
barWidth: 16,
barGap: '-100%', // 重叠显示
itemStyle: {
borderRadius: [0, 8, 8, 0],
color: 'rgba(255,255,255,0.1)',
borderColor: '#666',
borderWidth: 1
},
data: Array(alarmTypeType.value.length).fill(maxAlarmValue),
zlevel: 1,
silent: true, // 不响应鼠标事件
label: {
show: true,
position: 'right',
color: '#fff',
fontWeight: 'bold',
fontSize: 14,
formatter: (params: any) => {
const realValue = alarmTypeType.value[params.dataIndex]?.value || 0;
return `${realValue}`;
},
}
},
// 告警类型实际值柱状图
{
type: 'bar',
xAxisIndex: 0,
yAxisIndex: 0,
barWidth: 16,
barGap: '-100%', // 重叠显示
itemStyle: {
borderRadius: [0, 8, 8, 0],
color: function (params: any) {
// 如果值为0不显示颜色条
if (params.value === 0) {
return 'transparent';
}
// 渐变色
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: false }, // 实际值柱状图不显示标签
data: alarmTypeType.value.map((item: any) => item.value || 0),
zlevel: 2
},
// Top3透明背景柱状图
{
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 1,
barWidth: 16,
barGap: '-100%', // 重叠显示
itemStyle: {
borderRadius: [0, 20, 20, 0],
color: 'rgba(255,255,255,0.1)',
borderColor: '#666',
borderWidth: 1
},
data: Array(alarmTypeTypeTop.value.length).fill(maxTopValue),
zlevel: 1,
silent: true, // 不响应鼠标事件
label: {
show: true,
position: 'right',
color: '#fff',
fontWeight: 'bold',
fontSize: 14,
formatter: (params: any) => {
const realValue = alarmTypeTypeTop.value[params.dataIndex]?.total || 0;
return `${realValue}`;
},
}
},
// Top3实际值柱状图
{
name: 'Top3',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 1,
barWidth: 16,
barGap: '-100%', // 重叠显示
itemStyle: {
borderRadius: [0, 20, 20, 0],
color: function (params: any) {
// 如果值为0不显示颜色条
if (params.value === 0) {
return 'transparent';
}
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#f0f5ff' },
{ offset: 0.5, color: '#adc6ff' },
{ offset: 1, color: '#2f54eb' },
]);
}
},
label: { show: false }, // 实际值柱状图不显示标签
data: alarmTypeTypeTop.value.map((item: any) => item.total || 0),
zlevel: 2
}
]
};
fnDesign(alarmTypeBar.value, optionData);
});
}
function fnDesign(container: HTMLElement | undefined, option: any) {
if (!container) return;
alarmTypeBarChart.value = markRaw(echarts.init(container, 'light'));
option && alarmTypeBarChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (alarmTypeBarChart.value) {
alarmTypeBarChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
onMounted(() => {
initPicture();
});
</script>
<template>
<div ref="alarmTypeBar" class="chart-container"></div>
</template>
<style lang="less" scoped>
.chart-container {
/* 设置图表容器大小和位置 */
width: 100%;
height: 100%;
}
</style>

View File

@@ -1,284 +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') }}:&nbsp;
<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') }}:&nbsp;
<span>{{ parseDuration(item.data.callDuration) }}</span>
</div>
<div v-else></div>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:&nbsp;
<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') }}:&nbsp;
<span v-if="item.data.callType !== 'sms'">
<DictTag
:options="dict.cdrSipCode"
:value="item.data.cause"
value-default="0"
/>
&nbsp;-&nbsp;
<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>

View File

@@ -1,369 +0,0 @@
<script setup lang="ts">
import { onMounted, ref, nextTick, watch } from 'vue';
import * as echarts from 'echarts/core';
import { GridComponent, 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]);
/**图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: '{a}: {c}%',
formatter: (params: any) => {
return `${params.seriesName}: ${(params.value * 100).toFixed(2)}%`;
},
},
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>

View File

@@ -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>

View File

@@ -1,290 +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: '001',
beginTime: 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>

View File

@@ -1,314 +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') }}:&nbsp;
<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'">
<div>
GNB ID: <span>{{ item.data.nbId }}</span>
</div>
<div>
Cell ID: <span>{{ item.data.cellId }}</span>
</div>
<div>
TAC ID: <span>{{ item.data.tac }}</span>
</div>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<template v-if="item.data?.recordTime">
{{ parseDateToStr(item.data.recordTime) }}
</template>
<template v-else> - </template>
</div>
<div v-if="item.type === 'Auth'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<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'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<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') }}:&nbsp;
<span v-if="item.type === 'CM'">
{{
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'">
<div>
ENB ID: <span>{{ item.data.nbId }}</span>
</div>
<div>
Cell ID: <span>{{ item.data.cellId }}</span>
</div>
<div>
TAC ID: <span>{{ item.data.tac }}</span>
</div>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<template v-if="item.data?.recordTime">
{{ parseDateToStr(item.data.recordTime) }}
</template>
<template v-else> - </template>
</div>
<div v-if="item.type === 'Auth'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<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'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<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>

View File

@@ -1,506 +0,0 @@
.viewport {
/* 限定大小 */
min-width: 1024px;
max-width: 1920px;
min-height: 780px;
margin: 0 auto;
position: relative;
display: flex;
padding: 5rem 0.833rem 0;
line-height: 1.15;
background-image:
linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.75)),
url(../images/bj.png);
height: 100vh;
margin-bottom: -20px;
background-size:80% 80%;
background-attachment:fixed;
-webkit-background-size: cover;
}
.column {
flex: 2;
position: relative;
display: flex;
flex-direction: column;
}
/* 边框 */
.panel {
box-sizing: border-box;
border: 2px solid rgba(252, 252, 252, 0);
border-width: 2.125rem 1.583rem 0.875rem 5.5rem;
position: relative;
margin-bottom: 0.833rem;
}
/* 使用伪元素创建L形角框 */
/* 添加L形角框边框效果 */
.panel::before,
.panel::after {
content: '';
position: absolute;
pointer-events: none;
z-index: 10;
display: none; /* 隐藏所有角框 */
}
/* 左上角L形 - 相对于inner的实际边界 */
.panel::before {
top: -2.125rem;
left: -5.5rem;
width: 10px;
height: 10px;
border-top: 3px solid #4c9bfd;
border-left: 3px solid #4c9bfd;
}
/* 右下角L形 - 相对于inner的实际边界 */
.panel::after {
bottom: -0.875rem;
right: -1.583rem;
width: 10px;
height: 10px;
border-bottom: 3px solid #4c9bfd;
border-right: 3px solid #4c9bfd;
}
/* 右上角L形 - 相对于inner的实际边界 */
.panel .inner::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 10px;
height: 10px;
border-top: 3px solid #4c9bfd;
border-right: 3px solid #4c9bfd;
pointer-events: none;
z-index: 10;
display: none; /* 隐藏所有角框 */
}
/* 左下角L形 - 相对于inner的实际边界 */
.panel .inner::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 10px;
height: 10px;
border-bottom: 3px solid #4c9bfd;
border-left: 3px solid #4c9bfd;
pointer-events: none;
z-index: 10;
display: none; /* 隐藏所有角框 */
}
/* 为base模块调整角框显示 */
/* 第一个base模块只显示上方角框左上角和右上角 */
.skim.panel.base:first-of-type::after,
.skim.panel.base:first-of-type .inner::after {
display: none;
}
/* 第二个base模块只显示下方角框左下角和右下角 */
.skim.panel.base:not(:first-of-type)::before,
.skim.panel.base:not(:first-of-type) .inner::before {
display: none;
}
.panel .inner {
/* 装内容 */
/* height: 60px; */
position: absolute;
top: -2.125rem;
right: -1.583rem;
bottom: -0.875rem;
left: -5.5rem;
padding: 1rem 0.3rem;
}
.panel h3 {
font-size: 0.833rem;
color: #fff;
background: rgba(76, 155, 253, 0.1); /* 为所有模块标题添加淡蓝色背景 */
padding: 0.5rem 1.2rem;
border-radius: 0.7rem 0.7rem 0 0;
margin: 0;
box-sizing: border-box;
}
.leftright {
width: 100%;
min-height: 2.5rem;
/*background: url(../images/title.png) no-repeat center center;*/
background: rgba(76, 155, 253, 0.1); /* 添加淡蓝色背景 */
background-size: 100%;
color: #4c9bfd;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
padding: 0.5rem 1.2rem;
border-radius: 0.7rem 0.7rem 0 0;
margin: 0;
box-sizing: border-box;
text-shadow: 0 1px 4px #000a;
flex-wrap: nowrap;
/* 保证内容不换行且居中 */
}
.leftright .title {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
font-size: 1.2rem;
padding-left: 0;
}
.centerStyle {
width: 100%;
min-height: 2.5rem;
/*background: url(../images/title.png) no-repeat center center;*/
background: rgba(76, 155, 253, 0.1); /* 添加淡蓝色背景 */
background-size: 90%;
color: #4c9bfd;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
padding: 0.5rem 1.2rem;
border-radius: 0.7rem 0.7rem 0 0;
margin: 0;
box-sizing: border-box;
text-shadow: 0 1px 4px #000a;
flex-wrap: nowrap;
/* 保证内容不换行且居中 */
}
.centerStyle .title {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
font-size: 1.2rem;
padding-left: 0;
}
/* 总览标题 */
.brand {
background-image: url(../images/newBrand.png);
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
position: absolute;
top: 0.833rem;
left: 0;
right: 0;
width: 100%;
height: 5rem;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
}
.brand .brand-title {
color: #ffffff;
font-size: 1.4rem;
font-weight: 600;
padding-top: 1rem;
padding-bottom: 0.5rem;
}
.brand .brand-desc {
color: #d9d9d9;
font-size: 0.9rem;
}
/* 实时流量 */
.upfFlow {
/* min-height: 16rem; */
height: 40%;
}
.upfFlow .inner .chart {
width: 100%;
height: 100%;
margin-top: 0rem;
}
/* 网络拓扑 */
.topology {
/* min-height: 27.8rem; */
height: 46.4%;
flex: 1;
}
.topology .inner h3 {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: baseline;
background: rgba(76, 155, 253, 0.1); /* 为网络拓扑模块标题添加淡蓝色背景 */
padding: 0.5rem 1.2rem;
border-radius: 0.7rem 0.7rem 0 0;
margin: 0;
box-sizing: border-box;
}
.topology .inner h3 .normal {
color: #52c41a;
font-size: 1.1rem;
margin-right: 8px;
}
.topology .inner h3 .abnormal {
color: #f5222d;
font-size: 1.1rem;
}
.topology .inner .chart {
width: 100%;
height: 100%;
margin-top: 0.1rem;
}
/* 概览区域 */
.skim {
/* min-height: 7.78rem; */
height: 14.4%;
}
.skim .inner .data {
display: flex;
flex-direction: row;
align-items: center;
height: 90%;
}
.skim .inner .data .item {
display: flex;
flex-direction: column;
align-items: baseline;
width: 33%;
}
.skim .inner .data .item div {
font-size: 1.467rem;
color: #fff;
margin-bottom: 0;
display: flex;
align-items: baseline;
line-height: 2rem;
}
.skim .inner .data .item span {
color: #4c9bfd;
font-size: 0.833rem;
width: 100%;
position: relative;
line-height: 2rem;
white-space: nowrap;
text-align: start;
text-overflow: ellipsis;
overflow: hidden;
}
.skim .inner .data .item span::before {
content: ' ';
position: absolute;
top: 2px;
left: 0;
right: 0;
bottom: 0;
z-index: 0;
background-image: linear-gradient(to right, #fff, #fff0);
height: 1px;
border-radius: 4px;
}
/* 概览区域 衍生基站信息 */
.skim.base {
height: 11.25%;
}
.skim.base .inner .data {
display: flex;
flex-direction: row;
align-items: center;
height: 75%;
}
.skim.base .inner .data .item {
display: flex;
flex-direction: column;
align-items: baseline;
width: 50%;
}
/* 用户行为 */
.userActivity {
/* min-height: 35.8rem; */
height: 54.6%;
flex: 1;
}
.userActivity .inner .chart {
width: 100%;
height: 90%;/*生效了底部边距*/
margin-top: 1rem;
padding-bottom: 1rem;
box-sizing: border-box;/*不知道为啥没生效*/
}
/* 流量统计 */
.upfFlowTotal1 {
/* min-height: 7.5rem; */
height: 14.4%;
}
.upfFlowTotal1 .inner h3 {
display: flex;
justify-content: space-between;
background: rgba(76, 155, 253, 0.1); /* 为流量统计模块标题添加淡蓝色背景 */
padding: 0.5rem 1.2rem;
border-radius: 0.7rem 0.7rem 0 0;
margin: 0;
box-sizing: border-box;
}
.upfFlowTotal1 .inner h3 .filter {
display: flex;
}
.upfFlowTotal1 .inner h3 .filter span {
display: block;
height: 0.75rem;
line-height: 1;
padding: 0 0.75rem;
color: #1950c4;
font-size: 0.75rem;
border-right: 0.083rem solid #00f2f1;
cursor: pointer;
}
.upfFlowTotal1 .inner h3 .filter span:first-child {
padding-left: 0;
}
.upfFlowTotal1 .inner h3 .filter span:last-child {
border-right: none;
}
.upfFlowTotal1 .inner h3 .filter span.active {
color: #fff;
font-size: 0.833rem;
}
.upfFlowTotal1 .inner .chart {
width: 100%;
height: 100%;
margin-top: 0.1rem;
}
.upfFlowTotal1 .inner .chart .data {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 60%;
}
.upfFlowTotal1 .inner .chart .data .item {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.upfFlowTotal1 .inner .chart .data .item h4 {
font-size: 1.467rem;
color: #fff;
margin-bottom: 0;
}
.upfFlowTotal1 .inner .chart .data .item span {
color: #4c9bfd;
font-size: 0.867rem;
}
/* 流量统计 */
.upfFlowTotal {
/* min-height: 7.5rem; */
height: 14.4%;
}
.upfFlowTotal .inner h3 {
display: flex;
justify-content: space-between;
background: rgba(76, 155, 253, 0.1); /* 为流量统计模块标题添加淡蓝色背景 */
padding: 0.5rem 1.2rem;
border-radius: 0.7rem 0.7rem 0 0;
margin: 0;
box-sizing: border-box;
}
.upfFlowTotal .inner h3 .filter {
display: flex;
}
.upfFlowTotal .inner h3 .filter span {
display: block;
height: 0.75rem;
line-height: 1;
padding: 0 0.75rem;
color: #1950c4;
font-size: 0.75rem;
border-right: 0.083rem solid #00f2f1;
cursor: pointer;
}
.upfFlowTotal .inner h3 .filter span:first-child {
padding-left: 0;
}
.upfFlowTotal .inner h3 .filter span:last-child {
border-right: none;
}
.upfFlowTotal .inner h3 .filter span.active {
color: #fff;
font-size: 0.833rem;
}
.upfFlowTotal .inner .chart {
width: 100%;
height: 100%;
margin-top: 0.1rem;
}
.upfFlowTotal .inner .chart .data {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 60%;
}
.upfFlowTotal .inner .chart .data .item {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.upfFlowTotal .inner .chart .data .item h4 {
font-size: 1.467rem;
color: #fff;
margin-bottom: 0;
}
.upfFlowTotal .inner .chart .data .item span {
color: #4c9bfd;
font-size: 0.867rem;
}
/* 资源情况 */
.resources {
/* min-height: 18rem; */
height: 24.4%;
}
.resources .inner .chart {
width: 100%;
height: 100%;
margin-top: 2rem;
}
/* 告警统计 */
.alarmType {
/* min-height: 25rem; */
height: 35%;
}
.alarmType .inner .chart {
width: 100%;
height: 100%;
}
/* 为base模块的第二个模块4G部分添加标题样式 */
.skim.panel.base:not(:first-of-type) h3 {
background: transparent; /* 移除背景,避免阴影问题 */
padding: 0;
margin: 0;
min-height: 0;
display: none; /* 完全隐藏空的h3元素 */
}
/* 跳转鼠标悬浮 */
.toRouter:hover {
cursor: pointer;
color: #fff !important;
}
.toRouter:hover > *,
.toRouter:hover > * > * {
color: #fff !important;
}

View File

@@ -1,197 +0,0 @@
import { parseDateToStr } from '@/utils/date-utils';
import { computed, reactive, ref } from 'vue';
/**非网元元素 */
export const notNeNodes = [
'5GC',
'DN',
'UE',
'Base',
'lan',
'lan1',
'lan2',
'lan3',
'lan4',
'lan5',
'lan6',
'lan7',
'LAN',
'NR',
];
/**图状态 */
export const graphState = reactive<Record<string, any>>({
/**当前图组名 */
group: '5GC System Architecture',
/**图数据 */
data: {
combos: [],
edges: [],
nodes: [],
},
});
/**图实例对象 */
export const graphG6 = ref<any>(null);
/**图点击选择 */
export const graphNodeClickID = ref<string>('UPF_001');
/**图节点网元信息状态 */
export const graphNodeState = computed(() =>{
return graphState.data.nodes.map((item: any) => ({
id: item.id,
label: item.label,
neInfo: item.neInfo,
neState: item.neState,
neInfoList:item.neInfoList,
neStateMap: item.neStateMap,
}))
}
);
/**图节点网元状态数量 */
export const graphNodeStateNum = computed(() => {
let normal = 0;
let abnormal = 0;
for (const item of graphState.data.nodes) {
const neId = item.neState.neId;
if (neId) {
if (item.neState.online) {
normal += 1;
} else {
abnormal += 1;
}
}
}
return [normal, abnormal];
});
/**网元状态请求标记 */
export const neStateRequestMap = ref<Map<string, boolean>>(new Map());
/**neStateParse 网元状态 数据解析 */
export function neStateParse(neType: string, data: Record<string, any>,neId: string) {
// console.log('neStateParse',neType, data, neId);
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对象
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 查询节点实例
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 {
// 更新节点
graphG6.value.updateItem(item, {
label: newNeState.neType,
style: {
fill: stateColor, // 填充色
stroke: stateColor, // 填充色
},
});
// 设置状态
graphG6.value.setItemState(item, 'stroke', newNeState.online);
}
}
// 设置边状态
for (const edge of edges) {
const edgeSource: string = edge.source;
const edgeTarget: string = edge.target;
const neS = nodes.find((n: any) => n.id === edgeSource);
const neT = nodes.find((n: any) => n.id === edgeTarget);
// console.log(neS, edgeSource, neT, edgeTarget);
if (neS && neT) {
// 通过 ID 查询节点实例
// const item = graphG6.value.findById(edge.id);
// console.log(
// `${edgeSource} - ${edgeTarget}`,
// neS.neState.online && neT.neState.online
// );
// const stateColor = neS.neState.online && neT.neState.online ? '#000000' : '#ff4d4f'; // 状态颜色
// 更新边
// graphG6.value.updateItem(item, {
// label: `${edgeSource} - ${edgeTarget}`,
// style: {
// stroke: stateColor, // 填充色
// },
// labelCfg: {
// style: {
// fill: '#ffffff', // 标签文本色
// },
// },
// });
// 设置状态
graphG6.value.setItemState(
edge.id,
'circle-move',
neS.neState.online && neT.neState.online
);
}
if (neS && notNeNodes.includes(edgeTarget)) {
graphG6.value.setItemState(edge.id, 'line-dash', neS.neState.online);
}
if (neT && notNeNodes.includes(edgeSource)) {
graphG6.value.setItemState(edge.id, 'line-dash', neT.neState.online);
}
}
// 请求标记复位
neStateRequestMap.value.set(neType, false);
}
/**属性复位 */
export function topologyReset() {
graphState.data = {
combos: [],
edges: [],
nodes: [],
};
graphG6.value = null;
graphNodeClickID.value = 'UPF_001';
neStateRequestMap.value = new Map();
}

View File

@@ -1,110 +0,0 @@
import { parseDateToStr } from '@/utils/date-utils';
import { parseSizeFromByte, parseSizeFromKbs } from '@/utils/parse-utils';
import { ref } from 'vue';
type FDType = {
/**时间 */
lineXTime: string[];
/**上行 N3 */
lineYUp: number[];
/**下行 N6 */
lineYDown: number[];
/**容量 */
cap: number;
};
/**UPF-流量数据 */
export const upfFlowData = ref<FDType>({
lineXTime: [],
lineYUp: [],
lineYDown: [],
cap: 0,
});
/**UPF-流量数据 数据解析 */
export function upfFlowParse(data: Record<string, string>) {
upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup'], 'HH:mm:ss'));
const upN3 = parseSizeFromKbs(+data['UPF.03'], 5);
upfFlowData.value.lineYUp.push(upN3[0]);
const downN6 = parseSizeFromKbs(+data['UPF.06'], 5);
upfFlowData.value.lineYDown.push(downN6[0]);
upfFlowData.value.cap += 1;
// 超过 25 弹出
if (upfFlowData.value.cap > 25) {
upfFlowData.value.lineXTime.shift();
upfFlowData.value.lineYUp.shift();
upfFlowData.value.lineYDown.shift();
upfFlowData.value.cap -= 1;
}
}
type TFType = {
/**上行 N3 */
up: number;
upFrom: string;
/**下行 N6 */
down: number;
downFrom: string;
/**请求标记 */
requestFlag: boolean;
};
/**UPF-总流量数 */
export const upfTotalFlow = ref<Record<string, TFType>>({
'0': {
up: 0,
upFrom: '0 B',
down: 0,
downFrom: '0 B',
requestFlag: false,
},
'7': {
up: 0,
upFrom: '0 B',
down: 0,
downFrom: '0 B',
requestFlag: false,
},
'30': {
up: 0,
upFrom: '0 B',
down: 0,
downFrom: '0 B',
requestFlag: false,
},
});
/**UPF-总流量数 数据解析 */
export function upfTFParse(day: string, data: Record<string, number>) {
let { up, down } = data;
upfTotalFlow.value[day] = {
up: up,
upFrom: parseSizeFromByte(up),
down: down,
downFrom: parseSizeFromByte(down),
requestFlag: false,
};
}
/**UPF-总流量数 选中 */
export const upfTFActive = ref<string>('0');
/**属性复位 */
export function upfTotalFlowReset() {
upfFlowData.value = {
lineXTime: [],
lineYUp: [],
lineYDown: [],
cap: 0,
};
for (const key of Object.keys(upfTotalFlow.value)) {
upfTotalFlow.value[key] = {
up: 0,
upFrom: '0 B',
down: 0,
downFrom: '0 B',
requestFlag: false,
};
}
upfTFActive.value = '0';
}

View File

@@ -1,144 +0,0 @@
import { ref } from 'vue';
/**ueEventAMFParse UE会话事件AMF 数据解析 */
function ueEventAMFParse(
item: Record<string, any>
): false | Record<string, any> {
let evData: Record<string, any> = item.eventJSON;
if (typeof evData === 'string') {
try {
evData = JSON.parse(evData);
} catch (error) {
console.error(error);
}
}
return {
eType: 'amf_ue',
eId: `amf_ue_${item.id}_${Date.now()}`,
eTime: +item.timestamp,
id: item.id,
type: item.eventType,
data: evData,
};
}
/**ueEventMMEParse UE会话事件MME 数据解析 */
function ueEventMMEParse(
item: Record<string, any>
): false | Record<string, any> {
let evData: Record<string, any> = item.eventJSON;
if (typeof evData === 'string') {
try {
evData = JSON.parse(evData);
} catch (error) {
console.error(error);
}
}
return {
eType: 'mme_ue',
eId: `mme_ue_${item.id}_${Date.now()}`,
eTime: +item.timestamp,
id: item.id,
type: item.eventType,
data: evData,
};
}
/**cdrEventIMSParse CDR会话事件IMS 数据解析 */
function cdrEventIMSParse(
item: Record<string, any>
): false | Record<string, any> {
let evData: Record<string, any> = item.cdrJSON || item.CDR;
if (typeof evData === 'string') {
try {
evData = JSON.parse(evData);
} catch (error) {
console.error(error);
return false;
}
}
// 指定显示CDR类型MOC/MTSM
if (!['MOC', 'MTSM'].includes(evData.recordType)) {
return false;
}
return {
eType: 'ims_cdr',
eId: `ims_cdr_${item.id}_${Date.now()}`,
eTime: +item.timestamp,
id: item.id,
data: evData,
};
}
/**eventListParse 事件列表解析 */
export function eventListParse(
type: 'ims_cdr' | 'amf_ue' | 'mme_ue',
data: any
) {
eventTotal.value += data.total;
for (const item of data.rows) {
let v: false | Record<string, any> = false;
if (type === 'ims_cdr') {
v = cdrEventIMSParse(item);
}
if (type === 'amf_ue') {
v = ueEventAMFParse(item);
}
if (type === 'mme_ue') {
v = ueEventMMEParse(item);
}
if (v) {
eventData.value.push(v);
}
}
// 激活选中
if (eventData.value.length > 0) {
eventId.value = eventData.value[0].eId;
}
}
/**eventItemParseAndPush 事件项解析并添加 */
export async function eventItemParseAndPush(
type: 'ims_cdr' | 'amf_ue' | 'mme_ue',
item: any
) {
let v: false | Record<string, any> = false;
if (type === 'ims_cdr') {
v = cdrEventIMSParse(item);
}
if (type === 'amf_ue') {
v = ueEventAMFParse(item);
}
if (type === 'mme_ue') {
v = ueEventMMEParse(item);
}
if (v) {
eventData.value.unshift(v);
eventTotal.value += 1;
eventId.value = v.eId;
await new Promise(resolve => setTimeout(resolve, 800));
if (eventData.value.length > 20) {
eventData.value.pop();
}
}
}
/**CDR+UE事件数据 */
export const eventData = ref<Record<string, any>[]>([]);
/**CDR+UE事件总量 */
export const eventTotal = ref<number>(0);
/**CDR/UE事件推送id */
export const eventId = ref<string>('');
/**属性复位 */
export function userActivityReset() {
eventData.value = [];
eventTotal.value = 0;
eventId.value = '';
}

View File

@@ -1,222 +0,0 @@
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,
} from './useUserActivity';
import {
upfTotalFlow,
upfTFParse,
upfFlowParse,
upfTotalFlowReset,
} from './useUPFTotalFlow';
import { topologyReset, neStateParse, neStateRequestMap } from './useTopology';
import PQueue from 'p-queue';
/**UPF-的Id */
export const upfWhoId = ref<any>('');
/**websocket连接 */
export default function useWS() {
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
/**发消息 */
function wsSend(data: Record<string, any>) {
ws.send(data);
}
/**接收数据后回调 */
function wsMessage(res: Record<string, any>) {
//console.log(res);
const { code, requestId, data } = res;
if (code === RESULT_CODE_ERROR) {
console.warn(res.msg);
return;
}
// 网元状态
if (requestId && requestId.startsWith('neState')) {
const neType = requestId.split('_')[1];
const neId = requestId.split('_')[2];
neStateParse(neType, data,neId);
return;
}
// 普通信息
switch (requestId) {
// AMF_UE会话事件
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`:
upfTFParse('0', data);
break;
case `upf_${upfWhoId.value}_7`:
upfTFParse('7', data);
break;
case `upf_${upfWhoId.value}_30`:
upfTFParse('30', data);
break;
}
// 订阅组信息
if (!data?.groupId) {
return;
}
switch (data.groupId) {
// kpiEvent 指标UPF
case `10_UPF_${upfWhoId.value}`:
if (data.data) {
upfFlowParse(data.data);
}
break;
// AMF_UE会话事件
case '1010_001':
if (data.data) {
queue.add(() => eventItemParseAndPush('amf_ue', data.data));
}
break;
// MME_UE会话事件
case '1011_001':
if (data.data) {
queue.add(() => eventItemParseAndPush('mme_ue', data.data));
}
break;
// IMS_CDR会话事件
case '1005_001':
if (data.data) {
queue.add(() => eventItemParseAndPush('ims_cdr', data.data));
}
break;
}
}
/**UPF-总流量数 发消息*/
function upfTFSend(day: '0' | '7' | '30') {
// 请求标记检查避免重复发送
if (upfTotalFlow.value[day].requestFlag) {
return;
}
upfTotalFlow.value[day].requestFlag = true;
ws.send({
requestId: `upf_${upfWhoId.value}_${day}`,
type: 'upf_tf',
data: {
neType: 'UPF',
neId: upfWhoId.value,
day: Number(day),
},
});
}
/**userActivitySend 用户行为事件基础列表数据 发消息*/
function userActivitySend() {
// AMF_UE会话事件
ws.send({
requestId: 'amf_1010_001',
type: 'amf_ue',
data: {
neType: 'AMF',
neId: '001',
sortField: 'timestamp',
sortOrder: 'desc',
pageNum: 1,
pageSize: 20,
},
});
// MME_UE会话事件
ws.send({
requestId: 'mme_1011_001',
type: 'mme_ue',
data: {
neType: 'MME',
neId: '001',
sortField: 'timestamp',
sortOrder: 'desc',
pageNum: 1,
pageSize: 20,
},
});
// IMS_CDR会话事件
ws.send({
requestId: 'ims_1005_001',
type: 'ims_cdr',
data: {
neType: 'IMS',
neId: '001',
recordType: 'MOC',
sortField: 'timestamp',
sortOrder: 'desc',
pageNum: 1,
pageSize: 20,
},
});
}
/**重新发送至UPF 10_UPF_neId */
function reSendUPF(neId: string) {
upfWhoId.value = neId;
//初始时时无需还原全部属性以及关闭
if (ws.state() === WebSocket.OPEN) {
ws.close();
// userActivityReset();
upfTotalFlowReset();
neStateRequestMap.value = new Map();
//topologyReset();
}
const options: OptionsType = {
url: '/ws',
params: {
/**订阅通道组
*
* 指标UPF (GroupID:10_neType_neId)
* AMF_UE会话事件(GroupID:1010_neId)
* MME_UE会话事件(GroupID:1011_neId)
* IMS_CDR会话事件(GroupID:1005_neId)
*/
subGroupID: `10_UPF_${neId},1010_001,1011_001,1005_001`,
},
onmessage: wsMessage,
onerror: (ev: any) => {
console.error(ev);
},
};
ws.connect(options);
}
onBeforeUnmount(() => {
ws.close();
userActivityReset();
upfTotalFlowReset();
topologyReset();
upfWhoId.value = '';
});
return {
wsSend,
userActivitySend,
upfTFSend,
reSendUPF,
};
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -1,876 +0,0 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import svgBase from '@/assets/svg/base.svg';
import svgUserIMS from '@/assets/svg/userIMS.svg';
import svgUserSMF from '@/assets/svg/userSMF.svg';
import useI18n from '@/hooks/useI18n';
import Topology from './components/Topology/index.vue';
import NeResources from './components/NeResources/index.vue';
import UserActivity from './components/UserActivity/index.vue';
import IMSActivity from './components/IMSActivity/index.vue';
import AlarnTypeBar from './components/AlarnTypeBar/index.vue';
import UPFFlow from './components/UPFFlow/index.vue';
import { listUDMSub } from '@/api/neData/udm_sub';
import { listSMFSubNum } from '@/api/neData/smf';
import { listIMSSessionNum } from '@/api/neData/ims';
import { listAMFNblist } from '@/api/neData/amf';
import { listMMENblist } from '@/api/neData/mme';
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 useNeListStore from '@/store/modules/ne_list';
import { message } from 'ant-design-vue';
import { upfWhoId } from './hooks/useWS';
import { listAMFNbStatelist } from '@/api/neData/amf';
import { listMMENbStatelist } from '@/api/neData/mme';
const neListStore = useNeListStore();
const router = useRouter();
const appStore = useAppStore();
const { t } = useI18n();
const { wsSend, userActivitySend, upfTFSend, reSendUPF } = useWS();
/**概览状态类型 */
type SkimStateType = {
/**UDM签约用户数量 */
udmSubNum: number;
/**SMF在线用户数 */
smfUeNum: number;
/**IMS在线用户数 */
imsUeNum: number;
/**5G基站数量 */
gnbNum: number;
/**5G在线用户数量 */
gnbUeNum: number;
/**4G基站数量 */
enbNum: number;
/**4G在线用户数量 */
enbUeNum: number;
/**5G用户总数量 */
gNbSumNum: number;
/**4G用户总数量 */
eNbSumNum: number;
};
/**概览状态信息 */
let skimState: SkimStateType = reactive({
udmSubNum: 0,
smfUeNum: 0,
imsUeNum: 0,
gnbNum: 0,
gnbUeNum: 0,
enbNum: 0,
enbUeNum: 0,
gNbSumNum: 0,
eNbSumNum: 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() {
let tempGnbSumNum = 0;
let tempEnbSumNum = 0;
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.data.total),
// },
// ],
[
'SMF',
{
request: (neId: string) => listSMFSubNum(neId),
process: (res: any) => {
if (
res.code === RESULT_CODE_SUCCESS &&
typeof res.data === 'number'
) {
skimState.smfUeNum += res.data;
}
},
},
],
[
'IMS',
{
request: (neId: string) => listIMSSessionNum(neId),
process: (res: any) => {
if (
res.code === RESULT_CODE_SUCCESS &&
typeof res.data === 'number'
) {
skimState.imsUeNum += res.data;
}
},
},
],
// [
// 'AMF',
// {
// request: (neId: string) => listAMFNblist({ neId }),
// process: (res: any) => {
// if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// skimState.gnbNum += res.data.length;
// skimState.gnbUeNum += res.data.reduce(
// (sum: number, item: any) => sum + item.ueNum,
// 0
// );
// }
// },
// },
// ],
[
'AMF',
{
request: (neId: string) => listAMFNblist({ neId }),
process: async (res: any, neId: any) => {
console.log(neId);
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
skimState.gnbNum += res.data.length;
skimState.gnbUeNum += res.data.reduce(
(sum: number, item: any) => sum + item.ueNum,
0
);
const amfNbRes = await listAMFNbStatelist({ neId });
console.log(amfNbRes);
if (
amfNbRes.code === RESULT_CODE_SUCCESS &&
Array.isArray(amfNbRes.data)
) {
// skimState.gNbSumNum += amfNbRes.data.length;
tempGnbSumNum += amfNbRes.data.length;
}
}
},
},
],
// [
// 'MME',
// {
// request: (neId: string) => listMMENblist({ neId }),
// process: (res: any) => {
// if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// skimState.enbNum += res.data.length;
// skimState.enbUeNum += res.data.reduce(
// (sum: number, item: any) => sum + item.ueNum,
// 0
// );
// }
// },
// },
// ],
[
'MME',
{
request: (neId: string) => listMMENblist({ neId }),
process: async (res: any, neId: any) => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
skimState.enbNum += res.data.length;
skimState.enbUeNum += res.data.reduce(
(sum: number, item: any) => sum + item.ueNum,
0
);
const mmeNbRes = await listMMENbStatelist({ neId });
console.log(mmeNbRes);
if (
mmeNbRes.code === RESULT_CODE_SUCCESS &&
Array.isArray(mmeNbRes.data)
) {
// skimState.eNbSumNum += mmeNbRes.data.length;
console.log(mmeNbRes);
tempEnbSumNum += mmeNbRes.data.length;
}
}
},
},
],
]);
console.log(neCascaderOptions);
const requests = neCascaderOptions.value.flatMap(
(ne: any) =>
ne.children
?.map((child: any) => {
console.log(child.neId);
const handler = neHandlers.get(child.neType);
return handler
? {
promise: handler.request(child.neId),
process: handler.process,
neId: child.neId, // 这里加上neId
}
: 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,
});
// results.forEach((result, index) => {
// if (result.status === 'fulfilled') {
// requests[index].process(result.value);
// } else {
// requests[index].process(0);
// }
// });
const processPromises = results.map((result: any, index: any) => {
const req = requests[index];
if (result.status === 'fulfilled') {
return req.process(result.value, req.neId);
} else {
return req.process(0, req.neId);
}
});
await Promise.all(processPromises);
skimState.gNbSumNum = tempGnbSumNum;
skimState.eNbSumNum = tempEnbSumNum;
// UDM
listUDMSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
skimState.udmSubNum = res.data.total;
}
});
}
/**初始数据函数 */
function loadData() {
fnGetNeState(); // 获取网元状态
userActivitySend();
upfTFSend('0');
upfTFSend('7');
upfTFSend('30');
clearInterval(interval10s.value);
interval10s.value = setInterval(() => {
if (!interval10s.value || !initFlag) 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();
}
// UPF实时流量下拉菜单选择
function fnSelectUPF(e: any) {
upfWhoId.value = e.key;
reSendUPF(e.key);
for (var key in upfTotalFlow.value) {
upfTotalFlow.value[key].requestFlag = false;
}
}
let udmNeId = ref<string>('001');
let udmOtions = ref<Record<string, any>[]>([]);
let onlineOtions = ref<Record<string, any>[]>([]);
/**用户数量-选择UDM */
async function fnSelectUDM(e: any) {
udmNeId.value = e.key;
try {
const res = await listUDMSub({
neId: udmNeId.value,
pageNum: 1,
pageSize: 1,
});
console.log(res);
// listUDMSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
if (
res.code === RESULT_CODE_SUCCESS &&
typeof res.data.total === 'number'
) {
skimState.udmSubNum = res.data.total;
console.log(res);
} else {
skimState.udmSubNum = 0;
}
// }).catch(() => {
// skimState.udmSubNum = 0;
// });
} catch (error) {
skimState.udmSubNum = 0;
}
}
/**资源控制-选择NE */
function fnSelectNeRe(e: any) {
console.log(e);
graphNodeClickID.value = e.key;
}
//
// 定义一个方法返回 views 容器
const getPopupContainer = () => {
// 使用 ref 或其他方式来引用你的 views 容器
// 如果 views 容器直接在这个组件内部,你可以使用 ref
// 但在这个例子中,我们假设它是通过类名来获取的
return document.querySelector('.viewport');
};
onMounted(() => {
// 获取网元网元列表
neListStore.neCascaderOptions.forEach(item => {
console.log(item);
if (item.value === 'UPF') {
neOtions.value = JSON.parse(JSON.stringify(item.children));
}
if (item.value === 'UDM') {
udmOtions.value = JSON.parse(JSON.stringify(item.children));
}
});
if (neOtions.value.length > 0) {
fnSelectNe(neOtions.value[0].value, neOtions.value[0]);
}
if (udmOtions.value.length > 0) {
fnSelectUDM({ key: udmOtions.value[0].value });
}
// if (onlineArr.length > 0) {
// fnSelectNeRe({ key: onlineArr[0].value });
// }
// 过滤不可用的网元
neCascaderOptions.value = neListStore.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;
}
//online Ne
let onlineArr: Record<string, any>[] = [];
// UDM
neListStore.neList.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,
});
}
});
onlineOtions.value = onlineArr;
initFlag = true;
fnGetSkim().then(() => {
loadData();
});
});
onBeforeUnmount(() => {
clearInterval(interval10s.value);
interval10s.value = null;
clearInterval(interval5s.value);
interval5s.value = null;
initFlag = false;
});
</script>
<template>
<div class="viewport" ref="viewportDom">
<div class="brand">
<div
class="brand-title"
@click="toggle"
:title="t('views.dashboard.overview.fullscreen')"
>
{{ t('views.dashboard.overview.title') }}
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</div>
<div class="brand-desc">{{ appStore.appName }}</div>
</div>
<div class="column">
<div class="skim panel">
<div class="inner">
<h3 class="leftright">
<span class="title">
<IdcardOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.skim.userTitle') }}
</span>
</h3>
<div class="data">
<div
class="item toRouter"
:title="t('views.dashboard.overview.toRouter')"
v-if="neListStore.fnHasNe(['udm'])"
>
<div @click="fnToRouter('UdmSub_2001')">
<UserOutlined
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
/>
{{ skimState.udmSubNum || 0 }}
</div>
<span>
<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('ImsSub_2004')"
:title="t('views.dashboard.overview.toRouter')"
style="margin: 0 12px"
v-perms:has="['dashboard:overview:imsUeNum']"
v-if="neListStore.fnHasNe(['ims'])"
>
<div>
<img :src="svgUserIMS" style="width: 18px; margin-right: 8px" />
{{ skimState.imsUeNum || 0 }}
</div>
<span>
{{ t('views.dashboard.overview.skim.imsUeNum') }}
</span>
</div>
<div
class="item toRouter"
@click="fnToRouter('SmfSub_2005')"
:title="t('views.dashboard.overview.toRouter')"
v-perms:has="['dashboard:overview:smfUeNum']"
v-if="neListStore.fnHasNe(['smf'])"
>
<div>
<img :src="svgUserSMF" style="width: 18px; margin-right: 8px" />
{{ skimState.smfUeNum || 0 }}
</div>
<span>
{{ t('views.dashboard.overview.skim.smfUeNum') }}
</span>
</div>
</div>
</div>
</div>
<!--告警统计-->
<div class="alarmType panel">
<div class="inner">
<h3
class="toRouter leftright"
:title="t('views.dashboard.overview.toRouter')"
>
<span class="title" @click="fnToRouter('HistoryAlarm_2097')">
<PieChartOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.alarmTypeBar.alarmSum') }}
</span>
</h3>
<div class="chart">
<AlarnTypeBar />
</div>
</div>
</div>
<!-- 用户行为 -->
<div class="userActivity panel">
<div class="inner">
<h3 class="leftright">
<span class="title">
<WhatsAppOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.userActivity.title') }}
</span>
</h3>
<div class="chart">
<UserActivity />
</div>
</div>
</div>
</div>
<div class="column" style="flex: 4; margin: 1.333rem 0.833rem 0">
<!-- 实时流量 -->
<div class="upfFlow panel">
<div class="inner">
<h3 class="centerStyle">
<span class="title">
<div
class="toRouter"
@click="fnToRouter('GoldTarget_2104')"
:title="t('views.dashboard.overview.toRouter')"
>
<AreaChartOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.upfFlow.title') }}
</div>
&nbsp;&nbsp;&nbsp;&nbsp;
<a-dropdown
:trigger="['click']"
:get-Popup-Container="getPopupContainer"
>
<div class="toDeep-text">
{{
neOtions.find(item => item.value === upfWhoId)?.label ||
'Select UPF'
}}
<DownOutlined style="margin-left: -2px; font-size: 12px" />
</div>
<template #overlay>
<a-menu @click="fnSelectUPF">
<a-menu-item v-for="v in neOtions" :key="v.value">
{{ v.label }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</span>
</h3>
<div class="chart">
<UPFFlow />
</div>
</div>
</div>
<!-- 网络拓扑 -->
<div class="topology panel">
<div class="inner">
<h3
class="toRouter centerStyle"
@click="fnToRouter('TopologyArchitecture_2128')"
:title="t('views.dashboard.overview.toRouter')"
>
<span class="title">
<ApartmentOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.topology.title') }}
</span>
</h3>
<div class="chart">
<Topology />
</div>
</div>
</div>
</div>
<div class="column">
<!-- 基站信息 -->
<div
class="skim panel base"
v-perms:has="['dashboard:overview:gnbBase']"
v-if="neListStore.fnHasNe(['amf'])"
>
<div class="inner">
<h3 class="leftright">
<span class="title">
<GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.skim.nodeBInfo') }}
</span>
</h3>
<div class="data" style="margin-top: 20px">
<div
class="item toRouter"
@click="fnToRouter('BaseStation_2007', { neType: 'AMF' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start">
<img
:src="svgBase"
style="width: 18px; margin-right: 8px; height: 2rem"
/>
{{ skimState.gNbSumNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.gnbSumBase') }}</span>
</div>
<div
class="item toRouter"
@click="fnToRouter('BaseStation_2007', { neType: 'AMF' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start">
<img
:src="svgBase"
style="width: 18px; margin-right: 8px; height: 2rem"
/>
{{ skimState.gnbNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.gnbBase') }}</span>
</div>
<div
class="item toRouter"
@click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start">
<UserOutlined
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
/>
{{ skimState.gnbUeNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.gnbUeNum') }}</span>
</div>
</div>
</div>
</div>
<div
class="skim panel base"
v-perms:has="['dashboard:overview:enbBase']"
v-if="neListStore.fnHasNe(['mme'])"
>
<div class="inner">
<h3></h3>
<div class="data" style="margin-top: 40px">
<div
class="item toRouter"
@click="fnToRouter('BaseStation_2007', { neType: 'MME' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start">
<img
:src="svgBase"
style="width: 18px; margin-right: 8px; height: 2rem"
/>
{{ skimState.eNbSumNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.enbSumBase') }}</span>
</div>
<div
class="item toRouter"
@click="fnToRouter('BaseStation_2007', { neType: 'MME' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start">
<img
:src="svgBase"
style="width: 18px; margin-right: 8px; height: 2rem"
/>
{{ skimState.enbNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.enbBase') }}</span>
</div>
<div
class="item toRouter"
@click="fnToRouter('BaseStation_2007', { neType: 'MME' })"
:title="t('views.dashboard.overview.toRouter')"
>
<div style="align-items: flex-start">
<UserOutlined
style="color: #4096ff; margin-right: 8px; font-size: 1.1rem"
/>
{{ skimState.enbUeNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.enbUeNum') }}</span>
</div>
</div>
</div>
</div>
<!-- 资源情况 -->
<div class="resources panel">
<div class="inner">
<h3 class="resources leftright">
<span class="title">
<DashboardOutlined
style="color: #68d8fe; font-size: 20px"
/>&nbsp;&nbsp;
<div style="margin-left: -3px">
{{ t('views.dashboard.overview.resources.title') }}
</div>
<a-dropdown
:trigger="['click']"
:get-Popup-Container="getPopupContainer"
>
<div class="toDeep-text">
{{ graphNodeClickID }}
<DownOutlined style="margin-left: -2px; 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>
</span>
</h3>
<div class="chart">
<NeResources />
</div>
</div>
</div>
<!-- IMS用户行为 -->
<div class="userActivity panel">
<div class="inner">
<h3 class="leftright">
<span class="title">
<WhatsAppOutlined
style="color: #68d8fe; font-size: 20px"
/>&nbsp;&nbsp;
{{ t('views.dashboard.overview.userActivity.imsTitle') }}
</span>
</h3>
<div class="chart">
<IMSActivity />
</div>
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
@import url('./css/index.css');
.toDeep {
--editor-background-color: blue;
}
.toDeep :deep(.ant-select-selector) {
background-color: #050f23;
border: none;
}
.toDeep :deep(.ant-select-arrow) {
color: #4c9bfd;
}
.toDeep :deep(.ant-select-selection-item) {
color: #4c9bfd;
}
.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>

View File

@@ -5,7 +5,7 @@ import { Modal, message } from 'ant-design-vue/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/es/table';
import useNeListStore from '@/store/modules/ne_list';
import useNeStore from '@/store/modules/ne';
import useI18n from '@/hooks/useI18n';
import {
RESULT_CODE_ERROR,
@@ -23,7 +23,7 @@ import saveAs from 'file-saver';
import { useClipboard } from '@vueuse/core';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n();
const neListStore = useNeListStore();
const neListStore = useNeStore();
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
@@ -111,12 +111,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.chargingID'), // 计费ID
dataIndex: 'cdrJSON',
@@ -260,11 +260,14 @@ function fnTableSelectedRowKeys(keys: (string | number)[]) {
type ModalStateType = {
/**确定按钮 loading */
confirmLoading: boolean;
/**最大ID值 */
maxId: number;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
confirmLoading: false,
maxId: 0,
});
/**
@@ -366,6 +369,11 @@ function fnGetList(pageNum?: number) {
return item;
});
// 取最大值ID用作实时累加
if (total > 0) {
modalState.maxId = Number(rows[0].id);
}
}
tableState.loading = false;
});
@@ -378,11 +386,9 @@ function fnExportList() {
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.pageSize = 10000;
exportSGWCDataCDR(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
@@ -454,18 +460,15 @@ function wsMessage(res: Record<string, any>) {
// cdrEvent CDR会话事件
if (data.groupId === `1008_${queryParams.neId}`) {
const cdrEvent = data.data;
let cdrJSON = {};
try {
cdrJSON = JSON.parse(cdrEvent.cdrJSON);
} catch (e) {}
queue.add(async () => {
modalState.maxId += 1;
tableState.data.unshift({
id: cdrEvent.id,
id: modalState.maxId,
neType: cdrEvent.neType,
neName: cdrEvent.neName,
rmUID: cdrEvent.rmUID,
timestamp: cdrEvent.timestamp,
cdrJSON: cdrJSON,
cdrJSON: cdrEvent.CDR,
});
tablePagination.total += 1;
if (tableState.data.length > 100) {

View File

@@ -5,7 +5,7 @@ import { Modal, message } from 'ant-design-vue/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/es/table';
import useNeListStore from '@/store/modules/ne_list';
import useNeStore from '@/store/modules/ne';
import useI18n from '@/hooks/useI18n';
import {
RESULT_CODE_ERROR,
@@ -20,11 +20,10 @@ import { OptionsType, WS } from '@/plugins/ws-websocket';
import PQueue from 'p-queue';
import saveAs from 'file-saver';
import dayjs, { type Dayjs } from 'dayjs';
import { parseSizeFromByte } from '@/utils/parse-utils';
import { useClipboard } from '@vueuse/core';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n();
const neListStore = useNeListStore();
const neListStore = useNeStore();
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
@@ -108,12 +107,12 @@ let tableState: TabeStateType = reactive({
/**表格字段列 */
let tableColumns = ref<ColumnsType>([
// {
// title: t('common.rowId'),
// dataIndex: 'id',
// align: 'center',
// width: 100,
// },
{
title: t('common.rowId'),
dataIndex: 'id',
align: 'center',
width: 100,
},
{
title: t('views.dashboard.cdr.chargingID'), // 计费ID
dataIndex: 'cdrJSON',
@@ -166,7 +165,7 @@ let tableColumns = ref<ColumnsType>([
}
}
}
return parseSizeFromByte(dataVolumeUplink);
return dataVolumeUplink;
},
},
{
@@ -191,7 +190,7 @@ let tableColumns = ref<ColumnsType>([
}
}
}
return parseSizeFromByte(dataVolumeDownlink);
return dataVolumeDownlink;
},
},
{
@@ -216,7 +215,7 @@ let tableColumns = ref<ColumnsType>([
}
}
}
return parseSizeFromByte(dataTotalVolume);
return dataTotalVolume;
},
},
{
@@ -288,11 +287,14 @@ function fnTableSelectedRowKeys(keys: (string | number)[]) {
type ModalStateType = {
/**确定按钮 loading */
confirmLoading: boolean;
/**最大ID值 */
maxId: number;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
confirmLoading: false,
maxId: 0,
});
/**
@@ -394,6 +396,11 @@ function fnGetList(pageNum?: number) {
return item;
});
// 取最大值ID用作实时累加
if (total > 0) {
modalState.maxId = Number(rows[0].id);
}
}
tableState.loading = false;
});
@@ -406,11 +413,9 @@ function fnExportList() {
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.pageSize = 10000;
exportSMFDataCDR(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
@@ -482,18 +487,15 @@ function wsMessage(res: Record<string, any>) {
// cdrEvent CDR会话事件
if (data.groupId === `1006_${queryParams.neId}`) {
const cdrEvent = data.data;
let cdrJSON = {};
try {
cdrJSON = JSON.parse(cdrEvent.cdrJSON);
} catch (e) {}
queue.add(async () => {
modalState.maxId += 1;
tableState.data.unshift({
id: cdrEvent.id,
id: modalState.maxId,
neType: cdrEvent.neType,
neName: cdrEvent.neName,
rmUID: cdrEvent.rmUID,
timestamp: cdrEvent.timestamp,
cdrJSON: cdrJSON,
cdrJSON: cdrEvent.CDR,
});
tablePagination.total += 1;
if (tableState.data.length > 100) {
@@ -813,21 +815,15 @@ onBeforeUnmount(() => {
<div>
<div>
<span>Data Total Volume: </span>
<span>
{{ parseSizeFromByte(udata.dataTotalVolume || 0) }}
</span>
<span>{{ udata.dataTotalVolume }}</span>
</div>
<div>
<span>Data Volume Downlink: </span>
<span>
{{ parseSizeFromByte(udata.dataVolumeDownlink || 0) }}
</span>
<span>{{ udata.dataVolumeDownlink }}</span>
</div>
<div>
<span>Data Volume Uplink: </span>
<span>
{{ parseSizeFromByte(udata.dataVolumeUplink || 0) }}
</span>
<span>{{ udata.dataVolumeUplink }}</span>
</div>
<!-- <div>
<span>Time: </span>

View File

@@ -35,11 +35,10 @@ import {
} from '@/constants/result-constants';
import { parseSizeFromByte } from '@/utils/parse-utils';
import { message } from 'ant-design-vue';
import useNeListStore from '@/store/modules/ne_list';
import useNeStore from '@/store/modules/ne';
import dayjs, { Dayjs } from 'dayjs';
import { parseDateToStr } from '@/utils/date-utils';
const { t, currentLocale } = useI18n();
const neListStore = useNeListStore();
const neListStore = useNeStore();
const ws = new WS();
/**图DOM节点实例对象 */
@@ -49,7 +48,7 @@ let cdrChart: echarts.ECharts | null = null;
/**图表配置 */
const option = {
title: {
text: t('views.dashboard.chart.charttitle'),
text: 'Data Usage Report',
left: 'left',
},
tooltip: {
@@ -149,12 +148,12 @@ const option = {
],
yAxis: [
{
name: t('views.dashboard.chart.downlink'),
name: 'Downlink (Byte)',
type: 'value',
},
{
gridIndex: 1,
name: t('views.dashboard.chart.uplink'),
name: 'Uplink (Byte)',
type: 'value',
inverse: true,
},
@@ -382,7 +381,7 @@ function fnRanderChartDataLoad() {
break;
}
// 时间
const dataTime = parseDateToStr(item.cdrJSON.invocationTimestamp);
const dataTime = item.cdrJSON.invocationTimestamp;
const listOfMultipleUnitUsage = item.cdrJSON.listOfMultipleUnitUsage;
if (
!Array.isArray(listOfMultipleUnitUsage) ||
@@ -505,10 +504,6 @@ function fnRealTime() {
// cdrEvent CDR会话事件
if (data.groupId === `1006_${queryParams.neId}`) {
const cdrEvent = data.data;
let cdrJSON: any = {};
try {
cdrJSON = JSON.parse(cdrEvent.cdrJSON);
} catch (e) {}
// 对应结束时间内
if (queryParams.endTime) {
const endTime = Math.round(queryParams.endTime / 1000);
@@ -516,6 +511,7 @@ function fnRealTime() {
return;
}
}
const cdrJSON = cdrEvent.CDR;
if (!cdrJSON.invocationTimestamp) {
return;
}
@@ -687,11 +683,11 @@ onBeforeUnmount(() => {
<!-- 图数据 -->
<div ref="cdrChartDom" style="height: 600px; width: 100%"></div>
<a-descriptions :title="t('views.dashboard.chart.datausage')" bordered :column="2">
<a-descriptions-item :label="t('views.dashboard.chart.totaluplink')">
<a-descriptions title="Data Usage" bordered :column="2">
<a-descriptions-item label="Total Uplink">
{{ state.dataUsage[0] }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.dashboard.chart.totaldownlink')">
<a-descriptions-item label="Total Downlink">
{{ state.dataUsage[1] }}
</a-descriptions-item>
</a-descriptions>

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