Merge branch 'main-v2' into lite
@@ -1,4 +1,5 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { sessionGet } from '@/utils/cache-session-utils';
|
||||
|
||||
/**
|
||||
* 登录方法
|
||||
@@ -87,3 +88,69 @@ 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,
|
||||
});
|
||||
}
|
||||
|
||||
75
src/api/cbc/cbe.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* CBC列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export function listCBC(query: Record<string, any>) {
|
||||
return request({
|
||||
url: `/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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export async function getActiveAlarmTotal() {
|
||||
url: `/neData/alarm/list`,
|
||||
method: 'GET',
|
||||
params: {
|
||||
alarmStatus: '1',
|
||||
alarmStatus: 'Active',
|
||||
sortField: 'event_time',
|
||||
sortOrder: 'desc',
|
||||
pageNum: 1,
|
||||
@@ -194,33 +194,13 @@ export async function exportAll(query: Record<string, any>) {
|
||||
* @returns bolb
|
||||
*/
|
||||
export async function origGet() {
|
||||
let totalSQL = `select count(*) as value,orig_severity as name from alarm 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`,
|
||||
return await request({
|
||||
url: `/neData/alarm/count/severity`,
|
||||
method: 'GET',
|
||||
params: {
|
||||
SQL: totalSQL,
|
||||
alarmStatus: 'Active',
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,34 +208,13 @@ export async function origGet() {
|
||||
* @param filterFlag 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
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`,
|
||||
export async function top3Sel() {
|
||||
return await request({
|
||||
url: `/neData/alarm/count/ne`,
|
||||
method: 'GET',
|
||||
params: {
|
||||
SQL: top3SQL,
|
||||
alarmStatus: 'Active',
|
||||
top: 3,
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
|
||||
import { sessionGet } from '@/utils/cache-session-utils';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 获取下拉框数据
|
||||
* @returns object
|
||||
*/
|
||||
export function getBakFile() {
|
||||
return request({
|
||||
url: '/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,
|
||||
});
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
|
||||
/**
|
||||
* 查询日志列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function 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;
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
|
||||
/**
|
||||
* 查询日志列表
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function 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;
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
|
||||
/**
|
||||
* 查询网元可用cmd命令
|
||||
@@ -8,22 +6,17 @@ import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
* @returns object
|
||||
*/
|
||||
export async function getMMLByNE(neType: string) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/mml_system`,
|
||||
return request({
|
||||
url: '/tool/mml/system/list',
|
||||
method: 'GET',
|
||||
params: {
|
||||
SQL: `select * from mml_system where ne_type = '${neType}' and status = 'Active'`,
|
||||
neType: neType,
|
||||
status: 'Active',
|
||||
pageNum: 1,
|
||||
pageSize: 1000,
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,16 +33,15 @@ export async function sendMMlByNE(
|
||||
objectType: string,
|
||||
cmdArr: string[]
|
||||
) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/operationManagement/v1/elementType/${neType}/objectType/${objectType}?ne_id=${neId}`,
|
||||
return request({
|
||||
url: '/tool/mml/command',
|
||||
method: 'POST',
|
||||
data: { mml: cmdArr },
|
||||
data: {
|
||||
neType: neType,
|
||||
neId: neId,
|
||||
type: objectType,
|
||||
command: cmdArr,
|
||||
},
|
||||
timeout: 180_000,
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
result.data = result.data.data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,28 +1,21 @@
|
||||
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() {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/databaseManagement/v1/elementType/omc_db/objectType/mml_subscriber`,
|
||||
return request({
|
||||
url: '/tool/mml/subscriber/list',
|
||||
method: 'GET',
|
||||
params: {
|
||||
SQL: `select * from mml_subscriber where ne_type = 'UDM' and status = 'Active'`,
|
||||
neType: 'UDM',
|
||||
status: 'Active',
|
||||
pageNum: 1,
|
||||
pageSize: 1000,
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,16 +25,15 @@ export async function getMMLByUDM() {
|
||||
* @returns
|
||||
*/
|
||||
export async function sendMMlByUDM(neId: string, cmdArr: string[]) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/api/rest/operationManagement/v1/elementType/UDM/objectType/mml?ne_id=${neId}`,
|
||||
return request({
|
||||
url: '/tool/mml/command',
|
||||
method: 'POST',
|
||||
data: { mml: cmdArr },
|
||||
data: {
|
||||
neType: 'UDM',
|
||||
neId: neId,
|
||||
type: 'General',
|
||||
command: cmdArr,
|
||||
},
|
||||
timeout: 180_000,
|
||||
});
|
||||
// 解析数据
|
||||
if (result.code === RESULT_CODE_SUCCESS && Array.isArray(result.data.data)) {
|
||||
result.data = result.data.data;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
@@ -29,14 +29,14 @@ export function delAMFDataUE(ueIds: string | number) {
|
||||
|
||||
/**
|
||||
* AMF-UE会话列表导出
|
||||
* @param data 查询列表条件
|
||||
* @param query 查询列表条件
|
||||
* @returns object
|
||||
*/
|
||||
export function exportAMFDataUE(data: Record<string, any>) {
|
||||
export function exportAMFDataUE(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/amf/ue/export',
|
||||
method: 'POST',
|
||||
data,
|
||||
method: 'GET',
|
||||
params: query,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
|
||||
@@ -29,14 +29,14 @@ export function delIMSDataCDR(cdrIds: string | number) {
|
||||
|
||||
/**
|
||||
* IMS-CDR会话列表导出
|
||||
* @param data 查询列表条件
|
||||
* @param query 查询列表条件
|
||||
* @returns object
|
||||
*/
|
||||
export function exportIMSDataCDR(data: Record<string, any>) {
|
||||
export function exportIMSDataCDR(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/ims/cdr/export',
|
||||
method: 'POST',
|
||||
data,
|
||||
method: 'GET',
|
||||
params: query,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
|
||||
@@ -29,14 +29,14 @@ export function delMMEDataUE(ueIds: string | number) {
|
||||
|
||||
/**
|
||||
* MME-UE会话列表导出
|
||||
* @param data 查询列表条件
|
||||
* @param query 查询列表条件
|
||||
* @returns object
|
||||
*/
|
||||
export function exportMMEDataUE(data: Record<string, any>) {
|
||||
export function exportMMEDataUE(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/mme/ue/export',
|
||||
method: 'POST',
|
||||
data,
|
||||
method: 'GET',
|
||||
params: query,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
|
||||
@@ -16,14 +16,14 @@ export function listNBState(query: Record<string, any>) {
|
||||
|
||||
/**
|
||||
* 历史记录列表导出
|
||||
* @param data 查询列表条件
|
||||
* @param query 查询列表条件
|
||||
* @returns object
|
||||
*/
|
||||
export function exportNBState(data: Record<string, any>) {
|
||||
export function exportNBState(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/nb-state/export',
|
||||
method: 'POST',
|
||||
data,
|
||||
method: 'GET',
|
||||
params: query,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
|
||||
@@ -29,14 +29,14 @@ export function delSGWCDataCDR(cdrIds: string | number) {
|
||||
|
||||
/**
|
||||
* SGWC-CDR会话列表导出
|
||||
* @param data 查询列表条件
|
||||
* @param query 查询列表条件
|
||||
* @returns object
|
||||
*/
|
||||
export function exportSGWCDataCDR(data: Record<string, any>) {
|
||||
export function exportSGWCDataCDR(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/sgwc/cdr/export',
|
||||
method: 'POST',
|
||||
data,
|
||||
method: 'GET',
|
||||
params: query,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
|
||||
@@ -29,14 +29,14 @@ export function delSMFDataCDR(cdrIds: string | number) {
|
||||
|
||||
/**
|
||||
* SMF-CDR会话列表导出
|
||||
* @param data 查询列表条件
|
||||
* @param query 查询列表条件
|
||||
* @returns object
|
||||
*/
|
||||
export function exportSMFDataCDR(data: Record<string, any>) {
|
||||
export function exportSMFDataCDR(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/smf/cdr/export',
|
||||
method: 'POST',
|
||||
data,
|
||||
method: 'GET',
|
||||
params: query,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
|
||||
@@ -29,14 +29,14 @@ export function delSMSCDataCDR(cdrIds: string | number) {
|
||||
|
||||
/**
|
||||
* SMSC-CDR会话列表导出
|
||||
* @param data 查询列表条件
|
||||
* @param query 查询列表条件
|
||||
* @returns object
|
||||
*/
|
||||
export function exportSMSCDataCDR(data: Record<string, any>) {
|
||||
export function exportSMSCDataCDR(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/smsc/cdr/export',
|
||||
method: 'POST',
|
||||
data,
|
||||
method: 'GET',
|
||||
params: query,
|
||||
responseType: 'blob',
|
||||
timeout: 60_000,
|
||||
});
|
||||
|
||||
@@ -3,36 +3,14 @@ import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
* @param query 查询参数
|
||||
* N3IWF-在线订阅用户列表信息
|
||||
* @param query 查询参数 {imsi}
|
||||
* @returns object
|
||||
*/
|
||||
export async function listN3iwf(query: Record<string, any>) {
|
||||
const result = await request({
|
||||
url: '/api/rest/ueManagement/v1/elementType/n3iwf/objectType/ueInfo',
|
||||
export function listN3IWFSubList(query: Record<string, any>) {
|
||||
return request({
|
||||
url: '/neData/n3iwf/sub/list',
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -3,24 +3,24 @@ import { request } from '@/plugins/http-fetch';
|
||||
import { parseObjLineToHump } from '@/utils/parse-utils';
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
* NSSF-在线订阅用户列表信息
|
||||
* @param query 查询参数 {imsi}
|
||||
* @returns object
|
||||
*/
|
||||
export async function listNSSF() {
|
||||
const result = await request({
|
||||
url: '/api/rest/ueManagement/v1/elementType/nssf/objectType/subscriptions',
|
||||
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',
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { request } from '@/plugins/http-fetch';
|
||||
export async function listCustomData(query: Record<string, any>) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/pm/kpiC/report`,
|
||||
url: `/neData/kpic/data`,
|
||||
method: 'GET',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
|
||||
@@ -1,30 +1,16 @@
|
||||
import { request } from '@/plugins/http-fetch';
|
||||
|
||||
/**
|
||||
* 新 查询自定义指标
|
||||
* 查询自定义指标
|
||||
* @param query 查询参数
|
||||
* @returns object
|
||||
*/
|
||||
export async function listCustom(query?: Record<string, any>) {
|
||||
// 发起请求
|
||||
const result = await request({
|
||||
url: `/pm/kpiC/title/totalList`,
|
||||
return await request({
|
||||
url: `/neData/kpic/title/list`,
|
||||
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',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,7 +20,7 @@ export async function getCustom(id: string | number) {
|
||||
*/
|
||||
export function addCustom(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/pm/kpiC/title`,
|
||||
url: `/neData/kpic/title`,
|
||||
method: 'POST',
|
||||
data: data,
|
||||
});
|
||||
@@ -47,7 +33,7 @@ export function addCustom(data: Record<string, any>) {
|
||||
*/
|
||||
export function updateCustom(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/pm/kpiC/title/${data.id}`,
|
||||
url: `/neData/kpic/title`,
|
||||
method: 'PUT',
|
||||
data: data,
|
||||
});
|
||||
@@ -59,7 +45,7 @@ export function updateCustom(data: Record<string, any>) {
|
||||
*/
|
||||
export async function delCustom(data: Record<string, any>) {
|
||||
return request({
|
||||
url: `/pm/kpiC/title/${data.id}`,
|
||||
url: `/neData/kpic/title?id=${data.id}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
64
src/api/system/login-source.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
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',
|
||||
});
|
||||
}
|
||||
25
src/api/tool/mml.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
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,
|
||||
});
|
||||
}
|
||||
@@ -261,12 +261,16 @@ export default {
|
||||
login: {
|
||||
tabPane1: 'Account password login',
|
||||
tabPane2: 'Login with phone number',
|
||||
registerBtn: 'Register an account',
|
||||
registerBtn: 'Register Account',
|
||||
loginBtn: 'Sign In',
|
||||
loginSuccess: 'Login Successful',
|
||||
loginMethod: 'Other login methods:',
|
||||
loginMethodWX: 'WeChat Scan Login',
|
||||
loginMethodQQ: 'QQ Scan Code Login',
|
||||
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',
|
||||
},
|
||||
register: {
|
||||
registerBtn: 'Register',
|
||||
@@ -357,6 +361,54 @@ 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",
|
||||
@@ -368,10 +420,13 @@ 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",
|
||||
@@ -401,6 +456,7 @@ export default {
|
||||
},
|
||||
userActivity: {
|
||||
title: "User Activity",
|
||||
imsTitle: "IMS Activity",
|
||||
type: "Type",
|
||||
duration: "Duration",
|
||||
caller: "Caller",
|
||||
@@ -443,6 +499,14 @@ 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",
|
||||
@@ -675,7 +739,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',
|
||||
@@ -1053,6 +1117,21 @@ 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: {
|
||||
@@ -1637,11 +1716,12 @@ export default {
|
||||
userName: 'Nick Name',
|
||||
permission: 'Role',
|
||||
className: 'Department',
|
||||
userType: 'User Type',
|
||||
loginIp: 'Login Address',
|
||||
loginTime: 'Login Time',
|
||||
status: 'Status',
|
||||
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',
|
||||
userNameTip:'Please enter the correct username format no less than 4 digits',
|
||||
passwdTip:'Please enter the correct password format no less than 6 digits',
|
||||
nickNameTip:'Nicknames no less than 2 digits',
|
||||
emailTip:'Please enter the correct email address',
|
||||
phoneTip:'Please enter the correct phone number',
|
||||
@@ -1699,6 +1779,55 @@ 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',
|
||||
|
||||
@@ -264,9 +264,13 @@ export default {
|
||||
registerBtn: '注册账号',
|
||||
loginBtn: '登录',
|
||||
loginSuccess: '登录成功',
|
||||
loginMethod: '其他登录方式:',
|
||||
loginMethodWX: '微信扫一扫登录',
|
||||
loginMethodQQ: 'QQ扫码登录',
|
||||
otherMethod: '其他方式',
|
||||
backBtn: '返回',
|
||||
backBtnLogin: '返回登录',
|
||||
authorizedNotfound: '授权无效',
|
||||
authorizedFailed: '授权失败',
|
||||
authorizedSuccess: '授权成功',
|
||||
redirectHome: '{i} 秒后跳转主页',
|
||||
},
|
||||
register: {
|
||||
registerBtn: '注册',
|
||||
@@ -357,6 +361,54 @@ 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: "核心网系统看板",
|
||||
@@ -367,11 +419,14 @@ export default {
|
||||
userTitle:'用户信息',
|
||||
imsUeNum: "IMS 会话数",
|
||||
smfUeNum: "Data 会话数",
|
||||
gnbBase: "5G 基站数",
|
||||
gnbBase: "5G 基站在线数",
|
||||
gnbSumBase: "5G 基站总数",
|
||||
gnbUeNum:'5G 用户数',
|
||||
enbBase: "4G 基站数",
|
||||
enbBase: "4G 基站在线数",
|
||||
enbSumBase: "4G 基站总数",
|
||||
enbUeNum:'4G 用户数',
|
||||
baseTitle:'在线信息',
|
||||
nodeBInfo: '基站信息',
|
||||
},
|
||||
upfFlow:{
|
||||
title: "用户面吞吐量",
|
||||
@@ -401,6 +456,7 @@ export default {
|
||||
},
|
||||
userActivity: {
|
||||
title: "用户活动",
|
||||
imsTitle: "IMS 活动",
|
||||
type: "类型",
|
||||
duration: "时长",
|
||||
caller: "主叫",
|
||||
@@ -443,6 +499,14 @@ export default {
|
||||
sgwcVolumeGPRSUplink: 'GPRS 上行链路',
|
||||
sgwcVolumeGPRSDownlink: 'GPRS 下行链路',
|
||||
},
|
||||
chart:{
|
||||
charttitle:'数据使用报告',
|
||||
uplink:'上行流量(B)',
|
||||
downlink:'下行流量(B)',
|
||||
datausage:'流量使用',
|
||||
totaluplink:'总上行流量',
|
||||
totaldownlink:'总下行流量',
|
||||
},
|
||||
ue: {
|
||||
eventType: "事件类型",
|
||||
realTimeDataStart: "开启实时数据",
|
||||
@@ -675,7 +739,7 @@ export default {
|
||||
toIpPleace: "请输入远程备份服务器 IP 地址",
|
||||
toPort: "服务端口",
|
||||
username: "登录用户名",
|
||||
usernamePleace: '请输入服务登录用户名',
|
||||
usernamePleace: '请输入服务登录用户名',
|
||||
password: "登录密码",
|
||||
dir: "保存目录",
|
||||
dirPleace: '请输入服务地址目标文件目录',
|
||||
@@ -1053,6 +1117,21 @@ export default {
|
||||
"chooseShowMetrics":"选择需要显示的指标",
|
||||
"chooseMetrics":"选择指标",
|
||||
},
|
||||
voiceOverView:{
|
||||
"voiceTitle":"语音通话仪表盘",
|
||||
"tips":"每分钟数据语音统计",
|
||||
"ne":"网元",
|
||||
"now":"现在",
|
||||
"last":"过去",
|
||||
"calls":"呼叫",
|
||||
"activeCall":"正在通话",
|
||||
"callMOMT":"呼叫 主叫接通率/被叫接通率",
|
||||
"failedcall":"失败呼叫",
|
||||
"registration":"注册",
|
||||
"activeregistration":"主动注册",
|
||||
"registrationsuccess":"注册成功率",
|
||||
"failedregistration":"失败注册",
|
||||
},
|
||||
},
|
||||
traceManage: {
|
||||
pcap: {
|
||||
@@ -1635,13 +1714,14 @@ export default {
|
||||
userNum: '用户编号',
|
||||
account: '登录账号',
|
||||
userName: '用户昵称',
|
||||
permission: '用户权限',
|
||||
permission: '用户角色',
|
||||
className: '部门名称',
|
||||
userType: '用户类型',
|
||||
loginIp: '登录地址',
|
||||
loginTime: '登录时间',
|
||||
status: '用户状态',
|
||||
userNameTip:'账号只能包含大写字母、小写字母和数字的字符串,长度至少为6位',
|
||||
passwdTip:'请输入正确的密码格式',
|
||||
userNameTip:'请输入正确的用户名格式不少于4位',
|
||||
passwdTip:'请输入正确的密码格式不少于6位',
|
||||
nickNameTip:'昵称不少于2位',
|
||||
emailTip:'请输入正确的邮箱地址',
|
||||
phoneTip:'请输入正确的手机号码',
|
||||
@@ -1699,6 +1779,55 @@ 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: '提交保存',
|
||||
|
||||
@@ -80,7 +80,12 @@ function fnChangeLocale(e: any) {
|
||||
<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">
|
||||
<a-button
|
||||
type="text"
|
||||
style="color: inherit"
|
||||
@click="fnClickAlarm"
|
||||
v-perms:has="['faultManage:active-alarm:index']"
|
||||
>
|
||||
<template #icon>
|
||||
<a-badge
|
||||
:count="useAlarmStore().activeAlarmTotal"
|
||||
|
||||
@@ -49,7 +49,7 @@ router.afterEach((to, from, failure) => {
|
||||
/**无Token可访问页面地址白名单 */
|
||||
const WHITE_LIST: string[] = [
|
||||
'/login',
|
||||
'/auth-redirect',
|
||||
'/login/oauth2',
|
||||
'/help',
|
||||
'/register',
|
||||
'/quick-start',
|
||||
|
||||
@@ -58,7 +58,7 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/trace-task-hlr',
|
||||
name: 'TraceTaskHLR', // 跟踪任务HLR
|
||||
meta: { title: 'router.traceTaskHLR', neType: ['HLR'] },
|
||||
meta: { title: 'router.traceTaskHLR', neType: ['UDM', 'HLR'] },
|
||||
component: () => import('@/views/traceManage/task-hlr/index.vue'),
|
||||
},
|
||||
{
|
||||
@@ -74,11 +74,16 @@ 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.vue'),
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
|
||||
@@ -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;
|
||||
/**其他信息 */
|
||||
profile: Record<string, any>;
|
||||
/**用户类型 */
|
||||
userType: string;
|
||||
};
|
||||
|
||||
const useUserStore = defineStore('user', {
|
||||
@@ -49,7 +49,7 @@ const useUserStore = defineStore('user', {
|
||||
phone: '',
|
||||
email: '',
|
||||
sex: undefined,
|
||||
profile: {},
|
||||
userType: 'System',
|
||||
}),
|
||||
getters: {
|
||||
/**
|
||||
@@ -132,6 +132,7 @@ 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) {
|
||||
|
||||
@@ -20,8 +20,9 @@ export const regExpPort =
|
||||
* 有效账号格式
|
||||
*
|
||||
* 账号只能包含大写字母、小写字母和数字的字符串,长度至少为6位
|
||||
* /^[A-Za-z0-9]{6,}$/
|
||||
*/
|
||||
export const regExpUserName = /^[A-Za-z0-9]{6,}$/;
|
||||
export const regExpUserName = /^.{4,}$/;
|
||||
|
||||
/**
|
||||
* 有效密码格式
|
||||
|
||||
@@ -6,8 +6,10 @@ 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();
|
||||
|
||||
@@ -43,6 +45,7 @@ onActivated(() => {
|
||||
<a-tab-pane
|
||||
key="reset-passwd"
|
||||
:tab="t('views.account.settings.resetPasswd')"
|
||||
v-if="userStore.userType === 'System'"
|
||||
>
|
||||
<ResetPasswd></ResetPasswd>
|
||||
</a-tab-pane>
|
||||
|
||||
1706
src/views/cbc/cbe/index.vue
Normal file
@@ -364,9 +364,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>
|
||||
|
||||
@@ -174,7 +174,7 @@ let tableColumns: ColumnsType = [
|
||||
if (record?.time) {
|
||||
return record.time;
|
||||
}
|
||||
return parseDateToStr(+record.timestamp * 1000);
|
||||
return parseDateToStr(record.timestamp);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -685,16 +685,16 @@ onBeforeUnmount(() => {
|
||||
<DictTag :options="dict.ueEventType" :value="record.eventType" />
|
||||
</template>
|
||||
<template v-if="column.key === 'result'">
|
||||
<span v-if="record.eventType === 'auth-result'">
|
||||
<span v-if="record.eventType === 'Auth'">
|
||||
<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-state'">
|
||||
<span v-if="record.eventType === 'CM'">
|
||||
<DictTag
|
||||
:options="dict.ueEventCmState"
|
||||
:value="record.eventJSON.result"
|
||||
@@ -766,16 +766,16 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.ue.result') }}: </span>
|
||||
<span v-if="record.eventType === 'auth-result'">
|
||||
<span v-if="record.eventType === 'Auth'">
|
||||
<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-state'">
|
||||
<span v-if="record.eventType === 'CM'">
|
||||
<DictTag
|
||||
:options="dict.ueEventCmState"
|
||||
:value="record.eventJSON.result"
|
||||
|
||||
@@ -290,14 +290,11 @@ function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
type ModalStateType = {
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**最大ID值 */
|
||||
maxId: number;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
confirmLoading: false,
|
||||
maxId: 0,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -399,11 +396,6 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
// 取最大值ID用作实时累加
|
||||
if (total > 0) {
|
||||
modalState.maxId = Number(rows[0].id);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
@@ -490,15 +482,18 @@ 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: modalState.maxId,
|
||||
id: cdrEvent.id,
|
||||
neType: cdrEvent.neType,
|
||||
neName: cdrEvent.neName,
|
||||
rmUID: cdrEvent.rmUID,
|
||||
timestamp: cdrEvent.timestamp,
|
||||
cdrJSON: cdrEvent.CDR,
|
||||
cdrJSON: cdrJSON,
|
||||
});
|
||||
tablePagination.total += 1;
|
||||
if (tableState.data.length > 100) {
|
||||
|
||||
@@ -175,7 +175,7 @@ let tableColumns: ColumnsType = [
|
||||
if (record?.time) {
|
||||
return record.time;
|
||||
}
|
||||
return parseDateToStr(+record.timestamp * 1000);
|
||||
return parseDateToStr(record.timestamp);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -460,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-state') {
|
||||
if (item.value === 'CM') {
|
||||
item.label = item.label.replace('CM', 'ECM');
|
||||
}
|
||||
return item;
|
||||
@@ -693,16 +693,16 @@ onBeforeUnmount(() => {
|
||||
<DictTag :options="dict.ueEventType" :value="record.eventType" />
|
||||
</template>
|
||||
<template v-if="column.key === 'result'">
|
||||
<span v-if="record.eventType === 'auth-result'">
|
||||
<span v-if="record.eventType === 'Auth'">
|
||||
<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-state'">
|
||||
<span v-if="record.eventType === 'CM'">
|
||||
<DictTag
|
||||
:options="dict.ueEventCmState"
|
||||
:value="record.eventJSON.result"
|
||||
@@ -774,16 +774,16 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.dashboard.ue.result') }}: </span>
|
||||
<span v-if="record.eventType === 'auth-result'">
|
||||
<span v-if="record.eventType === 'Auth'">
|
||||
<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-state'">
|
||||
<span v-if="record.eventType === 'CM'">
|
||||
<DictTag
|
||||
:options="dict.ueEventCmState"
|
||||
:value="record.eventJSON.result"
|
||||
|
||||
@@ -78,9 +78,9 @@ const alarmTypeType = ref<any>([
|
||||
|
||||
/**告警类型Top数据 */
|
||||
const alarmTypeTypeTop = ref<any>([
|
||||
{ name: 'AMF', value: 0 },
|
||||
{ name: 'UDM', value: 0 },
|
||||
{ name: 'SMF', value: 0 },
|
||||
{ neType: 'AMF', total: 0 },
|
||||
{ neType: 'UDM', total: 0 },
|
||||
{ neType: 'SMF', total: 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.name) {
|
||||
switch (item.severity) {
|
||||
case 'Critical':
|
||||
index = 0;
|
||||
break;
|
||||
@@ -109,7 +109,7 @@ function initPicture() {
|
||||
// index = 4;
|
||||
// break;
|
||||
}
|
||||
alarmTypeType.value[index].value = Number(item.value);
|
||||
alarmTypeType.value[index].value = Number(item.total);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ function initPicture() {
|
||||
alarmTypeTypeTop.value = alarmTypeTypeTop.value
|
||||
.concat(res1.data)
|
||||
.sort((a: any, b: any) => {
|
||||
return b.value - a.value;
|
||||
return b.total - a.total;
|
||||
})
|
||||
.slice(0, 3);
|
||||
}
|
||||
@@ -210,7 +210,7 @@ function initPicture() {
|
||||
{ offset: 1, color: '#2f54eb' },
|
||||
]), // 渐变
|
||||
},
|
||||
data: alarmTypeTypeTop.value,
|
||||
data: alarmTypeTypeTop.value.map((item: any) => item.total),
|
||||
},
|
||||
],
|
||||
// 柱状图设置
|
||||
@@ -237,7 +237,7 @@ function initPicture() {
|
||||
show: false,
|
||||
},
|
||||
inverse: true,
|
||||
data: alarmTypeTypeTop.value.map((item: any) => item.name),
|
||||
data: alarmTypeTypeTop.value.map((item: any) => item.neType),
|
||||
axisLabel: {
|
||||
color: '#A7D6F4',
|
||||
fontSize: 14,
|
||||
|
||||
@@ -152,38 +152,35 @@ onMounted(() => {
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||
<div class="card-ue-w33" v-if="item.type === 'Auth'">
|
||||
<div>
|
||||
GNB ID: <span>{{ item.data.gNBID }}</span>
|
||||
GNB ID: <span>{{ item.data.nbId }}</span>
|
||||
</div>
|
||||
<div>
|
||||
Cell ID: <span>{{ item.data.cellID }}</span>
|
||||
Cell ID: <span>{{ item.data.cellId }}</span>
|
||||
</div>
|
||||
<div>
|
||||
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||
TAC ID: <span>{{ item.data.tac }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<template v-if="item.data?.time">
|
||||
{{ parseDateToStr(item.data.time) }}
|
||||
</template>
|
||||
<template v-else-if="item.data?.timestamp">
|
||||
{{ parseDateToStr(+item.data.timestamp * 1000) }}
|
||||
<template v-if="item.data?.recordTime">
|
||||
{{ parseDateToStr(item.data.recordTime) }}
|
||||
</template>
|
||||
<template v-else> - </template>
|
||||
</div>
|
||||
<div v-if="item.type === 'auth-result'">
|
||||
<div v-if="item.type === 'Auth'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<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-state'">
|
||||
<div class="card-ue-w33" v-if="item.type === 'CM'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span>
|
||||
<DictTag :options="dict.ueEventCmState" :value="item.data.result" />
|
||||
@@ -199,7 +196,7 @@ onMounted(() => {
|
||||
<div class="card-ue-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||
<span v-if="item.type === 'cm-state'">
|
||||
<span v-if="item.type === 'CM'">
|
||||
{{
|
||||
dict.ueEventType
|
||||
.find(s => s.value === item.type)
|
||||
@@ -216,42 +213,35 @@ onMounted(() => {
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
|
||||
<div class="card-ue-w33" v-if="item.type === 'Auth'">
|
||||
<div>
|
||||
ENB ID: <span>{{ item.data.eNBID }}</span>
|
||||
ENB ID: <span>{{ item.data.nbId }}</span>
|
||||
</div>
|
||||
<div>
|
||||
Cell ID: <span>{{ item.data.cellID }}</span>
|
||||
Cell ID: <span>{{ item.data.cellId }}</span>
|
||||
</div>
|
||||
<div>
|
||||
TAC ID: <span>{{ item.data.tacID }}</span>
|
||||
TAC ID: <span>{{ item.data.tac }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<template v-if="item.data?.time">
|
||||
{{ parseDateToStr(item.data.time) }}
|
||||
</template>
|
||||
<template v-else-if="item.data?.timestamp">
|
||||
{{
|
||||
typeof item.data?.timestamp === 'number'
|
||||
? parseDateToStr(+item.data?.timestamp * 1000)
|
||||
: parseDateToStr(item.data?.timestamp)
|
||||
}}
|
||||
<template v-if="item.data?.recordTime">
|
||||
{{ parseDateToStr(item.data.recordTime) }}
|
||||
</template>
|
||||
<template v-else> - </template>
|
||||
</div>
|
||||
<div v-if="item.type === 'auth-result'">
|
||||
<div v-if="item.type === 'Auth'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<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-state'">
|
||||
<div class="card-ue-w33" v-if="item.type === 'CM'">
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span>
|
||||
<DictTag :options="dict.ueEventCmState" :value="item.data.result" />
|
||||
|
||||
@@ -19,9 +19,6 @@ 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();
|
||||
@@ -71,13 +68,13 @@ export default function useWS() {
|
||||
}
|
||||
break;
|
||||
//UPF-总流量数
|
||||
case 'upf_001_0':
|
||||
case `upf_${upfWhoId.value}_0`:
|
||||
upfTFParse('0', data);
|
||||
break;
|
||||
case 'upf_001_7':
|
||||
case `upf_${upfWhoId.value}_7`:
|
||||
upfTFParse('7', data);
|
||||
break;
|
||||
case 'upf_001_30':
|
||||
case `upf_${upfWhoId.value}_30`:
|
||||
upfTFParse('30', data);
|
||||
break;
|
||||
}
|
||||
@@ -87,7 +84,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);
|
||||
}
|
||||
@@ -122,11 +119,11 @@ export default function useWS() {
|
||||
upfTotalFlow.value[day].requestFlag = true;
|
||||
|
||||
ws.send({
|
||||
requestId: `upf_001_${day}`,
|
||||
requestId: `upf_${upfWhoId.value}_${day}`,
|
||||
type: 'upf_tf',
|
||||
data: {
|
||||
neType: 'UPF',
|
||||
neId: '001',
|
||||
neId: upfWhoId.value,
|
||||
day: Number(day),
|
||||
},
|
||||
});
|
||||
@@ -197,7 +194,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) => {
|
||||
|
||||
392
src/views/dashboard/overview2/components/AlarnTypeBar/index.vue
Normal file
@@ -0,0 +1,392 @@
|
||||
<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>
|
||||
284
src/views/dashboard/overview2/components/IMSActivity/index.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<script setup lang="ts">
|
||||
import { parseDuration, parseDateToStr } from '@/utils/date-utils';
|
||||
import { eventData, eventId } from '../../hooks/useUserActivity';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { onMounted, reactive } from 'vue';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCode: DictType[];
|
||||
/**CDR 呼叫类型 */
|
||||
cdrCallType: DictType[];
|
||||
/**UE 事件认证代码类型 */
|
||||
ueAauthCode: DictType[];
|
||||
/**UE 事件类型 */
|
||||
ueEventType: DictType[];
|
||||
/**UE 事件CM状态 */
|
||||
ueEventCmState: DictType[];
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCodeCause: DictType[];
|
||||
} = reactive({
|
||||
cdrSipCode: [],
|
||||
cdrCallType: [],
|
||||
ueAauthCode: [],
|
||||
ueEventType: [],
|
||||
ueEventCmState: [],
|
||||
cdrSipCodeCause: [],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('cdr_sip_code'),
|
||||
getDict('cdr_call_type'),
|
||||
getDict('ue_auth_code'),
|
||||
getDict('ue_event_type'),
|
||||
getDict('ue_event_cm_state'),
|
||||
getDict('cdr_sip_code_cause'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrSipCode = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.cdrCallType = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.ueAauthCode = resArr[2].value;
|
||||
}
|
||||
if (resArr[3].status === 'fulfilled') {
|
||||
dict.ueEventType = resArr[3].value;
|
||||
}
|
||||
if (resArr[4].status === 'fulfilled') {
|
||||
dict.ueEventCmState = resArr[4].value;
|
||||
}
|
||||
if (resArr[5].status === 'fulfilled') {
|
||||
dict.cdrSipCodeCause = resArr[5].value;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="activty">
|
||||
<template v-for="item in eventData" :key="item.eId">
|
||||
<!-- CDR事件IMS -->
|
||||
<div
|
||||
class="card-cdr"
|
||||
:class="{ active: item.eId === eventId }"
|
||||
v-if="item.eType === 'ims_cdr'"
|
||||
>
|
||||
<div class="card-cdr-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||
<span>
|
||||
<DictTag
|
||||
:options="dict.cdrCallType"
|
||||
:value="item.data.callType"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="card-cdr-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.caller') }}:
|
||||
<span :title="item.data.callerParty">
|
||||
{{ item.data.callerParty }}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.called') }}:
|
||||
<span :title="item.data.calledParty">
|
||||
{{ item.data.calledParty }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="item.data.callType !== 'sms'">
|
||||
{{ t('views.dashboard.overview.userActivity.duration') }}:
|
||||
<span>{{ parseDuration(item.data.callDuration) }}</span>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.time') }}:
|
||||
<span :title="item.data.releaseTime">
|
||||
{{
|
||||
typeof item.data.releaseTime === 'number'
|
||||
? parseDateToStr(+item.data.releaseTime * 1000)
|
||||
: parseDateToStr(item.data.releaseTime)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.result') }}:
|
||||
<span v-if="item.data.callType !== 'sms'">
|
||||
<DictTag
|
||||
:options="dict.cdrSipCode"
|
||||
:value="item.data.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
-
|
||||
<DictTag
|
||||
:options="dict.cdrSipCodeCause"
|
||||
:value="item.data.cause"
|
||||
value-default="0"
|
||||
/>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ t('views.dashboard.overview.userActivity.resultOK') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.activty {
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
height: 94%;
|
||||
color: #61a8ff;
|
||||
font-size: 0.75rem;
|
||||
|
||||
& .card-ue {
|
||||
border: 1px #61a8ff solid;
|
||||
border-radius: 4px;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-bottom: 0.3rem;
|
||||
line-height: 1rem;
|
||||
|
||||
& span {
|
||||
color: #68d8fe;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
& > div {
|
||||
width: 50%;
|
||||
white-space: nowrap;
|
||||
text-align: start;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&-w33 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
& > div {
|
||||
width: 33%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .card-cdr {
|
||||
border: 1px #61a8ff solid;
|
||||
border-radius: 4px;
|
||||
padding: 0.2rem 0.5rem;
|
||||
margin-bottom: 0.3rem;
|
||||
line-height: 1rem;
|
||||
|
||||
& span {
|
||||
color: #68d8fe;
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
|
||||
& > div {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
text-align: start;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .active {
|
||||
color: #faad14;
|
||||
border: 1px #faad14 solid;
|
||||
animation: backInRight 0.3s alternate;
|
||||
|
||||
& span {
|
||||
color: #faad14;
|
||||
}
|
||||
}
|
||||
|
||||
/* 兼容当行显示字内容 */
|
||||
@media (max-width: 1720px) {
|
||||
& .card-cdr {
|
||||
&-item {
|
||||
display: block;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .card-ue {
|
||||
&-item {
|
||||
display: block;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 修改滚动条的样式 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
/* 设置滚动条宽度 */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: #101129;
|
||||
/* 设置滚动条轨道背景颜色 */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #28293f;
|
||||
/* 设置滚动条滑块颜色 */
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #68d8fe;
|
||||
/* 设置鼠标悬停时滚动条滑块颜色 */
|
||||
}
|
||||
|
||||
@keyframes backInRight {
|
||||
0% {
|
||||
opacity: 0.7;
|
||||
-webkit-transform: translateX(2000px) scale(0.7);
|
||||
transform: translateX(2000px) scale(0.7);
|
||||
}
|
||||
|
||||
80% {
|
||||
opacity: 0.7;
|
||||
-webkit-transform: translateX(0) scale(0.7);
|
||||
transform: translateX(0) scale(0.7);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
369
src/views/dashboard/overview2/components/NeResources/index.vue
Normal file
@@ -0,0 +1,369 @@
|
||||
<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>
|
||||
337
src/views/dashboard/overview2/components/Topology/index.vue
Normal file
@@ -0,0 +1,337 @@
|
||||
<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>
|
||||
290
src/views/dashboard/overview2/components/UPFFlow/index.vue
Normal file
@@ -0,0 +1,290 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { listKPIData } from '@/api/perfManage/goldTarget';
|
||||
import * as echarts from 'echarts/core';
|
||||
import {
|
||||
TooltipComponent,
|
||||
TooltipComponentOption,
|
||||
GridComponent,
|
||||
GridComponentOption,
|
||||
LegendComponent,
|
||||
LegendComponentOption,
|
||||
} from 'echarts/components';
|
||||
import { LineChart, LineSeriesOption } from 'echarts/charts';
|
||||
import { UniversalTransition } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { markRaw } from 'vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { 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>
|
||||
314
src/views/dashboard/overview2/components/UserActivity/index.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<script setup lang="ts">
|
||||
import { parseDuration, parseDateToStr } from '@/utils/date-utils';
|
||||
import { eventData, eventId } from '../../hooks/useUserActivity';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { onMounted, reactive } from 'vue';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCode: DictType[];
|
||||
/**CDR 呼叫类型 */
|
||||
cdrCallType: DictType[];
|
||||
/**UE 事件认证代码类型 */
|
||||
ueAauthCode: DictType[];
|
||||
/**UE 事件类型 */
|
||||
ueEventType: DictType[];
|
||||
/**UE 事件CM状态 */
|
||||
ueEventCmState: DictType[];
|
||||
/**CDR SIP响应代码类别类型 */
|
||||
cdrSipCodeCause: DictType[];
|
||||
} = reactive({
|
||||
cdrSipCode: [],
|
||||
cdrCallType: [],
|
||||
ueAauthCode: [],
|
||||
ueEventType: [],
|
||||
ueEventCmState: [],
|
||||
cdrSipCodeCause: [],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('cdr_sip_code'),
|
||||
getDict('cdr_call_type'),
|
||||
getDict('ue_auth_code'),
|
||||
getDict('ue_event_type'),
|
||||
getDict('ue_event_cm_state'),
|
||||
getDict('cdr_sip_code_cause'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.cdrSipCode = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.cdrCallType = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.ueAauthCode = resArr[2].value;
|
||||
}
|
||||
if (resArr[3].status === 'fulfilled') {
|
||||
dict.ueEventType = resArr[3].value;
|
||||
}
|
||||
if (resArr[4].status === 'fulfilled') {
|
||||
dict.ueEventCmState = resArr[4].value;
|
||||
}
|
||||
if (resArr[5].status === 'fulfilled') {
|
||||
dict.cdrSipCodeCause = resArr[5].value;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="activty">
|
||||
<template v-for="item in eventData" :key="item.eId">
|
||||
<!-- UE事件AMF -->
|
||||
<div
|
||||
class="card-ue"
|
||||
:class="{ active: item.eId === eventId }"
|
||||
v-if="item.eType === 'amf_ue'"
|
||||
>
|
||||
<div class="card-ue-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||
<span>
|
||||
<DictTag :options="dict.ueEventType" :value="item.type" />
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div class="card-ue-w33" v-if="item.type === 'Auth'">
|
||||
<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') }}:
|
||||
<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') }}:
|
||||
<span>
|
||||
<DictTag :options="dict.ueEventCmState" :value="item.data.result" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- UE事件MME -->
|
||||
<div
|
||||
class="card-ue"
|
||||
:class="{ active: item.eId === eventId }"
|
||||
v-if="item.eType === 'mme_ue'"
|
||||
>
|
||||
<div class="card-ue-item">
|
||||
<div>
|
||||
{{ t('views.dashboard.overview.userActivity.type') }}:
|
||||
<span v-if="item.type === 'CM'">
|
||||
{{
|
||||
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') }}:
|
||||
<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') }}:
|
||||
<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>
|
||||
506
src/views/dashboard/overview2/css/index.css
Normal file
@@ -0,0 +1,506 @@
|
||||
.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;
|
||||
}
|
||||
197
src/views/dashboard/overview2/hooks/useTopology.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
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();
|
||||
}
|
||||
110
src/views/dashboard/overview2/hooks/useUPFTotalFlow.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { parseSizeFromBits, parseSizeFromKbs } from '@/utils/parse-utils';
|
||||
import { ref } from 'vue';
|
||||
|
||||
type FDType = {
|
||||
/**时间 */
|
||||
lineXTime: string[];
|
||||
/**上行 N3 */
|
||||
lineYUp: number[];
|
||||
/**下行 N6 */
|
||||
lineYDown: number[];
|
||||
/**容量 */
|
||||
cap: number;
|
||||
};
|
||||
|
||||
/**UPF-流量数据 */
|
||||
export const upfFlowData = ref<FDType>({
|
||||
lineXTime: [],
|
||||
lineYUp: [],
|
||||
lineYDown: [],
|
||||
cap: 0,
|
||||
});
|
||||
|
||||
/**UPF-流量数据 数据解析 */
|
||||
export function upfFlowParse(data: Record<string, string>) {
|
||||
upfFlowData.value.lineXTime.push(parseDateToStr(+data['timeGroup'], '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: parseSizeFromBits(up),
|
||||
down: down,
|
||||
downFrom: parseSizeFromBits(down),
|
||||
requestFlag: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**UPF-总流量数 选中 */
|
||||
export const upfTFActive = ref<string>('0');
|
||||
|
||||
/**属性复位 */
|
||||
export function upfTotalFlowReset() {
|
||||
upfFlowData.value = {
|
||||
lineXTime: [],
|
||||
lineYUp: [],
|
||||
lineYDown: [],
|
||||
cap: 0,
|
||||
};
|
||||
for (const key of Object.keys(upfTotalFlow.value)) {
|
||||
upfTotalFlow.value[key] = {
|
||||
up: 0,
|
||||
upFrom: '0 B',
|
||||
down: 0,
|
||||
downFrom: '0 B',
|
||||
requestFlag: false,
|
||||
};
|
||||
}
|
||||
upfTFActive.value = '0';
|
||||
}
|
||||
144
src/views/dashboard/overview2/hooks/useUserActivity.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
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 = '';
|
||||
}
|
||||
222
src/views/dashboard/overview2/hooks/useWS.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
BIN
src/views/dashboard/overview2/images/bj.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
src/views/dashboard/overview2/images/border.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
src/views/dashboard/overview2/images/brand.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src/views/dashboard/overview2/images/line.png
Normal file
|
After Width: | Height: | Size: 237 B |
BIN
src/views/dashboard/overview2/images/newBrand.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/views/dashboard/overview2/images/rect.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/views/dashboard/overview2/images/title.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
875
src/views/dashboard/overview2/index.vue
Normal file
@@ -0,0 +1,875 @@
|
||||
<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') {
|
||||
upfTFSend('7');
|
||||
upfTFActive.value = '7';
|
||||
} else if (upfTFActive.value === '7') {
|
||||
upfTFSend('30');
|
||||
upfTFActive.value = '30';
|
||||
} else if (upfTFActive.value === '30') {
|
||||
upfTFSend('0');
|
||||
upfTFActive.value = '0';
|
||||
}
|
||||
}, 10_000);
|
||||
|
||||
clearInterval(interval5s.value);
|
||||
interval5s.value = setInterval(() => {
|
||||
if (!interval5s.value || !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" />
|
||||
{{ 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" />
|
||||
{{ 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" />
|
||||
{{ 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" />
|
||||
{{ t('views.dashboard.overview.upfFlow.title') }}
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
{{ 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" />
|
||||
{{ 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"
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
{{ 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>
|
||||
@@ -260,14 +260,11 @@ function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
type ModalStateType = {
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**最大ID值 */
|
||||
maxId: number;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
confirmLoading: false,
|
||||
maxId: 0,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -369,11 +366,6 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
// 取最大值ID用作实时累加
|
||||
if (total > 0) {
|
||||
modalState.maxId = Number(rows[0].id);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
@@ -460,15 +452,18 @@ 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: modalState.maxId,
|
||||
id: cdrEvent.id,
|
||||
neType: cdrEvent.neType,
|
||||
neName: cdrEvent.neName,
|
||||
rmUID: cdrEvent.rmUID,
|
||||
timestamp: cdrEvent.timestamp,
|
||||
cdrJSON: cdrEvent.CDR,
|
||||
cdrJSON: cdrJSON,
|
||||
});
|
||||
tablePagination.total += 1;
|
||||
if (tableState.data.length > 100) {
|
||||
|
||||
@@ -287,14 +287,11 @@ function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
type ModalStateType = {
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**最大ID值 */
|
||||
maxId: number;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
confirmLoading: false,
|
||||
maxId: 0,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -396,11 +393,6 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
// 取最大值ID用作实时累加
|
||||
if (total > 0) {
|
||||
modalState.maxId = Number(rows[0].id);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
@@ -487,15 +479,18 @@ 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: modalState.maxId,
|
||||
id: cdrEvent.id,
|
||||
neType: cdrEvent.neType,
|
||||
neName: cdrEvent.neName,
|
||||
rmUID: cdrEvent.rmUID,
|
||||
timestamp: cdrEvent.timestamp,
|
||||
cdrJSON: cdrEvent.CDR,
|
||||
cdrJSON: cdrJSON,
|
||||
});
|
||||
tablePagination.total += 1;
|
||||
if (tableState.data.length > 100) {
|
||||
|
||||
@@ -37,6 +37,7 @@ import { parseSizeFromByte } from '@/utils/parse-utils';
|
||||
import { message } from 'ant-design-vue';
|
||||
import useNeListStore from '@/store/modules/ne_list';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
const { t, currentLocale } = useI18n();
|
||||
const neListStore = useNeListStore();
|
||||
const ws = new WS();
|
||||
@@ -48,7 +49,7 @@ let cdrChart: echarts.ECharts | null = null;
|
||||
/**图表配置 */
|
||||
const option = {
|
||||
title: {
|
||||
text: 'Data Usage Report',
|
||||
text: t('views.dashboard.chart.charttitle'),
|
||||
left: 'left',
|
||||
},
|
||||
tooltip: {
|
||||
@@ -148,12 +149,12 @@ const option = {
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
name: 'Downlink (Byte)',
|
||||
name: t('views.dashboard.chart.downlink'),
|
||||
type: 'value',
|
||||
},
|
||||
{
|
||||
gridIndex: 1,
|
||||
name: 'Uplink (Byte)',
|
||||
name: t('views.dashboard.chart.uplink'),
|
||||
type: 'value',
|
||||
inverse: true,
|
||||
},
|
||||
@@ -381,7 +382,7 @@ function fnRanderChartDataLoad() {
|
||||
break;
|
||||
}
|
||||
// 时间
|
||||
const dataTime = item.cdrJSON.invocationTimestamp;
|
||||
const dataTime = parseDateToStr(item.cdrJSON.invocationTimestamp);
|
||||
const listOfMultipleUnitUsage = item.cdrJSON.listOfMultipleUnitUsage;
|
||||
if (
|
||||
!Array.isArray(listOfMultipleUnitUsage) ||
|
||||
@@ -504,6 +505,10 @@ 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);
|
||||
@@ -511,7 +516,6 @@ function fnRealTime() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const cdrJSON = cdrEvent.CDR;
|
||||
if (!cdrJSON.invocationTimestamp) {
|
||||
return;
|
||||
}
|
||||
@@ -683,11 +687,11 @@ onBeforeUnmount(() => {
|
||||
<!-- 图数据 -->
|
||||
<div ref="cdrChartDom" style="height: 600px; width: 100%"></div>
|
||||
|
||||
<a-descriptions title="Data Usage" bordered :column="2">
|
||||
<a-descriptions-item label="Total Uplink">
|
||||
<a-descriptions :title="t('views.dashboard.chart.datausage')" bordered :column="2">
|
||||
<a-descriptions-item :label="t('views.dashboard.chart.totaluplink')">
|
||||
{{ state.dataUsage[0] }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="Total Downlink">
|
||||
<a-descriptions-item :label="t('views.dashboard.chart.totaldownlink')">
|
||||
{{ state.dataUsage[1] }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
@@ -254,14 +254,11 @@ function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
type ModalStateType = {
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**最大ID值 */
|
||||
maxId: number;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
confirmLoading: false,
|
||||
maxId: 0,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -363,11 +360,6 @@ function fnGetList(pageNum?: number) {
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
// 取最大值ID用作实时累加
|
||||
if (total > 0) {
|
||||
modalState.maxId = Number(rows[0].id);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
@@ -454,15 +446,18 @@ function wsMessage(res: Record<string, any>) {
|
||||
// cdrEvent CDR会话事件
|
||||
if (data.groupId === `1007_${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: modalState.maxId,
|
||||
id: cdrEvent.id,
|
||||
neType: cdrEvent.neType,
|
||||
neName: cdrEvent.neName,
|
||||
rmUID: cdrEvent.rmUID,
|
||||
timestamp: cdrEvent.timestamp,
|
||||
cdrJSON: cdrEvent.CDR,
|
||||
cdrJSON: cdrJSON,
|
||||
});
|
||||
tablePagination.total += 1;
|
||||
if (tableState.data.length > 100) {
|
||||
|
||||
@@ -70,7 +70,7 @@ let rangePickerPresets = ref([
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
alarmStatus: 1,
|
||||
alarmStatus: 'Active',
|
||||
sortField: 'event_time',
|
||||
sortOrder: 'desc',
|
||||
/**告警设备类型 */
|
||||
@@ -102,7 +102,7 @@ let queryParams = reactive({
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
alarmStatus: 1,
|
||||
alarmStatus: 'Active',
|
||||
/**告警设备类型 */
|
||||
neType: '',
|
||||
/**告警网元名称 */
|
||||
@@ -386,7 +386,7 @@ let modalState: ModalStateType = reactive({
|
||||
objectName: '',
|
||||
locationInfo: '',
|
||||
province: '',
|
||||
alarmStatus: '',
|
||||
alarmStatus: 'Active',
|
||||
specificProblemId: '',
|
||||
specificProblem: '',
|
||||
addInfo: '',
|
||||
|
||||
BIN
src/views/faultManage/alarm-overview/images/bg.png
Normal file
|
After Width: | Height: | Size: 257 KiB |
1338
src/views/faultManage/alarm-overview/index.vue
Normal file
@@ -680,7 +680,7 @@ onMounted(() => {
|
||||
:label="t('views.faultManage.activeAlarm.eventTime')"
|
||||
name="eventTime"
|
||||
>
|
||||
{{ modalState.from.eventTime }}
|
||||
{{ parseDateToStr(modalState.from.eventTime) }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
@@ -61,7 +61,7 @@ let rangePickerPresets = ref([
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
alarmStatus: 0,
|
||||
alarmStatus: 'Clear',
|
||||
sortField: 'event_time',
|
||||
sortOrder: 'desc',
|
||||
/**告警设备类型 */
|
||||
@@ -91,7 +91,7 @@ let queryParams = reactive({
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
alarmStatus: 0,
|
||||
alarmStatus: 'Clear',
|
||||
/**告警设备类型 */
|
||||
neType: '',
|
||||
/**告警网元名称 */
|
||||
@@ -317,7 +317,7 @@ let modalState: ModalStateType = reactive({
|
||||
objectName: '',
|
||||
locationInfo: '',
|
||||
province: '',
|
||||
alarmStatus: '',
|
||||
alarmStatus: 'Clear',
|
||||
specificProblemId: '',
|
||||
specificProblem: '',
|
||||
addInfo: '',
|
||||
|
||||
@@ -6,7 +6,7 @@ import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listMML } from '@/api/logManage/mml';
|
||||
import { mmlLogList } from '@/api/tool/mml';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -16,7 +16,7 @@ let queryRangePicker = ref<[string, string]>(['', '']);
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**登录账号 */
|
||||
accountName: '',
|
||||
user: '',
|
||||
/**记录时间 */
|
||||
beginTime: '',
|
||||
endTime: '',
|
||||
@@ -105,8 +105,8 @@ let tableColumns: ColumnsType = reactive([
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.mml.MML'),
|
||||
dataIndex: 'mml',
|
||||
key: 'mml',
|
||||
dataIndex: 'command',
|
||||
key: 'command',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
},
|
||||
@@ -157,8 +157,8 @@ function fnGetList(pageNum?: number) {
|
||||
}
|
||||
queryParams.beginTime = queryRangePicker.value[0];
|
||||
queryParams.endTime = queryRangePicker.value[1];
|
||||
listMML(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
mmlLogList(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
@@ -192,13 +192,11 @@ onMounted(() => {
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.logManage.mml.account')"
|
||||
name="accountName"
|
||||
>
|
||||
<a-form-item :label="t('views.logManage.mml.account')" name="user">
|
||||
<a-input
|
||||
v-model:value="queryParams.accountName"
|
||||
v-model:value="queryParams.user"
|
||||
:allow-clear="true"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@@ -294,7 +292,7 @@ onMounted(() => {
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:pagination="tablePagination"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<!-- <template v-if="column.key === 'mml'">
|
||||
|
||||
@@ -1,398 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import svgLight from '@/assets/svg/light.svg';
|
||||
import svgDark from '@/assets/svg/dark.svg';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { reactive, onMounted, computed, toRaw } from 'vue';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { getCaptchaImage } from '@/api/auth';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useLayoutStore from '@/store/modules/layout';
|
||||
import { viewTransitionTheme } from 'antdv-pro-layout';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { parseUrlPath } from '@/plugins/file-static-url';
|
||||
const { t, changeLocale, optionsLocale } = useI18n();
|
||||
const layoutStore = useLayoutStore();
|
||||
const appStore = useAppStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
let state = reactive({
|
||||
/**表单属性 */
|
||||
from: {
|
||||
/**账号 */
|
||||
username: '',
|
||||
/**密码 */
|
||||
password: '',
|
||||
/**验证码 */
|
||||
code: '',
|
||||
/**验证码uuid */
|
||||
uuid: '',
|
||||
},
|
||||
/**表单提交点击状态 */
|
||||
fromClick: false,
|
||||
/**验证码状态 */
|
||||
captcha: {
|
||||
/**验证码开关 */
|
||||
enabled: false,
|
||||
/**验证码图片地址 */
|
||||
codeImg: '',
|
||||
},
|
||||
/**验证码点击状态 */
|
||||
captchaClick: false,
|
||||
});
|
||||
|
||||
/**表单验证通过 */
|
||||
function fnFinish() {
|
||||
state.fromClick = true;
|
||||
// 发送请求
|
||||
useUserStore()
|
||||
.fnLogin(toRaw(state.from))
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('views.login.loginSuccess'), 1);
|
||||
/**登录后重定向页面 */
|
||||
const redirectPath = route.query?.redirect || '/index';
|
||||
router.push({ path: redirectPath as string });
|
||||
} else {
|
||||
message.error(`${res.msg}`, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.fromClick = false;
|
||||
// 刷新验证码
|
||||
if (state.captcha.enabled) {
|
||||
state.from.code = '';
|
||||
fnGetCaptcha();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*/
|
||||
function fnGetCaptcha() {
|
||||
if (state.captchaClick) return;
|
||||
state.captchaClick = true;
|
||||
getCaptchaImage()
|
||||
.then(res => {
|
||||
if (res.code !== RESULT_CODE_SUCCESS) {
|
||||
message.warning({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { enabled, img, uuid } = res.data;
|
||||
state.captcha.enabled = Boolean(enabled);
|
||||
if (state.captcha.enabled) {
|
||||
state.captcha.codeImg = img;
|
||||
state.from.uuid = uuid;
|
||||
}
|
||||
if (res.data?.text) {
|
||||
state.from.code = res.data.text;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.captchaClick = false;
|
||||
});
|
||||
}
|
||||
|
||||
// 判断是否有背景地址
|
||||
const calcBG = computed(() => {
|
||||
const bgURL = parseUrlPath(appStore.loginBackground);
|
||||
if (bgURL && bgURL !== '#') {
|
||||
return {
|
||||
backgroundImage: `url('${bgURL}')`,
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: 'cover',
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
/**
|
||||
* 国际化翻译转换
|
||||
*/
|
||||
function fnLocale() {
|
||||
let title = route.meta.title as string;
|
||||
if (title.indexOf('router.') !== -1) {
|
||||
title = t(title);
|
||||
}
|
||||
appStore.setTitle(title);
|
||||
}
|
||||
|
||||
/**改变主题色 */
|
||||
function fnClickTheme(e: any) {
|
||||
viewTransitionTheme(isDarkMode => {
|
||||
layoutStore.changeConf('theme', isDarkMode ? 'light' : 'dark');
|
||||
}, e);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fnLocale();
|
||||
fnGetCaptcha();
|
||||
});
|
||||
|
||||
/**改变多语言 */
|
||||
function fnChangeLocale(e: any) {
|
||||
changeLocale(e.key);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container" :style="calcBG">
|
||||
<section v-show="!calcBG">
|
||||
<div class="animation animation1"></div>
|
||||
<div class="animation animation2"></div>
|
||||
<div class="animation animation3"></div>
|
||||
<div class="animation animation4"></div>
|
||||
<div class="animation animation5"></div>
|
||||
</section>
|
||||
|
||||
<a-card :bordered="false" class="login-card">
|
||||
<div class="title">
|
||||
{{ t('common.title') }}
|
||||
</div>
|
||||
<a-form :model="state.from" name="stateFrom" @finish="fnFinish">
|
||||
<a-form-item
|
||||
name="username"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 2,
|
||||
max: 30,
|
||||
message: t('valid.userNamePlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.from.username"
|
||||
size="large"
|
||||
:placeholder="t('valid.userNameHit')"
|
||||
:maxlength="30"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
name="password"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 5,
|
||||
max: 26,
|
||||
message: t('valid.passwordPlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.from.password"
|
||||
size="large"
|
||||
:placeholder="t('valid.passwordHit')"
|
||||
:maxlength="26"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-row v-if="state.captcha.enabled">
|
||||
<a-col :span="16">
|
||||
<a-form-item
|
||||
name="code"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
message: t('valid.codePlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.from.code"
|
||||
size="large"
|
||||
:placeholder="t('valid.codeHit')"
|
||||
:maxlength="6"
|
||||
>
|
||||
<template #prefix>
|
||||
<RobotOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-image
|
||||
:alt="t('valid.codeHit')"
|
||||
style="cursor: pointer; border-radius: 2px"
|
||||
width="100px"
|
||||
height="40px"
|
||||
:preview="false"
|
||||
:src="state.captcha.codeImg"
|
||||
@click="fnGetCaptcha"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row
|
||||
justify="space-between"
|
||||
align="middle"
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<a-col :span="12" v-if="appStore.registerUser">
|
||||
<a-button
|
||||
type="link"
|
||||
target="_self"
|
||||
:title="t('views.login.registerBtn')"
|
||||
@click="() => router.push({ name: 'Register' })"
|
||||
>
|
||||
{{ t('views.login.registerBtn') }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-button
|
||||
block
|
||||
type="primary"
|
||||
size="large"
|
||||
html-type="submit"
|
||||
:loading="state.fromClick"
|
||||
>
|
||||
{{ t('views.login.loginBtn') }}
|
||||
</a-button>
|
||||
|
||||
<a-row justify="end" align="middle" style="margin-top: 18px">
|
||||
<a-col :span="3">
|
||||
<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-col>
|
||||
<a-col :span="3" v-if="appStore.i18nOpen">
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="text">
|
||||
<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-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
padding-top: 164px;
|
||||
|
||||
// background: url('./../assets/black_dot.png') 0% 0% / 14px 14px repeat;
|
||||
|
||||
background-image: url(/background/light.jpg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
|
||||
.animation {
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: #1be1f6;
|
||||
|
||||
&.animation1 {
|
||||
left: 15%;
|
||||
top: 70%;
|
||||
animation: slashStar 2s ease-in-out 0.3s infinite;
|
||||
}
|
||||
&.animation2 {
|
||||
left: 34%;
|
||||
top: 35%;
|
||||
animation: slashStar 2s ease-in-out 1.2s infinite;
|
||||
}
|
||||
&.animation3 {
|
||||
left: 10%;
|
||||
top: 8%;
|
||||
animation: slashStar 2s ease-in-out 0.5s infinite;
|
||||
}
|
||||
&.animation4 {
|
||||
left: 68%;
|
||||
top: 68%;
|
||||
animation: slashStar 2s ease-in-out 0.8s infinite;
|
||||
}
|
||||
&.animation5 {
|
||||
left: 87%;
|
||||
top: 30%;
|
||||
animation: slashStar 2s ease-in-out 1.5s infinite;
|
||||
}
|
||||
}
|
||||
@keyframes slashStar {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] .container {
|
||||
background-image: url(/background/dark.jpg);
|
||||
background-color: #141414;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 368px;
|
||||
min-width: 260px;
|
||||
margin: 0 auto;
|
||||
margin-left: 60%;
|
||||
border-radius: 6px;
|
||||
|
||||
& .title {
|
||||
text-align: left;
|
||||
margin-bottom: 18px;
|
||||
color: #141414;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
}
|
||||
& .prefix-icon {
|
||||
color: #8c8c8c;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] .login-card {
|
||||
& .title {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.login-card {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
339
src/views/login/components/CardLogin.vue
Normal file
@@ -0,0 +1,339 @@
|
||||
<script setup lang="ts">
|
||||
import svgLight from '@/assets/svg/light.svg';
|
||||
import svgDark from '@/assets/svg/dark.svg';
|
||||
import { reactive, toRaw, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { viewTransitionTheme } from 'antdv-pro-layout';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useLayoutStore from '@/store/modules/layout';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { getCaptchaImage } from '@/api/auth';
|
||||
import {
|
||||
loginSourceState,
|
||||
fnClickLoginSource,
|
||||
} from '@/views/login/hooks/useLoginSource';
|
||||
const { t, changeLocale, optionsLocale } = useI18n();
|
||||
const layoutStore = useLayoutStore();
|
||||
const appStore = useAppStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
let state = reactive({
|
||||
/**表单属性 */
|
||||
from: {
|
||||
/**账号 */
|
||||
username: '',
|
||||
/**密码 */
|
||||
password: '',
|
||||
/**验证码 */
|
||||
code: '',
|
||||
/**验证码uuid */
|
||||
uuid: '',
|
||||
},
|
||||
/**表单提交点击状态 */
|
||||
fromClick: false,
|
||||
/**验证码状态 */
|
||||
captcha: {
|
||||
/**验证码开关 */
|
||||
enabled: false,
|
||||
/**验证码图片地址 */
|
||||
codeImg: '',
|
||||
},
|
||||
/**验证码点击状态 */
|
||||
captchaClick: false,
|
||||
});
|
||||
|
||||
/**表单验证通过 */
|
||||
function fnFinish() {
|
||||
state.fromClick = true;
|
||||
// 发送请求
|
||||
useUserStore()
|
||||
.fnLogin(toRaw(state.from))
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('views.login.loginSuccess'), 1);
|
||||
/**登录后重定向页面 */
|
||||
const redirectPath = route.query?.redirect || '/index';
|
||||
router.push({ path: redirectPath as string });
|
||||
} else {
|
||||
message.error(`${res.msg}`, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.fromClick = false;
|
||||
// 刷新验证码
|
||||
if (state.captcha.enabled) {
|
||||
state.from.code = '';
|
||||
fnGetCaptcha();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*/
|
||||
function fnGetCaptcha() {
|
||||
if (state.captchaClick) return;
|
||||
state.captchaClick = true;
|
||||
getCaptchaImage()
|
||||
.then(res => {
|
||||
if (res.code !== RESULT_CODE_SUCCESS) {
|
||||
message.warning({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { enabled, img, uuid } = res.data;
|
||||
state.captcha.enabled = Boolean(enabled);
|
||||
if (state.captcha.enabled) {
|
||||
state.captcha.codeImg = img;
|
||||
state.from.uuid = uuid;
|
||||
}
|
||||
if (res.data?.text) {
|
||||
state.from.code = res.data.text;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.captchaClick = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化翻译转换
|
||||
*/
|
||||
function fnLocale() {
|
||||
let title = route.meta.title as string;
|
||||
if (title.indexOf('router.') !== -1) {
|
||||
title = t(title);
|
||||
}
|
||||
appStore.setTitle(title);
|
||||
}
|
||||
|
||||
/**改变主题色 */
|
||||
function fnClickTheme(e: any) {
|
||||
viewTransitionTheme(isDarkMode => {
|
||||
layoutStore.changeConf('theme', isDarkMode ? 'light' : 'dark');
|
||||
}, e);
|
||||
}
|
||||
|
||||
/**改变多语言 */
|
||||
function fnChangeLocale(e: any) {
|
||||
changeLocale(e.key);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fnLocale();
|
||||
fnGetCaptcha();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card :bordered="false" class="login-card">
|
||||
<div class="title">
|
||||
{{ t('common.title') }}
|
||||
</div>
|
||||
<a-form :model="state.from" name="stateFrom" @finish="fnFinish">
|
||||
<a-form-item
|
||||
name="username"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 2,
|
||||
max: 30,
|
||||
message: t('valid.userNamePlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.from.username"
|
||||
size="large"
|
||||
:placeholder="t('valid.userNameHit')"
|
||||
:maxlength="30"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
name="password"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 6,
|
||||
max: 26,
|
||||
message: t('valid.passwordPlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.from.password"
|
||||
size="large"
|
||||
:placeholder="t('valid.passwordHit')"
|
||||
:maxlength="26"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-row v-if="state.captcha.enabled">
|
||||
<a-col :span="16">
|
||||
<a-form-item
|
||||
name="code"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
message: t('valid.codePlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.from.code"
|
||||
size="large"
|
||||
:placeholder="t('valid.codeHit')"
|
||||
:maxlength="6"
|
||||
>
|
||||
<template #prefix>
|
||||
<RobotOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-image
|
||||
:alt="t('valid.codeHit')"
|
||||
style="cursor: pointer; border-radius: 2px"
|
||||
width="100px"
|
||||
height="40px"
|
||||
:preview="false"
|
||||
:src="state.captcha.codeImg"
|
||||
@click="fnGetCaptcha"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-button
|
||||
block
|
||||
type="primary"
|
||||
size="large"
|
||||
html-type="submit"
|
||||
:loading="state.fromClick"
|
||||
>
|
||||
{{ t('views.login.loginBtn') }}
|
||||
</a-button>
|
||||
|
||||
<template v-if="loginSourceState.list.length > 0">
|
||||
<a-divider :plain="true">
|
||||
{{ t('views.login.otherMethod') }}
|
||||
</a-divider>
|
||||
<a-row
|
||||
justify="center"
|
||||
align="middle"
|
||||
:wrap="false"
|
||||
:gutter="16"
|
||||
style="margin-top: 18px"
|
||||
>
|
||||
<a-col v-for="s in loginSourceState.list" :key="s.uid">
|
||||
<a-tooltip placement="top">
|
||||
<template #title>{{ s.name }}</template>
|
||||
<a-button
|
||||
type="text"
|
||||
shape="circle"
|
||||
@click="fnClickLoginSource(s)"
|
||||
>
|
||||
<template #icon>
|
||||
<a-avatar v-if="s.icon" :src="s.icon" :alt="s.name" />
|
||||
<a-avatar v-else :alt="s.name">{{ s.name }}</a-avatar>
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<a-row justify="end" align="middle" style="margin-top: 18px">
|
||||
<a-col :span="12" v-if="appStore.registerUser">
|
||||
<a-button
|
||||
type="link"
|
||||
target="_self"
|
||||
:title="t('views.login.registerBtn')"
|
||||
@click="() => router.push({ name: 'Register' })"
|
||||
>
|
||||
{{ t('views.login.registerBtn') }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col :span="3" v-if="appStore.i18nOpen">
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="text">
|
||||
<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-col>
|
||||
<a-col :span="3">
|
||||
<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-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.login-card {
|
||||
width: 368px;
|
||||
min-width: 260px;
|
||||
margin: 0 auto;
|
||||
margin-left: 60%;
|
||||
border-radius: 6px;
|
||||
|
||||
& .title {
|
||||
text-align: left;
|
||||
margin-bottom: 18px;
|
||||
color: #141414;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
}
|
||||
& .prefix-icon {
|
||||
color: #8c8c8c;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] .login-card {
|
||||
& .title {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.login-card {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
163
src/views/login/components/CardLoginForLDAP.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, toRaw } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { loginLDAP } from '@/api/auth';
|
||||
import {
|
||||
loginSourceState,
|
||||
fnClickLoginBack,
|
||||
} from '@/views/login/hooks/useLoginSource';
|
||||
import { setAccessToken, setRefreshToken } from '@/plugins/auth-token';
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
let state = reactive({
|
||||
/**表单属性 */
|
||||
from: {
|
||||
/**账号 */
|
||||
username: '',
|
||||
/**密码 */
|
||||
password: '',
|
||||
/**认证uid */
|
||||
uid: loginSourceState.selct.uid,
|
||||
},
|
||||
/**表单提交点击状态 */
|
||||
fromClick: false,
|
||||
});
|
||||
|
||||
/**表单验证通过 */
|
||||
function fnFinish() {
|
||||
state.fromClick = true;
|
||||
// 发送请求
|
||||
loginLDAP(toRaw(state.from))
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('views.login.loginSuccess'), 1);
|
||||
setAccessToken(res.data.accessToken, res.data.refreshExpiresIn);
|
||||
setRefreshToken(res.data.refreshToken, res.data.refreshExpiresIn);
|
||||
/**登录后重定向页面 */
|
||||
const redirectPath = route.query?.redirect || '/index';
|
||||
router.push({ path: redirectPath as string });
|
||||
} else {
|
||||
message.error(`${res.msg}`, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.fromClick = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card :bordered="false" class="login-card">
|
||||
<div class="title">
|
||||
{{ loginSourceState.selct.name }}
|
||||
</div>
|
||||
|
||||
<a-form :model="state.from" name="stateFrom" @finish="fnFinish">
|
||||
<a-form-item
|
||||
name="username"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 2,
|
||||
max: 30,
|
||||
message: t('valid.userNamePlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.from.username"
|
||||
size="large"
|
||||
:placeholder="t('valid.userNameHit')"
|
||||
:maxlength="30"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
name="password"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 6,
|
||||
max: 26,
|
||||
message: t('valid.passwordPlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.from.password"
|
||||
size="large"
|
||||
:placeholder="t('valid.passwordHit')"
|
||||
:maxlength="26"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-button
|
||||
block
|
||||
type="primary"
|
||||
size="large"
|
||||
html-type="submit"
|
||||
:loading="state.fromClick"
|
||||
>
|
||||
{{ t('views.login.loginBtn') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
block
|
||||
size="large"
|
||||
:disabled="state.fromClick"
|
||||
style="margin-top: 24px"
|
||||
@click="fnClickLoginBack"
|
||||
>
|
||||
{{ t('views.login.backBtn') }}
|
||||
</a-button>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.login-card {
|
||||
width: 368px;
|
||||
min-width: 260px;
|
||||
margin: 0 auto;
|
||||
margin-left: 60%;
|
||||
border-radius: 6px;
|
||||
|
||||
& .title {
|
||||
text-align: left;
|
||||
margin-bottom: 18px;
|
||||
color: #141414;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
}
|
||||
& .prefix-icon {
|
||||
color: #8c8c8c;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] .login-card {
|
||||
& .title {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.login-card {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
163
src/views/login/components/CardLoginForSMTP.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, toRaw } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { loginSMTP } from '@/api/auth';
|
||||
import {
|
||||
loginSourceState,
|
||||
fnClickLoginBack,
|
||||
} from '@/views/login/hooks/useLoginSource';
|
||||
import { setAccessToken, setRefreshToken } from '@/plugins/auth-token';
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
let state = reactive({
|
||||
/**表单属性 */
|
||||
from: {
|
||||
/**账号 */
|
||||
username: '',
|
||||
/**密码 */
|
||||
password: '',
|
||||
/**认证uid */
|
||||
uid: loginSourceState.selct.uid,
|
||||
},
|
||||
/**表单提交点击状态 */
|
||||
fromClick: false,
|
||||
});
|
||||
|
||||
/**表单验证通过 */
|
||||
function fnFinish() {
|
||||
state.fromClick = true;
|
||||
// 发送请求
|
||||
loginSMTP(toRaw(state.from))
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('views.login.loginSuccess'), 1);
|
||||
setAccessToken(res.data.accessToken, res.data.refreshExpiresIn);
|
||||
setRefreshToken(res.data.refreshToken, res.data.refreshExpiresIn);
|
||||
/**登录后重定向页面 */
|
||||
const redirectPath = route.query?.redirect || '/index';
|
||||
router.push({ path: redirectPath as string });
|
||||
} else {
|
||||
message.error(`${res.msg}`, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.fromClick = false;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card :bordered="false" class="login-card">
|
||||
<div class="title">
|
||||
{{ loginSourceState.selct.name }}
|
||||
</div>
|
||||
|
||||
<a-form :model="state.from" name="stateFrom" @finish="fnFinish">
|
||||
<a-form-item
|
||||
name="username"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 2,
|
||||
max: 30,
|
||||
message: t('valid.userNamePlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.from.username"
|
||||
size="large"
|
||||
:placeholder="t('valid.userNameHit')"
|
||||
:maxlength="30"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
name="password"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
min: 6,
|
||||
max: 26,
|
||||
message: t('valid.passwordPlease'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.from.password"
|
||||
size="large"
|
||||
:placeholder="t('valid.passwordHit')"
|
||||
:maxlength="26"
|
||||
>
|
||||
<template #prefix>
|
||||
<LockOutlined class="prefix-icon" />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<a-button
|
||||
block
|
||||
type="primary"
|
||||
size="large"
|
||||
html-type="submit"
|
||||
:loading="state.fromClick"
|
||||
>
|
||||
{{ t('views.login.loginBtn') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
block
|
||||
size="large"
|
||||
:disabled="state.fromClick"
|
||||
style="margin-top: 24px"
|
||||
@click="fnClickLoginBack"
|
||||
>
|
||||
{{ t('views.login.backBtn') }}
|
||||
</a-button>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.login-card {
|
||||
width: 368px;
|
||||
min-width: 260px;
|
||||
margin: 0 auto;
|
||||
margin-left: 60%;
|
||||
border-radius: 6px;
|
||||
|
||||
& .title {
|
||||
text-align: left;
|
||||
margin-bottom: 18px;
|
||||
color: #141414;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
}
|
||||
& .prefix-icon {
|
||||
color: #8c8c8c;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] .login-card {
|
||||
& .title {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.login-card {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
82
src/views/login/hooks/useLoginSource.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { getLoginSource, loginOAuth2URL } from '@/api/auth';
|
||||
import { parseUrlPath } from '@/plugins/file-static-url';
|
||||
import { defineAsyncComponent, nextTick, reactive, shallowRef } from 'vue';
|
||||
|
||||
// 异步加载组件
|
||||
const cardLogin = defineAsyncComponent(
|
||||
() => import('../components/CardLogin.vue')
|
||||
);
|
||||
|
||||
// 当前组件
|
||||
export const currentComponent = shallowRef(cardLogin);
|
||||
|
||||
/**登录认证源类型 */
|
||||
type LoginSourceType = {
|
||||
/**认证标识 */
|
||||
uid: string;
|
||||
/**名称 */
|
||||
name: string;
|
||||
/**类型 */
|
||||
type: string;
|
||||
/**图标 */
|
||||
icon: string;
|
||||
};
|
||||
|
||||
/**登录认证源 */
|
||||
export const loginSourceState = reactive({
|
||||
list: [] as LoginSourceType[],
|
||||
selct: {
|
||||
uid: '',
|
||||
name: '',
|
||||
type: '',
|
||||
icon: '',
|
||||
},
|
||||
});
|
||||
|
||||
/**获取登录认证源 */
|
||||
export function fnGetLoginSource() {
|
||||
getLoginSource().then(res => {
|
||||
loginSourceState.list = res.data.map((item: Record<string, string>) => ({
|
||||
...item,
|
||||
icon: parseUrlPath(item.icon),
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
/**点击登录认证源 */
|
||||
export function fnClickLoginSource(item: LoginSourceType) {
|
||||
loginSourceState.selct = { ...item };
|
||||
|
||||
let loadComponent: any = undefined;
|
||||
switch (item.type) {
|
||||
case 'LDAP':
|
||||
loadComponent = defineAsyncComponent(
|
||||
() => import('../components/CardLoginForLDAP.vue')
|
||||
);
|
||||
break;
|
||||
case 'SMTP':
|
||||
loadComponent = defineAsyncComponent(
|
||||
() => import('../components/CardLoginForSMTP.vue')
|
||||
);
|
||||
break;
|
||||
case 'OAuth2':
|
||||
const redirectUri = loginOAuth2URL(item.uid);
|
||||
window.location.href = redirectUri;
|
||||
// window.open(redirectUri, '_blank');
|
||||
break;
|
||||
default:
|
||||
fnClickLoginBack();
|
||||
}
|
||||
if (loadComponent) {
|
||||
nextTick(() => {
|
||||
currentComponent.value = loadComponent;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**点击登录返回 */
|
||||
export function fnClickLoginBack() {
|
||||
nextTick(() => {
|
||||
currentComponent.value = cardLogin;
|
||||
});
|
||||
}
|
||||
105
src/views/login/index.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted } from 'vue';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { parseUrlPath } from '@/plugins/file-static-url';
|
||||
import {
|
||||
currentComponent,
|
||||
fnGetLoginSource,
|
||||
} from '@/views/login/hooks/useLoginSource';
|
||||
const appStore = useAppStore();
|
||||
|
||||
// 判断是否有背景地址
|
||||
const calcBG = computed(() => {
|
||||
const bgURL = parseUrlPath(appStore.loginBackground);
|
||||
if (bgURL && bgURL !== '#') {
|
||||
return {
|
||||
backgroundImage: `url('${bgURL}')`,
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: 'cover',
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 获取登录认证源
|
||||
fnGetLoginSource();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container" :style="calcBG">
|
||||
<section v-show="!calcBG">
|
||||
<div class="animation animation1"></div>
|
||||
<div class="animation animation2"></div>
|
||||
<div class="animation animation3"></div>
|
||||
<div class="animation animation4"></div>
|
||||
<div class="animation animation5"></div>
|
||||
</section>
|
||||
|
||||
<component :is="currentComponent" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
padding-top: 164px;
|
||||
|
||||
// background: url('./../assets/black_dot.png') 0% 0% / 14px 14px repeat;
|
||||
|
||||
background-image: url(/background/light.jpg);
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
|
||||
.animation {
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background-color: #1be1f6;
|
||||
|
||||
&.animation1 {
|
||||
left: 15%;
|
||||
top: 70%;
|
||||
animation: slashStar 2s ease-in-out 0.3s infinite;
|
||||
}
|
||||
&.animation2 {
|
||||
left: 34%;
|
||||
top: 35%;
|
||||
animation: slashStar 2s ease-in-out 1.2s infinite;
|
||||
}
|
||||
&.animation3 {
|
||||
left: 10%;
|
||||
top: 8%;
|
||||
animation: slashStar 2s ease-in-out 0.5s infinite;
|
||||
}
|
||||
&.animation4 {
|
||||
left: 68%;
|
||||
top: 68%;
|
||||
animation: slashStar 2s ease-in-out 0.8s infinite;
|
||||
}
|
||||
&.animation5 {
|
||||
left: 87%;
|
||||
top: 30%;
|
||||
animation: slashStar 2s ease-in-out 1.5s infinite;
|
||||
}
|
||||
}
|
||||
@keyframes slashStar {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] .container {
|
||||
background-image: url(/background/dark.jpg);
|
||||
background-color: #141414;
|
||||
}
|
||||
</style>
|
||||
85
src/views/login/oauth2.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import {
|
||||
delAccessToken,
|
||||
delRefreshToken,
|
||||
setAccessToken,
|
||||
setRefreshToken,
|
||||
} from '@/plugins/auth-token';
|
||||
import { loginOAuth2Token } from '@/api/auth';
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
|
||||
const info = reactive({
|
||||
status: 'warning' as 'error' | 'warning' | 'success',
|
||||
title: t('views.login.authorizedNotfound'),
|
||||
subTitle: '',
|
||||
});
|
||||
|
||||
function fnToLogin() {
|
||||
router.replace({ name: 'Login' }).finally(() => {
|
||||
delAccessToken();
|
||||
delRefreshToken();
|
||||
location.replace(location.origin);
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 从URL参数中获取code和state
|
||||
const params = new URLSearchParams(location.search);
|
||||
const code = params.get('code');
|
||||
const state = params.get('state');
|
||||
if (code && state) {
|
||||
loginOAuth2Token(code, state).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
info.status = 'success';
|
||||
info.title = t('views.login.authorizedSuccess');
|
||||
info.subTitle = t('views.login.redirectHome', { i: 3 });
|
||||
setAccessToken(res.data.accessToken, res.data.refreshExpiresIn);
|
||||
setRefreshToken(res.data.refreshToken, res.data.refreshExpiresIn);
|
||||
// 3秒后跳转主页
|
||||
let i = 2;
|
||||
const timer = setInterval(() => {
|
||||
info.subTitle = t('views.login.redirectHome', { i });
|
||||
i--;
|
||||
if (i <= -1) {
|
||||
clearInterval(timer);
|
||||
/**登录后跳转主页 */
|
||||
router.replace({ name: 'Index' }).finally(() => {
|
||||
location.replace(location.origin);
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
info.status = 'error';
|
||||
info.title = t('views.login.authorizedFailed');
|
||||
info.subTitle = res.msg;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// info.subTitle = "未获取到code和state参数";
|
||||
info.subTitle = "code and state parameters not obtained";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-result
|
||||
:status="info.status"
|
||||
:title="info.title"
|
||||
v-model:subTitle="info.subTitle"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button
|
||||
type="default"
|
||||
v-if="info.status !== 'success'"
|
||||
@click="fnToLogin"
|
||||
>
|
||||
{{ t('views.login.backBtnLogin') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-result>
|
||||
</template>
|
||||
@@ -48,7 +48,7 @@ let state: StateType = reactive({
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
objectType: 'mml',
|
||||
objectType: 'General',
|
||||
param: [],
|
||||
},
|
||||
from: {
|
||||
@@ -103,7 +103,7 @@ function fnCleanFrom() {
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
objectType: 'mml',
|
||||
objectType: 'General',
|
||||
param: [],
|
||||
};
|
||||
state.from = {};
|
||||
@@ -171,6 +171,9 @@ function fnSendMML() {
|
||||
}
|
||||
cmdArr = value.split(';');
|
||||
}
|
||||
if (cmdArr.length === 1 && cmdArr[0] === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送
|
||||
state.from.sendLoading = true;
|
||||
@@ -342,7 +345,7 @@ function fnNeChange(keys: any, _: any) {
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
objectType: 'mml',
|
||||
objectType: 'General',
|
||||
param: {},
|
||||
};
|
||||
fnGetList();
|
||||
@@ -354,12 +357,12 @@ function fnGetList() {
|
||||
const neType = state.neType[0];
|
||||
state.mmlNeType = neType;
|
||||
getMMLByNE(neType).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
// 构建自动完成筛选结构
|
||||
const autoCompleteArr: Record<string, any>[] = [];
|
||||
// 构建树结构
|
||||
const treeArr: Record<string, any>[] = [];
|
||||
for (const item of res.data) {
|
||||
for (const item of res.data.rows) {
|
||||
const id = item['id'];
|
||||
const object = item['object'];
|
||||
const objectType = item['objectType'];
|
||||
@@ -540,7 +543,7 @@ onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
neCascaderOptions.value = neListStore.getNeCascaderOptions.filter(
|
||||
(item: any) => {
|
||||
return !['OMC'].includes(item.value); // 过滤不可用的网元
|
||||
return !['OMC', 'CBC', 'SGWC'].includes(item.value); // 过滤不可用的网元
|
||||
}
|
||||
);
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
@@ -680,10 +683,10 @@ onMounted(() => {
|
||||
v-model:value="state.mmlSelect.objectType"
|
||||
style="width: 20%"
|
||||
>
|
||||
<a-select-option value="mml">
|
||||
<a-select-option value="General">
|
||||
{{ t('views.mmlManage.neOperate.mml') }}
|
||||
</a-select-option>
|
||||
<a-select-option value="mml2">
|
||||
<a-select-option value="Standard">
|
||||
{{ t('views.mmlManage.neOperate.mml2') }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
|
||||
@@ -342,12 +342,12 @@ function ruleVerification(
|
||||
/**查询可选命令列表 */
|
||||
function fnGetList() {
|
||||
getMMLByUDM().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
// 构建自动完成筛选结构
|
||||
const autoCompleteArr: Record<string, any>[] = [];
|
||||
// 构建树结构
|
||||
const treeArr: Record<string, any>[] = [];
|
||||
for (const item of res.data) {
|
||||
for (const item of res.data.rows) {
|
||||
const id = item['id'];
|
||||
const object = item['object'];
|
||||
const operation = item['operation'];
|
||||
|
||||
@@ -2,7 +2,7 @@ import { addNeConfigData, editNeConfigData } from '@/api/ne/neConfig';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { readSheet } from '@/utils/execl-utils';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { reactive, toRaw } from 'vue';
|
||||
import { reactive } from 'vue';
|
||||
import saveAs from 'file-saver';
|
||||
|
||||
/**
|
||||
@@ -22,23 +22,25 @@ export default function useArrayImport({
|
||||
imeiWhitelist: {
|
||||
filename: 'import_amf_imeiWhitelist_template',
|
||||
fileetx: '.xlsx',
|
||||
itemKey: 'imeiPrefixValue',
|
||||
itemKey: 'index',
|
||||
item: (row: Record<string, any>) => {
|
||||
const index = row['Index'] || 0;
|
||||
return {
|
||||
imeiPrefixValue: row['IMEI Prefix'],
|
||||
index: 0,
|
||||
imeiPrefixValue: `${row['IMEI Prefix']}`,
|
||||
index: parseInt(index),
|
||||
};
|
||||
},
|
||||
},
|
||||
whitelist: {
|
||||
filename: 'import_amf_whitelist_template',
|
||||
fileetx: '.xlsx',
|
||||
itemKey: 'imsiValue',
|
||||
itemKey: 'index',
|
||||
item: (row: Record<string, any>) => {
|
||||
const index = row['Index'] || 0;
|
||||
return {
|
||||
imsiValue: row['IMSI Value'],
|
||||
imeiValue: row['IMEI Value/Prefix'],
|
||||
index: 0,
|
||||
imsiValue: `${row['IMSI Value']}`,
|
||||
imeiValue: `${row['IMEI Value/Prefix']}`,
|
||||
index: parseInt(index),
|
||||
};
|
||||
},
|
||||
},
|
||||
@@ -47,11 +49,12 @@ export default function useArrayImport({
|
||||
white_list: {
|
||||
filename: 'import_mme_imeiWhitelist_template',
|
||||
fileetx: '.xlsx',
|
||||
itemKey: 'imei',
|
||||
itemKey: 'index',
|
||||
item: (row: Record<string, any>) => {
|
||||
const index = row['Index'] || 0;
|
||||
return {
|
||||
imei: row['IMEI'],
|
||||
index: 0,
|
||||
imei: `${row['IMEI']}`,
|
||||
index: parseInt(index),
|
||||
};
|
||||
},
|
||||
},
|
||||
@@ -125,7 +128,7 @@ export default function useArrayImport({
|
||||
importState.loading = true;
|
||||
for (const row of rows) {
|
||||
const rowItem = importState.item(row);
|
||||
const rowKey = rowItem[importState.itemKey];
|
||||
const rowKey = rowItem[importState.itemKey] || -1;
|
||||
let result: any = null;
|
||||
// 检查index是否定义
|
||||
const has = arrayState.columnsData.find(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
@@ -17,6 +18,7 @@ import useArrayBatchDel from './hooks/useArrayBatchDel';
|
||||
import { getAllNeConfig, getNeConfigData } from '@/api/ne/neConfig';
|
||||
const neListStore = useNeListStore();
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const { ruleVerification, smfByUPFIdLoadData, smfByUPFIdOptions } = useOptions({
|
||||
t,
|
||||
});
|
||||
@@ -404,10 +406,20 @@ onMounted(() => {
|
||||
return;
|
||||
}
|
||||
// 默认选择AMF
|
||||
const item = neCascaderOptions.value.find(s => s.value === 'AMF');
|
||||
const queryNeType = (route.query.neType as string) || 'AMF';
|
||||
const queryNeId = (route.query.neId as string) || '001';
|
||||
const item = neCascaderOptions.value.find(s => s.value === queryNeType);
|
||||
if (item && item.children) {
|
||||
const info = item.children[0];
|
||||
neTypeSelect.value = [info.neType, info.neId];
|
||||
const info = item.children.find((s: any) => s.neId === queryNeId);
|
||||
if (info) {
|
||||
neTypeSelect.value = [info.neType, info.neId];
|
||||
} else {
|
||||
// 默认取第一个网元ID
|
||||
const info = item.children[0];
|
||||
if (info) {
|
||||
neTypeSelect.value = [info.neType, info.neId];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const info = neCascaderOptions.value[0].children[0];
|
||||
neTypeSelect.value = [info.neType, info.neId];
|
||||
@@ -652,7 +664,6 @@ onMounted(() => {
|
||||
</a-button>
|
||||
<TableColumnsDnd
|
||||
type="ghost"
|
||||
:cache-id="treeState.selectNode.key"
|
||||
:columns="treeState.selectNode.paramPerms.includes('get') ? [...arrayState.columns.filter((s:any)=>s.key !== 'index')] : arrayState.columns"
|
||||
v-model:columns-dnd="arrayState.columnsDnd"
|
||||
></TableColumnsDnd>
|
||||
@@ -795,7 +806,6 @@ onMounted(() => {
|
||||
</a-button>
|
||||
<TableColumnsDnd
|
||||
type="ghost"
|
||||
:cache-id="`${treeState.selectNode.key}:${arrayChildState.loc}`"
|
||||
:columns="[...arrayChildState.columns]"
|
||||
v-model:columns-dnd="arrayChildState.columnsDnd"
|
||||
v-if="arrayChildState.loc"
|
||||
|
||||
@@ -399,6 +399,7 @@ onMounted(() => {
|
||||
v-model:value="modalState.from.neType"
|
||||
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
|
||||
@change="fnNeTypeChange"
|
||||
:disabled="!!modalState.from.id"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
@@ -451,6 +452,7 @@ onMounted(() => {
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="32"
|
||||
@change="fnNeIdChange"
|
||||
:disabled="!!modalState.from.id"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
|
||||
@@ -2,7 +2,7 @@ import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { Modal, message } from 'ant-design-vue/es';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { updateNeConfigReload } from '@/api/configManage/configParam';
|
||||
import { updateNeConfigReload } from '@/api/tool/mml';
|
||||
import { serviceNeAction } from '@/api/ne/neInfo';
|
||||
import useMaskStore from '@/store/modules/mask';
|
||||
|
||||
|
||||
@@ -165,6 +165,21 @@ watch(
|
||||
placeholder="1-65535"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="UE_POOL" name="external.ue_pool">
|
||||
<a-input
|
||||
v-model:value="fromState.external.ue_pool"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> UE IP and mask </template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
@@ -244,28 +259,6 @@ watch(
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
:lg="12"
|
||||
:md="12"
|
||||
:xs="24"
|
||||
v-if="fromState.external.upf_type === 'LightUPF'"
|
||||
>
|
||||
<a-form-item label="UE_POOL" name="external.ue_pool">
|
||||
<a-input
|
||||
v-model:value="fromState.external.ue_pool"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> UE IP and maxk </template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
:lg="12"
|
||||
:md="12"
|
||||
|
||||
@@ -380,7 +380,7 @@ async function fnGraphDataBase() {
|
||||
edges: [],
|
||||
};
|
||||
// 添加基础网元
|
||||
for (const item of neListStore.getNeSelectOtions) {
|
||||
for (const item of neListStore.getNeCascaderOptions) {
|
||||
if ('OMC' === item.value) {
|
||||
if (item.children?.length === 0) continue;
|
||||
// 是否存在OMC保证唯一
|
||||
|
||||
@@ -232,12 +232,12 @@ const modalStateFrom = Form.useForm(
|
||||
],
|
||||
imsi: [
|
||||
{ required: true, message: 'IMSI' + t('common.unableNull') },
|
||||
{ min: 15, max: 15, message: t('views.neUser.auth.imsiConfirm') },
|
||||
{ min: 15, message: t('views.neUser.auth.imsiConfirm') },
|
||||
],
|
||||
amf: [{ required: true, message: 'AMF' + t('common.unableNull') }],
|
||||
ki: [
|
||||
{ required: true, message: 'KI' + t('common.unableNull') },
|
||||
{ min: 32, max: 32, message: t('views.neUser.auth.kiTip') },
|
||||
{ min: 32, message: t('views.neUser.auth.kiTip') },
|
||||
],
|
||||
algoIndex: [
|
||||
{ required: true, message: 'algoIndex' + t('common.unableNull') },
|
||||
@@ -865,6 +865,7 @@ onMounted(() => {
|
||||
ok-text="TXT"
|
||||
ok-type="default"
|
||||
@confirm="fnExportList('txt')"
|
||||
v-if="false"
|
||||
>
|
||||
<a-button type="dashed">
|
||||
<template #icon><ExportOutlined /></template>
|
||||
@@ -890,6 +891,7 @@ onMounted(() => {
|
||||
ok-type="default"
|
||||
@confirm="fnRecordExport('txt')"
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
v-if="false"
|
||||
>
|
||||
<a-button
|
||||
type="default"
|
||||
|
||||
@@ -5,7 +5,7 @@ import { 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 { listN3iwf } from '@/api/neUser/n3iwf';
|
||||
import { listN3IWFSubList } from '@/api/neUser/n3iwf';
|
||||
import useNeListStore from '@/store/modules/ne_list';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
@@ -91,10 +91,10 @@ function fnTableSize({ key }: MenuInfo) {
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
listN3iwf(toRaw(queryParams)).then(res => {
|
||||
listN3IWFSubList(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const { total, rows } = res.data;
|
||||
tableState.data = rows;
|
||||
tableState.data = rows;
|
||||
} else {
|
||||
tableState.data = [];
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PageContainer } from 'antdv-pro-layout';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import { listNSSF } from '@/api/neUser/nssf';
|
||||
import { listNSSFSubList } from '@/api/neUser/nssf';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
const { t } = useI18n();
|
||||
@@ -63,7 +63,7 @@ function fnTableSize({ key }: MenuInfo) {
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
listNSSF().then(res => {
|
||||
listNSSFSubList().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PageContainer } from 'antdv-pro-layout';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import { listNSSFAMF } from '@/api/neUser/nssfAmf';
|
||||
import { listNSSFAmfList } from '@/api/neUser/nssf';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
const { t } = useI18n();
|
||||
@@ -57,7 +57,7 @@ function fnTableSize({ key }: MenuInfo) {
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
listNSSFAMF().then(res => {
|
||||
listNSSFAmfList().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
|
||||
@@ -92,8 +92,8 @@ let tableColumns: ColumnsType = [
|
||||
},
|
||||
{
|
||||
title: t('views.perfManage.customTarget.expression'),
|
||||
dataIndex: 'exprAlias',
|
||||
align: 'center',
|
||||
dataIndex: 'expression',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('views.perfManage.customTarget.description'),
|
||||
@@ -354,7 +354,6 @@ function fnModalVisibleByEdit(row?: any, id?: any) {
|
||||
} else {
|
||||
fnSelectPerformanceInit(row.neType);
|
||||
modalState.from = Object.assign(modalState.from, row);
|
||||
modalState.from.expression = modalState.from.exprAlias;
|
||||
modalState.title = t('views.perfManage.customTarget.editCustom');
|
||||
modalState.openByEdit = true;
|
||||
}
|
||||
|
||||
@@ -166,13 +166,14 @@ let queryParams: any = reactive({
|
||||
/**网元标识 */
|
||||
neId: '',
|
||||
/**开始时间 */
|
||||
startTime: '',
|
||||
beginTime: '',
|
||||
/**结束时间 */
|
||||
endTime: '',
|
||||
/**排序字段 */
|
||||
sortField: 'created_at',
|
||||
sortField: 'timeGroup',
|
||||
/**排序方式 */
|
||||
sortOrder: 'desc',
|
||||
interval: 60,
|
||||
});
|
||||
|
||||
/**表格分页、排序、筛选变化时触发操作, 排序方式,取值为 ascend descend */
|
||||
@@ -306,7 +307,21 @@ function fnRecordExport() {
|
||||
for (const key of keys) {
|
||||
if (tableColumnsKeyArr[i] === key) {
|
||||
const title = tableColumnsTitleArr[i];
|
||||
kpiData[title] = item[key];
|
||||
if (key == 'timeGroup') {
|
||||
kpiData[title] = parseDateToStr(item[key]);
|
||||
} else if (key === 'neName' || key === 'startIndex') {
|
||||
kpiData[title] = item[key];
|
||||
} else {
|
||||
const v = parseFloat(item[key]);
|
||||
let kpiV = v.toFixed(3); // 有小数部分,保留 3 位小数
|
||||
// 判断数字是否有小数部分
|
||||
if (Math.abs(v) < 0.001) {
|
||||
kpiV = '0'; // 如果数字非常小,返回 0
|
||||
} else if (v % 1 === 0) {
|
||||
kpiV = v.toFixed(0); // 没有小数部分,保留 0 位小数
|
||||
}
|
||||
kpiData[title] = kpiV;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,7 +350,7 @@ function fnGetListTitle() {
|
||||
if (!state.neType[0]) return false;
|
||||
|
||||
// 获取表头文字
|
||||
listCustom({ neType: state.neType[0], status: '1' })
|
||||
listCustom({ neType: state.neType[0], status: '1', pageNum: 1, pageSize: 50 })
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
if (res.data.rows.length === 0) {
|
||||
@@ -367,6 +382,16 @@ function fnGetListTitle() {
|
||||
width: 100,
|
||||
minWidth: 150,
|
||||
maxWidth: 300,
|
||||
customRender: (opt: any) => {
|
||||
const num = parseFloat(opt.text);
|
||||
// 判断数字是否有小数部分
|
||||
if (Math.abs(num) < 0.001) {
|
||||
return '0'; // 如果数字非常小,返回 0
|
||||
} else if (num % 1 === 0) {
|
||||
return num.toFixed(0); // 没有小数部分,保留 0 位小数
|
||||
}
|
||||
return num.toFixed(3); // 有小数部分,保留 3 位小数
|
||||
},
|
||||
});
|
||||
}
|
||||
columns.push({
|
||||
@@ -384,6 +409,9 @@ function fnGetListTitle() {
|
||||
key: 'timeGroup',
|
||||
sorter: true,
|
||||
width: 100,
|
||||
customRender: (opt: any) => {
|
||||
return parseDateToStr(opt.text);
|
||||
},
|
||||
});
|
||||
|
||||
nextTick(() => {
|
||||
@@ -409,7 +437,7 @@ function fnGetList() {
|
||||
tableState.loading = true;
|
||||
queryParams.neType = state.neType[0];
|
||||
queryParams.neId = state.neType[1];
|
||||
queryParams.startTime = queryRangePicker.value[0];
|
||||
queryParams.beginTime = queryRangePicker.value[0];
|
||||
queryParams.endTime = queryRangePicker.value[1];
|
||||
listCustomData(toRaw(queryParams))
|
||||
.then(res => {
|
||||
@@ -452,20 +480,53 @@ function fnGetList() {
|
||||
});
|
||||
|
||||
// 计算总值
|
||||
const total = Number(
|
||||
values.reduce((sum, val) => sum + val, 0).toFixed(2)
|
||||
);
|
||||
const totalV = values.reduce((sum, val) => sum + val, 0);
|
||||
let total = totalV.toFixed(3);
|
||||
// 判断数字是否有小数部分
|
||||
if (Math.abs(totalV) < 0.001) {
|
||||
total = '0'; // 如果数字非常小,返回 0
|
||||
} else if (totalV % 1 === 0) {
|
||||
total = totalV.toFixed(0); // 没有小数部分,保留 0 位小数
|
||||
}
|
||||
|
||||
// 计算平均值
|
||||
const avg =
|
||||
values.length > 0 ? Number((total / values.length).toFixed(2)) : 0;
|
||||
const avgV = values.length > 0 ? totalV / values.length : 0;
|
||||
let avg = avgV.toFixed(3);
|
||||
// 判断数字是否有小数部分
|
||||
if (Math.abs(avgV) < 0.001) {
|
||||
avg = '0'; // 如果数字非常小,返回 0
|
||||
} else if (avgV % 1 === 0) {
|
||||
avg = avgV.toFixed(0); // 没有小数部分,保留 0 位小数
|
||||
}
|
||||
|
||||
// 计算最大值
|
||||
const maxV = values.length > 0 ? Math.max(...values) : 0;
|
||||
let max = maxV.toFixed(3);
|
||||
// 判断数字是否有小数部分
|
||||
if (Math.abs(maxV) < 0.001) {
|
||||
max = '0'; // 如果数字非常小,返回 0
|
||||
} else if (maxV % 1 === 0) {
|
||||
max = maxV.toFixed(0); // 没有小数部分,保留 0 位小数
|
||||
}
|
||||
|
||||
// 计算最小值
|
||||
const minV = values.length > 0 ? Math.min(...values) : 0;
|
||||
let min = minV.toFixed(3);
|
||||
// 判断数字是否有小数部分
|
||||
if (Math.abs(minV) < 0.001) {
|
||||
min = '0'; // 如果数字非常小,返回 0
|
||||
} else if (minV % 1 === 0) {
|
||||
min = minV.toFixed(0); // 没有小数部分,保留 0 位小数
|
||||
}
|
||||
|
||||
kpiStats.value.push({
|
||||
kpiId: columns.key,
|
||||
title: columns.title,
|
||||
unit: columns.unit,
|
||||
max: values.length > 0 ? Math.max(...values) : 0,
|
||||
min: values.length > 0 ? Math.min(...values) : 0,
|
||||
max,
|
||||
min,
|
||||
avg,
|
||||
total: total,
|
||||
total,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -638,8 +699,17 @@ function fnRanderChartData() {
|
||||
for (const y of chartDataYSeriesData) {
|
||||
for (const key of keys) {
|
||||
if (y.key === key) {
|
||||
y.data.push(+item[key]);
|
||||
chartDataXAxisData.push(item['timeGroup']);
|
||||
// 计算最小值
|
||||
const v = parseFloat(item[key]);
|
||||
let kpiV = v.toFixed(3);
|
||||
// 判断数字是否有小数部分
|
||||
if (Math.abs(v) < 0.001) {
|
||||
kpiV = '0'; // 如果数字非常小,返回 0
|
||||
} else if (v % 1 === 0) {
|
||||
kpiV = v.toFixed(0); // 没有小数部分,保留 0 位小数
|
||||
}
|
||||
y.data.push(kpiV);
|
||||
chartDataXAxisData.push(parseDateToStr(item['timeGroup']));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -749,7 +819,7 @@ function wsMessage(res: Record<string, any>) {
|
||||
// x轴
|
||||
if (key === 'timeGroup') {
|
||||
// chartDataXAxisData.shift();
|
||||
chartDataXAxisData.push(v);
|
||||
chartDataXAxisData.push(parseDateToStr(v));
|
||||
continue;
|
||||
}
|
||||
// y轴
|
||||
@@ -879,7 +949,7 @@ watch(
|
||||
onMounted(() => {
|
||||
// 目前支持的 AMF AUSF MME MOCNGW NSSF SMF UDM UPF PCF
|
||||
// 获取网元网元列表
|
||||
listCustom({ status: '1' }).then((res: any) => {
|
||||
listCustom({ status: '1', pageNum: 1, pageSize: 200 }).then((res: any) => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
if (!res.data.rows.length) {
|
||||
message.warning({
|
||||
@@ -925,9 +995,9 @@ onMounted(() => {
|
||||
const now = new Date();
|
||||
now.setMinutes(0, 0, 0);
|
||||
// 设置起始时间为整点前一小时
|
||||
const startTime = new Date(now);
|
||||
startTime.setHours(now.getHours() - 1);
|
||||
queryRangePicker.value[0] = `${startTime.getTime()}`;
|
||||
const beginTime = new Date(now);
|
||||
beginTime.setHours(now.getHours() - 1);
|
||||
queryRangePicker.value[0] = `${beginTime.getTime()}`;
|
||||
// 设置结束时间为整点
|
||||
const endTime = new Date(now);
|
||||
endTime.setMinutes(59, 59, 59);
|
||||
|
||||
1976
src/views/perfManage/overview/index.vue
Normal file
@@ -245,6 +245,21 @@ let modalState: ModalStateType = reactive({
|
||||
*/
|
||||
function fnModalVisibleByVive(row: Record<string, string>) {
|
||||
modalState.from = Object.assign(modalState.from, row);
|
||||
try {
|
||||
modalState.from.operaParam = JSON.stringify(
|
||||
JSON.parse(modalState.from.operaParam),
|
||||
null,
|
||||
4
|
||||
);
|
||||
} catch (_) {}
|
||||
try {
|
||||
modalState.from.operaMsg = JSON.stringify(
|
||||
JSON.parse(modalState.from.operaMsg),
|
||||
null,
|
||||
4
|
||||
);
|
||||
} catch (_) {}
|
||||
|
||||
modalState.title = t('views.system.log.operate.logInfo');
|
||||
modalState.openByView = true;
|
||||
}
|
||||
|
||||
1122
src/views/system/login-source/index.vue
Normal file
@@ -44,10 +44,13 @@ const { t } = useI18n();
|
||||
let dict: {
|
||||
/**状态 */
|
||||
sysNormalDisable: DictType[];
|
||||
/**用户类型 */
|
||||
sysUserType: DictType[];
|
||||
/**性别 */
|
||||
sysUserSex: DictType[];
|
||||
} = reactive({
|
||||
sysNormalDisable: [],
|
||||
sysUserType: [],
|
||||
sysUserSex: [],
|
||||
});
|
||||
|
||||
@@ -64,6 +67,8 @@ let queryParams = reactive({
|
||||
deptId: undefined,
|
||||
/**用户状态 */
|
||||
statusFlag: undefined,
|
||||
/**用户类型 */
|
||||
userType: undefined,
|
||||
/**记录开始时间 */
|
||||
beginTime: undefined as number | undefined,
|
||||
/**记录结束时间 */
|
||||
@@ -81,6 +86,7 @@ function fnQueryReset() {
|
||||
phone: '',
|
||||
deptId: undefined,
|
||||
statusFlag: undefined,
|
||||
userType: undefined,
|
||||
beginTime: undefined,
|
||||
endTime: undefined,
|
||||
pageNum: 1,
|
||||
@@ -148,6 +154,13 @@ let tableColumns: ColumnsType = [
|
||||
align: 'left',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.system.user.userType'),
|
||||
dataIndex: 'userType',
|
||||
key: 'userType',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.system.user.loginIp'),
|
||||
dataIndex: 'loginIp',
|
||||
@@ -792,13 +805,17 @@ onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('sys_normal_disable'),
|
||||
getDict('sys_user_type'),
|
||||
getDict('sys_user_sex'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysNormalDisable = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.sysUserSex = resArr[1].value;
|
||||
dict.sysUserType = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.sysUserSex = resArr[2].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
@@ -841,7 +858,7 @@ onMounted(() => {
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="4" :md="12" :xs="24">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.user.status')"
|
||||
name="statusFlag"
|
||||
@@ -855,6 +872,20 @@ onMounted(() => {
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.user.userType')"
|
||||
name="userType"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="queryParams.userType"
|
||||
allow-clear
|
||||
:options="dict.sysUserType"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.user.loginTime')"
|
||||
@@ -1025,6 +1056,12 @@ onMounted(() => {
|
||||
{{ r.roleName }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'userType'">
|
||||
<DictTag :options="dict.sysUserType" :value="record.userType" />
|
||||
<a-tag :bordered="false" v-if="record.userSource != '#'">
|
||||
{{ record.userSource }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'deptId'">
|
||||
{{ record.dept?.deptName }}
|
||||
</template>
|
||||
@@ -1072,7 +1109,9 @@ onMounted(() => {
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
placement="topLeft"
|
||||
v-if="record.userId != userStore.userId"
|
||||
v-if="
|
||||
record.userId != userStore.userId && record.userSource == '#'
|
||||
"
|
||||
>
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button
|
||||
@@ -1085,7 +1124,9 @@ onMounted(() => {
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
placement="topLeft"
|
||||
v-if="record.userId != userStore.userId"
|
||||
v-if="
|
||||
record.userId != userStore.userId && record.userSource == '#'
|
||||
"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('views.system.user.resetPwd') }}
|
||||
|
||||
@@ -248,7 +248,7 @@ function fnRecordStop(id: string) {
|
||||
content: t('views.traceManage.task.stopTaskTip', { id }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
stopTaskHLR({ id })
|
||||
stopTaskHLR({ id: `${id}` })
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
|
||||
@@ -31,6 +31,8 @@ const wk = new wkUtil.WK();
|
||||
|
||||
/**跟踪编号 */
|
||||
const traceId = ref<string>(route.query.traceId as string);
|
||||
/**任务编号 */
|
||||
const id = ref<string>(route.query.id as string);
|
||||
|
||||
/**关闭跳转 */
|
||||
function fnClose() {
|
||||
@@ -588,7 +590,7 @@ onBeforeUnmount(() => {
|
||||
<strong>{{ traceId }}</strong>
|
||||
</span>
|
||||
<!-- 任务信息 -->
|
||||
<TaskInfoIcon :trace-id="traceId" />
|
||||
<TaskInfoIcon :id="id" />
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
const props = defineProps({
|
||||
traceId: {
|
||||
id: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
@@ -50,7 +50,7 @@ onMounted(() => {
|
||||
})
|
||||
.finally(() => {
|
||||
// 获取信息
|
||||
fnGetTaskInfo(props.traceId);
|
||||
fnGetTaskInfo(props.id);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -23,6 +23,8 @@ const route = useRoute();
|
||||
|
||||
/**跟踪编号 */
|
||||
const traceId = ref<string>(route.query.traceId as string);
|
||||
/**任务编号 */
|
||||
const id = ref<string>(route.query.id as string);
|
||||
|
||||
/**关闭跳转 */
|
||||
function fnClose() {
|
||||
@@ -431,7 +433,7 @@ onMounted(() => {
|
||||
<strong>{{ traceId }}</strong>
|
||||
</span>
|
||||
<!-- 任务信息 -->
|
||||
<TaskInfoIcon :trace-id="traceId" />
|
||||
<TaskInfoIcon :id="id" />
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -592,10 +592,10 @@ function fnModalCancel() {
|
||||
}
|
||||
|
||||
/**跳转内嵌详情页面 */
|
||||
function fnRecordView(traceId: any, type: 'analyze' | 'data') {
|
||||
function fnRecordView(record: any, type: 'analyze' | 'data') {
|
||||
router.push({
|
||||
path: `${route.path}${MENU_PATH_INLINE}/${type}`,
|
||||
query: { traceId },
|
||||
query: { traceId: record.traceId, id: record.id },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -795,7 +795,7 @@ onMounted(() => {
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordView(record.traceId, 'data')"
|
||||
@click.prevent="fnRecordView(record, 'data')"
|
||||
>
|
||||
<template #icon><ContainerOutlined /></template>
|
||||
</a-button>
|
||||
@@ -808,7 +808,7 @@ onMounted(() => {
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordView(record.traceId, 'analyze')"
|
||||
@click.prevent="fnRecordView(record, 'analyze')"
|
||||
>
|
||||
<template #icon><BarsOutlined /></template>
|
||||
</a-button>
|
||||
|
||||