Merge remote-tracking branch 'origin/lichang'

This commit is contained in:
TsMask
2024-12-20 18:31:28 +08:00
29 changed files with 2103 additions and 2299 deletions

42
src/api/neData/sgwc.ts Normal file
View File

@@ -0,0 +1,42 @@
import { request } from '@/plugins/http-fetch';
/**
* 查询SGWC-CDR会话事件
* @param query 查询参数
* @returns object
*/
export function listSGWCDataCDR(query: Record<string, any>) {
return request({
url: '/neData/sgwc/cdr/list',
method: 'get',
params: query,
});
}
/**
* SGWC-CDR会话删除
* @param id 信息ID
* @returns object
*/
export function delSGWCDataCDR(cdrIds: string | number) {
return request({
url: `/neData/sgwc/cdr/${cdrIds}`,
method: 'delete',
timeout: 60_000,
});
}
/**
* SGWC-CDR会话列表导出
* @param data 查询列表条件
* @returns object
*/
export function exportSGWCDataCDR(data: Record<string, any>) {
return request({
url: '/neData/sgwc/cdr/export',
method: 'post',
data,
responseType: 'blob',
timeout: 60_000,
});
}

View File

@@ -41,14 +41,27 @@ export function exportSMFDataCDR(data: Record<string, any>) {
});
}
/**
* SMF-在线订阅用户数量
* @param query 查询参数
* @returns object
*/
export function listSMFSubNum(neId: string) {
return request({
url: '/neData/smf/sub/num',
method: 'get',
params: { neId },
});
}
/**
* SMF-在线订阅用户列表信息
* @param query 查询参数
* @returns object
*/
export function listSMFSubscribers(query: Record<string, any>) {
export function listSMFSubList(query: Record<string, any>) {
return request({
url: '/neData/smf/subscribers',
url: '/neData/smf/sub/list',
method: 'get',
params: query,
});

View File

@@ -4,6 +4,7 @@ export const NE_TYPE_LIST = [
'IMS',
'AMF',
'AUSF',
'UDR',
'UDM',
'SMF',
'PCF',
@@ -19,6 +20,8 @@ export const NE_TYPE_LIST = [
'SMSF',
'CBC',
'CHF',
'HLR',
'SGWC',
];
/**

View File

@@ -222,12 +222,11 @@ export default {
capability: 'Capability',
serialNum: 'Serial Number',
expiryDate: 'Expiry Date',
neStatus: 'NE status is abnormal',
runStatus:'Running Status',
mark:'Brief Information',
neStatus: 'Status Abnormal',
runStatus:'Status',
mark:'Information',
object:'Object',
versionNum:'Version',
systemStatus:'Status',
realNeStatus:'Status',
reloadTime:'Refresh Time',
Critical:'Critical',
@@ -351,163 +350,6 @@ export default {
description: "No data yet, try refreshing",
},
},
configManage: {
neManage: {
addNe:'Add Network Element',
delSure:'Confirm deleting the data item with network element name {msg}',
editNe:'Edit Network Element',
exportSure:'Confirm exporting the configuration information with the network element name {msg}',
exportTip:'Export successful, please go to backup management for download',
getInfo:'Failed to get network element information',
neType:'NE Type',
neTypePlease: 'Select network element type',
neId:'NE ID',
neName:'NE Name',
neTypeTip:'Fill in the type of network element created, such as:SMF',
uid:'RM UID',
uidTip:'Please enter a unique resource identifier',
ip:'IP Address',
mac:'NE MAC address',
macTip:'Able to locate the physical address (MAC) of the network element',
port:'Port',
portTip:'Maximum range 0~65535',
pvflag:'PV Flag',
pnf:'Physical Network Element',
vnf:'Virtual Network Element',
province:'Region',
vendorName:'Vendor Name',
dn:'Network Identification',
reload: 'Reload',
restart: 'Restart',
totalSure:'Confirm the network element with {operator} network element name {msg}',
stop: 'Stop',
start: 'Start',
log: 'Logs',
export: 'Export',
import: 'Import',
fileForm:'File Source',
selectPlease:'Please select the source of the import file',
server:'Server File',
local:'Local File',
fileSelect:'Please select the current import file',
sync:'Synchronize to NE',
open:'Open',
close:'Close',
addFail:'Add failed',
operFail:'Operation Failed'
},
backupManage: {
setBackupTask: 'Set automatic backup time',
neTypePlease: 'Query network element type',
neType: 'NE Type',
neID: 'NE ID',
fileName: 'File Name',
createAt: 'Create at',
remark:'Remark',
edit:'Edit Backup File',
totalSure:'Confirm that {oper} records item number {id}?',
},
softwareManage: {
sendBtn: 'Distribute',
runBtn: 'Activate',
backBtn: 'Rollback',
historyBtn: 'Distribution Record',
neTypePlease: 'Select network element type',
neType: 'NE Type',
fileName: 'File Name',
version: 'Version',
versionPlease: 'Version number cannot be empty',
updateTime: 'Uploaded Time',
description: 'Description',
deleteTip: 'Are you sure to delete the data item with software [{fileName}]?',
downloadTip: 'Are you sure to download the data item with software [{fileName}]?',
updateComment: 'Comment',
updateCommentPlease: 'Please enter the software description',
updateFile: 'Software File',
updateFilePlease: 'Please upload the updated software file',
verifyFile: 'Verify File',
selectFile: 'SELECT FILE',
sendTitle: 'Distribute software version',
sendContent: 'Are you sure to send the file with the software package [{fileName}] to the corresponding network element?',
runTitle: 'Activate software version',
runContent: 'Are you sure to activate the software version of [{fileName}] that has been issued to the corresponding network element?',
backTitle: 'Fallback software version',
backContent: 'Confirm that the software version of [{fileName}] has been issued for the corresponding network element rollback?',
neId: 'Corresponding network element',
neIdPlease: 'Please select the corresponding network element',
versions:'Version',
upVersions:'Version before upgrade',
backVersions:'Version before rollback',
status:'Status',
letUpTime:'Activation time',
createTime:'Creation time',
onlyAble:'Only upload file format {fileText} is supported',
nullVersion:'There is no rollback version for the current network element.',
},
license: {
neTypePlease: 'Select network element type',
neType: 'NE Type',
serialNum: 'Serial Num',
createTime: 'Time',
comment: 'Description',
updateComment: 'License Description',
updateCommentPlease: 'Please enter a license description',
updateFile: 'License File',
updateFilePlease: 'Please upload and update the License file',
selectFile: 'SELECT FILE',
neId: 'NE ID',
neIdPlease: 'Please select the corresponding network element',
},
configParam:{
dataNull:'No configuration item data yet',
editSuss:'Modification successful',
editFail:'Edit failed',
Unable:'Illegal operation of attribute value',
delSure:'Confirm to delete the data item with Index [{value}]?',
addSuss:'Add successfully',
addFail:'Add failed',
delArraySure:'Confirm to delete the data item with {arrayChildTitle} Index as [{value}]?',
parUnable:'The parameter value is not within the reasonable range',
ipv4Tip:'Not a legal IPV4 address',
ipv6Tip:'Not a legal IPV6 address',
enumTip:'Not a reasonable enumeration value',
boolTip:'Not a reasonable Boolean value',
default:'The input value is of unknown type',
reloadSuss:'Network element reloading completed',
reloadFail:'Network element reloading failed',
neNUll:'No network element list data yet',
reload:'Reload',
post:'Submit',
editSure:'Are you sure you want to update this attribute value? ',
arraryEdit:'Are you sure to submit the record whose updated Index is [{value}]? ',
addSure:'Are you sure to submit the new record of Index: [{value}]? '
},
configParamForm: {
treeTitle: "Navigation Configuration",
treeSelectTip: "Select configuration item information in the left configuration navigation!",
neType: 'NE Type',
neTypePleace: "Please select the network element type",
noConfigData: "No data on configuration items",
updateValue: "[ {num} ] parameter value modified successfully.",
updateValueErr: "Attribute value modification failure",
updateItem: "Modify Index to {num}.",
updateItemErr: "Record modification failure",
delItemOk: "Deleting Index as {num} succeeded",
addItemOk: "Add Index as {num} Record Succeeded",
addItemErr: "Record addition failure",
requireUn: "[ {display} ] input value is of unknown type",
requireString: "[ {display} ] parameter value is invalid.",
requireInt: "[ {display} ] parameter value not in reasonable range {filter}",
requireIpv4: "[ {display} ] not a legitimate IPV4 address",
requireIpv6: "[ {display} ] not a legitimate IPV6 address.",
requireEnum: "[ {display} ] is not a reasonable enumeration value.",
requireBool: "[ {display} ] is not a reasonable boolean value.",
editOkTip: "Confirm updating the value of this [ {num} ] attribute?",
updateItemTip: "Confirm updating the data item with Index [{num}]?",
delItemTip: "Confirm deleting the data item with Index [{num}]?",
arrayMore: "Expand",
},
},
dashboard: {
overview:{
title: "Core Network Dashboard",
@@ -579,14 +421,18 @@ export default {
resultFail: "Fail",
delTip: "Confirm deletion of the data item numbered [{msg}]?",
exportTip: "Do you confirm to export the current query conditions of the CDR data? (Maximum 10,000 items can be exported.)",
smfChargingID: 'Charging ID',
chargingID: 'Charging ID',
smfSubscriptionIDData: 'Subscription ID Data',
smfSubscriptionIDType: 'Subscription ID Type',
smfDataVolumeUplink: 'Data Volume Uplink',
smfDataVolumeDownlink: 'Data Volume Downlink',
smfDataTotalVolume: 'Data Total Volume',
smfDuration: 'Duration',
smfInvocationTime: 'Invocation Time',
durationTime: 'Duration',
invocationTime: 'Invocation Time',
sgwcServedIMSI: 'IMSI',
sgwcServedMSISDN: 'MSISDN',
sgwcVolumeGPRSUplink: 'GPRS Uplink',
sgwcVolumeGPRSDownlink: 'GPRS Downlink',
},
ue: {
eventType: "Event Type",
@@ -784,7 +630,9 @@ export default {
treeSelectTip: "Select configuration item information in the left configuration navigation!",
neType: 'NE Type',
neTypePleace: "Please select the network element type",
neIdSyncPleace: "Please select the synchronized network element",
noConfigData: "No data on configuration items",
noConfigdDisabled: "The configuration item is not normal",
updateValue: "[ {num} ] parameter value modified successfully.",
updateValueErr: "Attribute value modification failure",
updateItem: "Modify Index to {num}.",

View File

@@ -222,13 +222,12 @@ export default {
capability: '用户容量',
serialNum: '序列号',
expiryDate: '许可证到期日期',
neStatus:'网元状态异常',
neStatus:'状态异常',
runStatus:'运行状态',
mark:'简略信息',
mark:'信息',
object:'对象',
versionNum:'版本号',
systemStatus:'系统状态',
realNeStatus:'网元状态',
realNeStatus:'状态',
reloadTime:'刷新时间',
Critical:'严重告警',
Major:'主要告警',
@@ -351,163 +350,6 @@ export default {
description: "暂无数据,尝试刷新看看",
},
},
configManage: {
neManage: {
addNe:'添加网元',
delSure:'确认删除网元名称为{msg}的数据项 ',
editNe:'修改网元',
exportSure:'确认导出网元名称为 {msg} 的配置信息',
exportTip:'导出成功,请到备份管理进行下载',
getInfo:'获取网元信息失败',
neType:'网元类型',
neTypePlease: '请输入网元类型',
neId:'网元内部标识',
neName:'网元名称',
neTypeTip:'填写创建的网元类型,如:SMF',
uid:'资源唯一标识',
uidTip:'请输入资源唯一标识',
ip:'IP地址',
mac:'网元物理地址',
macTip:'能够定位网元的物理地址(MAC)',
port:'端口',
portTip:'最大范围0~65535',
pvflag:'网元虚拟化标识',
pnf:'物理网元',
vnf:'虚拟网元',
province:'网元服务省份',
vendorName:'厂商名称',
dn:'网络标识',
reload: '重载',
restart: '重启',
totalSure:'确认{oper}网元名称为 {msg} 的网元',
stop: '停止',
start: '启动',
log: '日志',
export: '导出',
import: '导入',
fileForm:'文件来源',
selectPlease:'请选择导入文件来源',
server:'服务器文件',
local:'本地文件',
fileSelect:'请选择当前导入文件',
sync:'同步到网元',
open:'开',
close:'关',
addFail:'新增失败',
operFail:'操作失败'
},
backupManage: {
setBackupTask: '设置自动备份时间',
neTypePlease: '查询网元类型',
neType: '网元类型',
neID: '网元内部标识',
fileName: '文件名',
createAt: '创建时间',
remark:'备份说明',
edit:'编辑备份文件',
totalSure:'确认{oper}记录编号为 {id} 的数据项?',
},
softwareManage: {
sendBtn: '下发',
runBtn: '激活',
backBtn: '回退',
historyBtn: '下发记录',
neTypePlease: '选择网元类型',
neType: '网元类型',
fileName: '文件名',
version: '版本号',
versionPlease: '版本号不能为空',
updateTime: '上传时间',
description: '功能描述',
deleteTip: '确认删除 【{fileName}】 的软件数据项?',
downloadTip: '确认下载 【{fileName}】 的软件数据项?',
updateComment: '软件说明',
updateCommentPlease: '请输入软件说明',
updateFile: '软件文件',
updateFilePlease: '请上传更新软件文件',
verifyFile: '校验文件',
selectFile: '选择文件',
sendTitle: '下发软件版本',
sendContent: '确认下发软件包为【{fileName}】的文件到对应网元?',
runTitle: '激活软件版本',
runContent: '确认在对应网元激活已下发【{fileName}】的软件版本?',
backTitle: '回退软件版本',
backContent: '确认在对应网元回退已下发【{fileName}】的软件版本?',
neId: '对应网元',
neIdPlease: '请选择对应网元',
versions:'版本',
upVersions:'升级前版本',
backVersions:'回退前版本',
status:'状态',
letUpTime:'激活时间',
createTime:'创建时间',
onlyAble:'只支持上传文件格式 {fileText}',
nullVersion:'当前网元无可回退版本',
},
license: {
neTypePlease: '选择网元类型',
neType: '网元类型',
serialNum: '序列号',
createTime: '时间',
comment: '说明',
updateComment: 'License说明',
updateCommentPlease: '请输入License说明',
updateFile: 'License文件',
updateFilePlease: '请上传更新License文件',
selectFile: '选择文件',
neId: '网元内部标识',
neIdPlease: '请选择对应网元',
},
configParam:{
dataNull:'暂无配置项数据',
editSuss:'修改成功',
editFail:'修改失败',
unable:'非法操作属性值',
delSure:'确认删除Index为 【{value}】 的数据项?',
addSuss:'新增成功',
addFail:'新增失败',
delArraySure:'确认删除{arrayChildTitle} Index 为 【{value}】 的数据项?',
parUnable:'参数值不在合理范围',
ipv4Tip:'不是合法的IPV4地址',
ipv6Tip:'不是合法的IPV6地址',
enumTip:'不是合理的枚举值',
boolTip:'不是合理的布尔类型的值',
default:'输入值是未知类型',
reloadSuss:'网元重新加载完成',
reloadFail:'网元重新加载失败',
neNUll:'暂无网元列表数据',
reload:'重载',
post:'提交',
editSure:'确认更新该属性值吗?',
arraryEdit:'确认提交更新 Index 为 【{value}】 的记录吗?',
addSure:'确认提交新增 Index :【{value}】 的记录吗?',
},
configParamForm: {
treeTitle: "配置导航",
treeSelectTip: "左侧配置导航中选择配置项信息!",
neType: "网元类型",
neTypePleace: "请选择网元类型",
noConfigData: "暂无配置项数据",
updateValue: "【 {num} 】 属性值修改成功",
updateValueErr: "属性值修改失败",
updateItem: "修改 Index 为 {num} 记录成功",
updateItemErr: "记录修改失败",
delItemOk: "删除 Index 为 {num} 记录成功",
addItemOk: "新增 Index 为 {num} 记录成功",
addItemErr: "记录新增失败",
requireUn: "【 {display} 】输入值是未知类型",
requireString: "【 {display} 】参数值不合理",
requireInt: "【 {display} 】参数值不在合理范围 {filter}",
requireIpv4: "【 {display} 】不是合法的IPV4地址",
requireIpv6: "【 {display} 】不是合法的IPV6地址",
requireEnum: "【 {display} 】不是合理的枚举值",
requireBool: "【 {display} 】不是合理的布尔类型的值",
editOkTip: "确认更新该【 {num} 】属性值吗?",
updateItemTip: "确认更新Index为 【{num}】 的数据项?",
delItemTip: "确认删除Index为 【{num}】 的数据项?",
arrayMore: "展开",
},
},
dashboard: {
overview:{
title: "核心网系统看板",
@@ -579,14 +421,18 @@ export default {
resultFail: "失败",
delTip: "确认删除编号为【{msg}】的数据项?",
exportTip: "确认导出当前查询条件的话单数据吗?(导出最大支持一万条)",
smfChargingID: '计费ID',
chargingID: '计费ID',
smfSubscriptionIDData: '订阅 ID 数据',
smfSubscriptionIDType: '订阅 ID 类型',
smfDataVolumeUplink: '数据量上行链路',
smfDataVolumeDownlink: '数据量下行链路',
smfDataTotalVolume: '数据总量',
smfDuration: '持续时间',
smfInvocationTime: '调用时间',
durationTime: '持续时间',
invocationTime: '调用时间',
sgwcServedIMSI: 'IMSI',
sgwcServedMSISDN: 'MSISDN',
sgwcVolumeGPRSUplink: 'GPRS 上行链路',
sgwcVolumeGPRSDownlink: 'GPRS 下行链路',
},
ue: {
eventType: "事件类型",
@@ -784,7 +630,9 @@ export default {
treeSelectTip: "左侧配置导航中选择配置项信息!",
neType: "网元类型",
neTypePleace: "请选择网元类型",
neIdSyncPleace: "请选择同步网元",
noConfigData: "暂无配置项数据",
noConfigdDisabled: "配置项网元未正常服务",
updateValue: "【 {num} 】 属性值修改成功",
updateValueErr: "属性值修改失败",
updateItem: "修改 Index 为 {num} 记录成功",

View File

@@ -126,7 +126,7 @@ export function parseDataToTreeExclude(
}
/**
* 解析树结构数据转出一维id数组
* 解析树结构数据转出所有一维id数组
*
* @param data 数组数据
* @param fieldId 读取节点字段 默认 'id'
@@ -158,7 +158,7 @@ export function parseTreeKeys(
}
/**
* 解析树结构数据转出含子节点的一维id数组
* 解析树结构数据转出节点的一维id数组
*
* @param data 数组数据
* @param fieldId 读取节点字段 默认 'id'
@@ -221,3 +221,43 @@ export function parseDataToOptions(
return options;
}
/**
* 解析树结构数据转出子节点关联根节点的一维id数组
*
* @param data 数组数据
* @param checkedKeys 子节点数组数据
* @param fieldId 读取节点字段 默认 'id'
* @param fieldChildren 读取子节点字段 默认 'children'
* @returns 层级数组
*/
export function parseTreeNodeKeysByChecked(
data: Record<string, any>[],
checkedKeys: (string | number)[],
fieldId: string = 'id',
fieldChildren: string = 'children'
) {
// 节点id
let treeIds: (string | number)[] = [];
componet(data);
/**闭包递归函数 */
function componet(data: Record<string, any>[]) {
if (data.length <= 0) return false;
let hasKey = false;
for (const iterator of data) {
const key = iterator[fieldId];
if (checkedKeys.includes(key)) {
hasKey = true;
}
let nodes = iterator[fieldChildren];
if (Array.isArray(nodes) && nodes.length > 0) {
if (componet(nodes)) {
treeIds.push(key);
hasKey = true;
}
}
}
return hasKey;
}
return treeIds;
}

File diff suppressed because it is too large Load Diff

View File

@@ -49,7 +49,6 @@ let indexColor = ref<DictType[]>([
]);
/**表格字段列 */
//customRender(){} ----单元格处理
let tableColumns: ColumnsType = [
{
title: t('views.index.object'),
@@ -67,7 +66,9 @@ let tableColumns: ColumnsType = [
dataIndex: 'serverState',
align: 'left',
customRender(opt) {
if (opt.value?.refreshTime) return parseDateToStr(opt.value?.refreshTime);
if (opt.value?.refreshTime) {
return parseDateToStr(opt.value?.refreshTime, 'HH:mm:ss');
}
return '-';
},
},
@@ -104,12 +105,13 @@ let tableColumns: ColumnsType = [
},
},
];
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**记录数据 */
data: object[];
data: Record<string, any>[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
@@ -121,105 +123,75 @@ let tableState: TabeStateType = reactive({
selectedRowKeys: [],
});
/**表格状态 */
let nfInfo: any = reactive({
obj: 'OMC',
version: appStore.version,
status: t('views.index.normal'),
outTimeDate: '',
serialNum: appStore.serialNum,
});
/**表格状态类型 */
type nfStateType = {
/**主机名 */
hostName: string;
/**操作系统信息 */
osInfo: string;
/**IP地址 */
ipAddress: string;
/**版本 */
version: string;
/**CPU利用率 */
cpuUse: string;
/**内存使用 */
memoryUse: string;
/**用户容量 */
capability: number;
/**序列号 */
serialNum: string;
/**许可证到期日期 */
/* selectedRowKeys: (string | number)[];*/
expiryDate: string;
};
/**网元详细信息 */
let pronInfo: nfStateType = reactive({
hostName: '5gc',
osInfo: 'Linux 5gc 4.15.0-112-generic 2020 x86_64 GNU/Linux',
ipAddress: '-',
version: '-',
cpuUse: '-',
memoryUse: '-',
capability: 0,
serialNum: '-',
expiryDate: '-',
});
/**状态 */
let serverState: any = ref({});
/**查询网元状态列表 */
function fnGetList(one: boolean) {
if (tableState.loading) return;
one && (tableState.loading = true);
listAllNeInfo({ bandStatus: true }).then(res => {
tableState.data = res.data;
tableState.loading = false;
var rightNum = 0;
var errorNum = 0;
res.data.forEach((item: any) => {
if (item.serverState.online) {
rightNum++;
if (one) {
tableState.loading = true;
}
listAllNeInfo({ bandStatus: true })
.then(res => {
tableState.data = res.data;
tableState.loading = false;
if (one && res.data.length > 0) {
const id = res.data[0].id;
fnTableSelectedRowKeys([id]);
} else {
errorNum++;
fnTableSelectedRowKeys(tableState.selectedRowKeys);
}
});
const optionData: any = {
title: {
text: '',
subtext: '',
left: 'center',
},
tooltip: {
trigger: 'item',
},
legend: {
orient: 'vertical',
left: 'left',
},
color: indexColor.value.map(item => item.tagClass),
series: [
{
name: t('views.index.realNeStatus'),
type: 'pie',
radius: '70%',
center: ['50%', '50%'],
data: [
{ value: rightNum, name: t('views.index.normal') },
{ value: errorNum, name: t('views.index.abnormal') },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
label: {},
})
.finally(() => {
var rightNum = 0;
var errorNum = 0;
for (const v of tableState.data) {
if (v?.serverState?.online) {
rightNum++;
} else {
errorNum++;
}
}
/// 图表数据
const optionData: any = {
title: {
text: '',
subtext: '',
left: 'center',
},
],
};
tooltip: {
trigger: 'item',
},
legend: {
orient: 'vertical',
left: 'left',
},
color: indexColor.value.map(item => item.tagClass),
series: [
{
name: t('views.index.realNeStatus'),
type: 'pie',
radius: '70%',
center: ['50%', '50%'],
data: [
{ value: rightNum, name: t('views.index.normal') },
{ value: errorNum, name: t('views.index.abnormal') },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
fnDesign(statusBar.value, optionData);
});
label: {},
},
],
};
fnDesign(statusBar.value, optionData);
});
}
function fnDesign(container: HTMLElement | undefined, option: any) {
@@ -240,66 +212,43 @@ function fnDesign(container: HTMLElement | undefined, option: any) {
observer.observe(container);
}
/**抽屉 网元详细信息 */
const open = ref(false);
const closeDrawer = () => {
open.value = false;
};
/**抽屉 网元详细信息 */
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
if (keys.length <= 0) return;
const id = keys[0];
const row: any = tableState.data.find((item: any) => item.id === id);
if (!row) {
message.error(t('views.index.neStatus'), 2);
return;
}
const neState = row.serverState;
if (!neState?.online) {
message.error(t('views.index.neStatus'), 2);
return;
}
tableState.selectedRowKeys = keys;
// Mem 将KB转换为MB
const totalMemInKB = neState.mem?.totalMem;
const nfUsedMemInKB = neState.mem?.nfUsedMem;
const sysMemUsageInKB = neState.mem?.sysMemUsage;
const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100;
const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
const sysMemUsageInMB = Math.round((sysMemUsageInKB / 1024) * 100) / 100;
/**监听表格行事件*/
function rowClick(record: any, index: any) {
return {
onClick: (event: any) => {
let pronData = JSON.parse(JSON.stringify(record.serverState));
if (!pronData.online) {
message.error(t('views.index.neStatus'), 2);
return false;
} else {
const totalMemInKB = pronData.mem?.totalMem;
const nfUsedMemInKB = pronData.mem?.nfUsedMem;
const sysMemUsageInKB = pronData.mem?.sysMemUsage;
// CPU
const nfCpu = neState.cpu?.nfCpuUsage;
const sysCpu = neState.cpu?.sysCpuUsage;
const nfCpuP = Math.round(nfCpu) / 100;
const sysCpuP = Math.round(sysCpu) / 100;
// 将KB转换为MB
const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100;
const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
const sysMemUsageInMB =
Math.round((sysMemUsageInKB / 1024) * 100) / 100;
//渲染详细信息
pronInfo = {
hostName: pronData.hostname,
osInfo: pronData.os,
ipAddress: pronData.neIP,
version: pronData.version,
cpuUse:
pronData.neName +
':' +
pronData.cpu?.nfCpuUsage / 100 +
'%; ' +
'SYS:' +
pronData.cpu?.sysCpuUsage / 100 +
'%',
memoryUse:
'Total:' +
totalMemInMB +
'MB; ' +
pronData.name +
':' +
nfUsedMemInMB +
'MB; SYS:' +
sysMemUsageInMB +
'MB',
capability: pronData.capability,
serialNum: pronData.sn,
expiryDate: pronData.expire,
};
}
open.value = true;
serverState.value = Object.assign(
{
cpuUse: `NE:${nfCpuP}%; SYS:${sysCpuP}%`,
memoryUse: `Total: ${totalMemInMB}MB; NE: ${nfUsedMemInMB}MB; SYS: ${sysMemUsageInMB}MB`,
},
};
neState
);
}
let timer: any;
/**
* 国际化翻译转换
@@ -312,6 +261,7 @@ function fnLocale() {
appStore.setTitle(title);
}
let timer: any;
onMounted(() => {
getDict('index_status')
.then(res => {
@@ -322,12 +272,11 @@ onMounted(() => {
.finally(() => {
fnLocale();
fnGetList(true);
timer = setInterval(() => fnGetList(false), 10000); // 每隔10秒执行一次
timer = setInterval(() => fnGetList(false), 10_000); // 每隔10秒执行一次
});
});
// 在组件卸载之前清除定时器
onBeforeUnmount(() => {
clearInterval(timer);
});
@@ -335,40 +284,6 @@ onBeforeUnmount(() => {
<template>
<PageContainer :breadcrumb="{}">
<div>
<a-drawer :open="open" @close="closeDrawer" :width="700">
<a-descriptions bordered :column="1" :label-style="{ width: '160px' }">
<a-descriptions-item :label="t('views.index.hostName')">{{
pronInfo.hostName
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.osInfo')">{{
pronInfo.osInfo
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.ipAddress')">{{
pronInfo.ipAddress
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.version')">{{
pronInfo.version
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.capability')">{{
pronInfo.capability
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.cpuUse')">{{
pronInfo.cpuUse
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.memoryUse')">{{
pronInfo.memoryUse
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.serialNum')">{{
pronInfo.serialNum
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.expiryDate')">{{
pronInfo.expiryDate
}}</a-descriptions-item>
</a-descriptions>
</a-drawer>
</div>
<a-row :gutter="16">
<a-col :lg="14" :md="16" :xs="24">
<!-- 表格列表 -->
@@ -381,7 +296,12 @@ onBeforeUnmount(() => {
:data-source="tableState.data"
:pagination="false"
:scroll="{ x: true }"
:customRow="rowClick"
:row-selection="{
type: 'radio',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
@@ -404,7 +324,7 @@ onBeforeUnmount(() => {
<div style="width: 100%; min-height: 200px" ref="statusBar"></div>
</a-card>
<a-card
:title="t('views.index.mark')"
:title="`${t('views.index.mark')} - ${serverState.neName || ''}`"
style="margin-top: 16px"
size="small"
>
@@ -413,25 +333,33 @@ onBeforeUnmount(() => {
:column="1"
:label-style="{ width: '160px' }"
>
<a-descriptions-item :label="t('views.index.object')">{{
nfInfo.obj
}}</a-descriptions-item>
<template v-if="nfInfo.obj === 'OMC'">
<a-descriptions-item :label="t('views.index.versionNum')">{{
nfInfo.version
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.systemStatus')">{{
nfInfo.status
}}</a-descriptions-item>
</template>
<template v-else>
<a-descriptions-item :label="t('views.index.serialNum')">{{
nfInfo.serialNum
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.expiryDate')">{{
nfInfo.outTimeDate
}}</a-descriptions-item>
</template>
<a-descriptions-item :label="t('views.index.hostName')">
{{ serverState.hostname }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.osInfo')">
{{ serverState.os }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.ipAddress')">
{{ serverState.neIP }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.version')">
{{ serverState.version }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.capability')">
{{ serverState.capability }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.cpuUse')">
{{ serverState.cpuUse }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.memoryUse')">
{{ serverState.memoryUse }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.serialNum')">
{{ serverState.sn }}
</a-descriptions-item>
<a-descriptions-item :label="t('views.index.expiryDate')">
{{ serverState.expire }}
</a-descriptions-item>
</a-descriptions>
</a-card>
</a-col>

View File

@@ -654,65 +654,59 @@ onBeforeUnmount(() => {
</template>
</template>
<template #expandedRowRender="{ record }">
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
<a-divider orientation="left">
{{ t('views.dashboard.ue.ueInfo') }}
</a-divider>
<div>
<span>{{ t('views.ne.common.neName') }}: </span>
<span>{{ record.neName }}</span>
</div>
<div>
<span>{{ t('views.ne.common.rmUid') }}: </span>
<span>{{ record.rmUID }}</span>
</div>
<a-divider orientation="left">
{{ t('views.dashboard.ue.rowInfo') }}
</a-divider>
<div>
<span>{{ t('views.dashboard.ue.time') }}: </span>
<span
v-if="record.eventType === 'auth-result'"
:title="record.eventJSON.authTime"
>
{{ record.eventJSON.authTime }}
</span>
<span
v-if="record.eventType === 'detach'"
:title="record.eventJSON.detachTime"
>
{{ record.eventJSON.detachTime }}
</span>
<span
v-if="record.eventType === 'cm-state'"
:title="record.eventJSON.changeTime"
>
{{ record.eventJSON.changeTime }}
</span>
</div>
<div>
<span>{{ t('views.dashboard.ue.eventType') }}: </span>
<DictTag :options="dict.ueEventType" :value="record.eventType" />
</div>
<div>
<span>{{ t('views.dashboard.ue.result') }}: </span>
<span v-if="record.eventType === 'auth-result'">
<a-row :gutter="16">
<a-col :lg="8" :md="12" :xs="24" :offset="2">
<a-divider orientation="left">
{{ t('views.dashboard.ue.rowInfo') }}
</a-divider>
<div>
<span>{{ t('views.dashboard.ue.time') }}: </span>
<span
v-if="record.eventType === 'auth-result'"
:title="record.eventJSON.authTime"
>
{{ record.eventJSON.authTime }}
</span>
<span
v-if="record.eventType === 'detach'"
:title="record.eventJSON.detachTime"
>
{{ record.eventJSON.detachTime }}
</span>
<span
v-if="record.eventType === 'cm-state'"
:title="record.eventJSON.changeTime"
>
{{ record.eventJSON.changeTime }}
</span>
</div>
<div>
<span>{{ t('views.dashboard.ue.eventType') }}: </span>
<DictTag
:options="dict.ueAauthCode"
:value="record.eventJSON.authCode"
:options="dict.ueEventType"
:value="record.eventType"
/>
</span>
<span v-if="record.eventType === 'detach'">
{{ t('views.dashboard.ue.resultOk') }}
</span>
<span v-if="record.eventType === 'cm-state'">
<DictTag
:options="dict.ueEventCmState"
:value="record.eventJSON.status"
/>
</span>
</div>
</div>
</div>
<div>
<span>{{ t('views.dashboard.ue.result') }}: </span>
<span v-if="record.eventType === 'auth-result'">
<DictTag
:options="dict.ueAauthCode"
:value="record.eventJSON.authCode"
/>
</span>
<span v-if="record.eventType === 'detach'">
{{ t('views.dashboard.ue.resultOk') }}
</span>
<span v-if="record.eventType === 'cm-state'">
<DictTag
:options="dict.ueEventCmState"
:value="record.eventJSON.status"
/>
</span>
</div>
</a-col>
</a-row>
</template>
</a-table>
</a-card>

View File

@@ -21,6 +21,8 @@ import { parseDateToStr, parseDuration } from '@/utils/date-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import saveAs from 'file-saver';
import PQueue from 'p-queue';
import { useClipboard } from '@vueuse/core';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n();
const { getDict } = useDictStore();
const ws = new WS();
@@ -307,6 +309,18 @@ function fnRecordDelete(id: string) {
});
}
/**
* 复制CDR
* @param jsonStr JSON字符串
*/
function fnRecordCopy(jsonStr: string) {
if (!jsonStr) return;
const text = JSON.stringify(jsonStr, null, 2);
copy(text).then(() => {
message.success(t('common.copyOk'), 3);
});
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
@@ -407,7 +421,9 @@ function fnRealTime() {
subGroupID: `1005_${queryParams.neId}`,
},
onmessage: wsMessage,
onerror: wsError,
onerror: (ev: any) => {
console.error(ev);
},
};
ws.connect(options);
} else {
@@ -417,12 +433,6 @@ function fnRealTime() {
}
}
/**接收数据后回调 */
function wsError(ev: any) {
// 接收数据后回调
console.error(ev);
}
/**接收数据后回调 */
function wsMessage(res: Record<string, any>) {
const { code, requestId, data } = res;
@@ -522,6 +532,7 @@ onBeforeUnmount(() => {
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
@change="fnQueryReset()"
/>
</a-form-item>
</a-col>
@@ -724,6 +735,17 @@ onBeforeUnmount(() => {
</template>
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.copyText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordCopy(record.cdrJSON)"
>
<template #icon>
<CopyOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.deleteText') }}</template>
<a-button
@@ -740,7 +762,7 @@ onBeforeUnmount(() => {
</template>
<template #expandedRowRender="{ record }">
<a-row :gutter="16">
<a-col :lg="5" :md="12" :xs="24">
<a-col :lg="8" :md="12" :xs="24" :offset="2">
<a-divider orientation="left">
{{ t('views.dashboard.cdr.cdrInfo') }}
</a-divider>
@@ -763,7 +785,7 @@ onBeforeUnmount(() => {
</span>
</div>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-col :lg="8" :md="12" :xs="24">
<a-divider orientation="left">
{{ t('views.dashboard.cdr.rowInfo') }}
</a-divider>

View File

@@ -685,48 +685,55 @@ onBeforeUnmount(() => {
</template>
</template>
<template #expandedRowRender="{ record }">
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
<a-divider orientation="left">
{{ t('views.dashboard.ue.ueInfo') }}
</a-divider>
<div>
<span>{{ t('views.ne.common.neName') }}: </span>
<span>{{ record.neName }}</span>
</div>
<div>
<span>{{ t('views.ne.common.rmUid') }}: </span>
<span>{{ record.rmUID }}</span>
</div>
<a-divider orientation="left">
{{ t('views.dashboard.ue.rowInfo') }}
</a-divider>
<div>
<span>{{ t('views.dashboard.ue.time') }}: </span>
{{ parseDateToStr(record.eventJSON.timestamp * 1000) }}
</div>
<div>
<span>{{ t('views.dashboard.ue.eventType') }}: </span>
<DictTag :options="dict.ueEventType" :value="record.eventType" />
</div>
<div>
<span>{{ t('views.dashboard.ue.result') }}: </span>
<span v-if="record.eventType === 'auth-result'">
<a-row :gutter="16">
<a-col :lg="8" :md="12" :xs="24" :offset="2">
<a-divider orientation="left">
{{ t('views.dashboard.ue.ueInfo') }}
</a-divider>
<div>
<span>{{ t('views.ne.common.neName') }}: </span>
<span>{{ record.neName }}</span>
</div>
<div>
<span>{{ t('views.ne.common.rmUid') }}: </span>
<span>{{ record.rmUID }}</span>
</div>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-divider orientation="left">
{{ t('views.dashboard.ue.rowInfo') }}
</a-divider>
<div>
<span>{{ t('views.dashboard.ue.time') }}: </span>
{{ parseDateToStr(record.eventJSON.timestamp * 1000) }}
</div>
<div>
<span>{{ t('views.dashboard.ue.eventType') }}: </span>
<DictTag
:options="dict.ueAauthCode"
:value="record.eventJSON.result"
:options="dict.ueEventType"
:value="record.eventType"
/>
</span>
<span v-if="record.eventType === 'detach'">
{{ t('views.dashboard.ue.resultOk') }}
</span>
<span v-if="record.eventType === 'cm-state'">
<DictTag
:options="dict.ueEventCmState"
:value="record.eventJSON.result"
/>
</span>
</div>
</div>
</div>
<div>
<span>{{ t('views.dashboard.ue.result') }}: </span>
<span v-if="record.eventType === 'auth-result'">
<DictTag
:options="dict.ueAauthCode"
:value="record.eventJSON.result"
/>
</span>
<span v-if="record.eventType === 'detach'">
{{ t('views.dashboard.ue.resultOk') }}
</span>
<span v-if="record.eventType === 'cm-state'">
<DictTag
:options="dict.ueEventCmState"
:value="record.eventJSON.result"
/>
</span>
</div>
</a-col>
</a-row>
</template>
</a-table>
</a-card>

View File

@@ -77,8 +77,12 @@ onMounted(() => {
<div></div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:&nbsp;
<span :title="parseDateToStr(item.data.releaseTime * 1000)">
{{ parseDateToStr(item.data.releaseTime * 1000) }}
<span :title="item.data.releaseTime">
{{
typeof item.data.releaseTime === 'number'
? parseDateToStr(+item.data.releaseTime * 1000)
: item.data.releaseTime
}}
</span>
</div>
</div>
@@ -203,7 +207,11 @@ onMounted(() => {
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<span :title="item.data?.timestamp">
{{ parseDateToStr(+item.data?.timestamp * 1000) }}
{{
typeof item.data?.timestamp === 'number'
? parseDateToStr(+item.data?.timestamp * 1000)
: item.data?.timestamp
}}
</span>
</div>
</div>

View File

@@ -102,7 +102,7 @@ function fnGetNeState() {
/**获取概览信息 */
async function fnGetSkim() {
console.log(neCascaderOptions.value);
// console.log(neCascaderOptions.value);
// const resArr = await Promise.allSettled([
// listUDMSub({
// neid: '001',
@@ -234,7 +234,7 @@ function loadData() {
clearInterval(interval10s.value);
interval10s.value = setInterval(() => {
if (!interval10s.value) return
if (!interval10s.value) return;
if (upfTFActive.value === '0') {
upfTFSend('7');
upfTFActive.value = '7';
@@ -249,7 +249,7 @@ function loadData() {
clearInterval(interval5s.value);
interval5s.value = setInterval(() => {
if (!interval5s.value) return
if (!interval5s.value) return;
fnGetSkim(); // 获取概览信息
fnGetNeState(); // 获取网元状态
}, 5_000);

View File

@@ -0,0 +1,815 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, onBeforeUnmount, ref } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { Modal, message } from 'ant-design-vue/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
import { ColumnsType } from 'ant-design-vue/es/table';
import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n';
import {
RESULT_CODE_ERROR,
RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import {
delSGWCDataCDR,
exportSGWCDataCDR,
listSGWCDataCDR,
} from '@/api/neData/sgwc';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import PQueue from 'p-queue';
import saveAs from 'file-saver';
import { useClipboard } from '@vueuse/core';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n();
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
/**网元可选 */
let neOtions = ref<Record<string, any>[]>([]);
/**开始结束时间 */
let queryRangePicker = ref<[string, string]>(['', '']);
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: 'SGWC',
neId: '001',
imsi: '',
msisdn: '',
sortField: 'timestamp',
sortOrder: 'desc',
/**开始时间 */
startTime: '',
/**结束时间 */
endTime: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
imsi: '',
msisdn: '',
startTime: '',
endTime: '',
pageNum: 1,
pageSize: 20,
});
queryRangePicker.value = ['', ''];
tablePagination.current = 1;
tablePagination.pageSize = 20;
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**搜索栏 */
seached: boolean;
/**记录数据 */
data: object[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'middle',
seached: true,
data: [],
selectedRowKeys: [],
});
/**表格字段列 */
let tableColumns: ColumnsType = [
{
title: t('common.rowId'),
dataIndex: 'id',
align: 'left',
width: 100,
},
{
title: t('views.dashboard.cdr.chargingID'), // 计费ID
dataIndex: 'cdrJSON',
align: 'left',
width: 100,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.chargingID;
},
},
{
title: t('views.dashboard.cdr.sgwcServedIMSI'), // IMSI
dataIndex: 'cdrJSON',
align: 'left',
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.servedIMSI;
},
},
{
title: t('views.dashboard.cdr.sgwcServedMSISDN'), // MSISDN
dataIndex: 'cdrJSON',
align: 'left',
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.servedMSISDN;
},
},
{
title: t('views.dashboard.cdr.sgwcVolumeGPRSUplink'), // GPRS 上行链路
dataIndex: 'cdrJSON',
align: 'left',
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
const listOfTrafficVolumes = cdrJSON.listOfTrafficVolumes;
if (
!Array.isArray(listOfTrafficVolumes) ||
listOfTrafficVolumes.length < 1
) {
return 0;
}
let dataVolumeGPRSUplink = 0;
for (const used of listOfTrafficVolumes) {
const v = +used.dataVolumeGPRSUplink;
dataVolumeGPRSUplink += isNaN(v) ? 0 : v;
}
return dataVolumeGPRSUplink;
},
},
{
title: t('views.dashboard.cdr.sgwcVolumeGPRSDownlink'), // GPRS 下行链路
dataIndex: 'cdrJSON',
align: 'left',
width: 180,
customRender(opt) {
const cdrJSON = opt.value;
const listOfTrafficVolumes = cdrJSON.listOfTrafficVolumes;
if (
!Array.isArray(listOfTrafficVolumes) ||
listOfTrafficVolumes.length < 1
) {
return 0;
}
let dataVolumeGPRSDownlink = 0;
for (const used of listOfTrafficVolumes) {
const v = +used.dataVolumeGPRSDownlink;
dataVolumeGPRSDownlink += isNaN(v) ? 0 : v;
}
return dataVolumeGPRSDownlink;
},
},
{
title: t('views.dashboard.cdr.durationTime'), // 持续时间
dataIndex: 'cdrJSON',
align: 'left',
width: 100,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.duration;
},
},
{
title: t('views.dashboard.cdr.invocationTime'), // 操作时间
dataIndex: 'cdrJSON',
align: 'left',
width: 200,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.recordOpeningTime;
},
},
{
title: t('common.operate'),
key: 'id',
align: 'left',
},
];
/**表格分页器参数 */
let tablePagination = reactive({
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: false,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**表格紧凑型变更操作 */
function fnTableSize({ key }: MenuInfo) {
tableState.size = key as SizeType;
}
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**确定按钮 loading */
confirmLoading: boolean;
/**最大ID值 */
maxId: number;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
confirmLoading: false,
maxId: 0,
});
/**
* 记录删除
* @param id 编号
*/
function fnRecordDelete(id: string) {
if (!id || modalState.confirmLoading) return;
let msg = id;
if (id === '0') {
msg = `${id}... ${tableState.selectedRowKeys.length}`;
id = tableState.selectedRowKeys.join(',');
}
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.dashboard.cdr.delTip', { msg }),
onOk() {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
delSGWCDataCDR(id)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
fnGetList(1);
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
},
});
}
/**
* 复制CDR
* @param jsonStr JSON字符串
*/
function fnRecordCopy(jsonStr: string) {
if (!jsonStr) return;
const text = JSON.stringify(jsonStr, null, 2);
copy(text).then(() => {
message.success(t('common.copyOk'), 3);
});
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
if (!queryRangePicker.value) {
queryRangePicker.value = ['', ''];
}
queryParams.startTime = queryRangePicker.value[0];
queryParams.endTime = queryRangePicker.value[1];
listSGWCDataCDR(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选
if (tableState.selectedRowKeys.length > 0) {
tableState.selectedRowKeys = [];
}
tablePagination.total = res.total;
// 遍历处理cdr字符串数据
tableState.data = res.rows.map(item => {
let cdrJSON = item.cdrJSON;
if (!cdrJSON) {
Reflect.set(item, 'cdrJSON', {});
}
try {
cdrJSON = JSON.parse(cdrJSON);
Reflect.set(item, 'cdrJSON', cdrJSON);
} catch (error) {
console.error(error);
Reflect.set(item, 'cdrJSON', {});
}
return item;
});
// 取最大值ID用作实时累加
if (res.total > 0) {
modalState.maxId = Number(res.rows[0].id);
}
}
tableState.loading = false;
});
}
/**列表导出 */
function fnExportList() {
if (modalState.confirmLoading) return;
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.dashboard.cdr.exportTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
const querys = toRaw(queryParams);
querys.pageSize = 10000;
exportSGWCDataCDR(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
saveAs(res.data, `sgwc_cdr_event_export_${Date.now()}.xlsx`);
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
},
});
}
/**实时数据开关 */
const realTimeData = ref<boolean>(false);
/**
* 实时数据
*/
function fnRealTime() {
realTimeData.value = !realTimeData.value;
if (realTimeData.value) {
tableState.seached = false;
// 建立链接
const options: OptionsType = {
url: '/ws',
params: {
/**订阅通道组
*
* CDR会话事件-SGWC (GroupID:1008)
*/
subGroupID: `1008_${queryParams.neId}`,
},
onmessage: wsMessage,
onerror: (ev: any) => {
console.error(ev);
},
};
ws.connect(options);
} else {
ws.close();
tableState.seached = true;
fnGetList(1);
}
}
/**接收数据后回调 */
function wsMessage(res: Record<string, any>) {
const { code, requestId, data } = res;
if (code === RESULT_CODE_ERROR) {
console.warn(res.msg);
return;
}
// 订阅组信息
if (!data?.groupId) {
return;
}
// cdrEvent CDR会话事件
if (data.groupId === `1008_${queryParams.neId}`) {
const cdrEvent = data.data;
queue.add(async () => {
modalState.maxId += 1;
tableState.data.unshift({
id: modalState.maxId,
neType: cdrEvent.neType,
neName: cdrEvent.neName,
rmUID: cdrEvent.rmUID,
timestamp: cdrEvent.timestamp,
cdrJSON: cdrEvent.CDR,
});
tablePagination.total += 1;
if (tableState.data.length > 100) {
tableState.data.pop();
}
await new Promise(resolve => setTimeout(resolve, 800));
});
}
}
onMounted(() => {
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
let arr: Record<string, any>[] = [];
res.data.forEach(i => {
if (i.neType === 'SGWC') {
arr.push({ value: i.neId, label: i.neName });
}
});
neOtions.value = arr;
if (arr.length > 0) {
queryParams.neId = arr[0].value;
}
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
// 获取列表数据
fnGetList();
});
});
onBeforeUnmount(() => {
if (ws.state() !== -1) {
ws.close();
}
});
</script>
<template>
<PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="SGWC" name="neId ">
<a-select
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
@change="fnQueryReset()"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.sgwcServedIMSI')"
name="imsi"
>
<a-input
v-model:value="queryParams.imsi"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="40"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.sgwcServedMSISDN')"
name="msisdn"
>
<a-input
v-model:value="queryParams.msisdn"
allow-clear
:placeholder="t('common.inputPlease')"
:maxlength="40"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="4" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList(1)">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-form-item
:label="t('views.dashboard.cdr.time')"
name="queryRangePicker"
>
<a-range-picker
v-model:value="queryRangePicker"
allow-clear
bordered
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
value-format="x"
style="width: 100%"
></a-range-picker>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
<a-popconfirm
placement="bottomLeft"
:title="
!realTimeData
? t('views.dashboard.cdr.realTimeDataStart')
: t('views.dashboard.cdr.realTimeDataStop')
"
ok-text="Yes"
cancel-text="No"
@confirm="fnRealTime()"
>
<a-button type="primary" :danger="realTimeData">
<template #icon><FundOutlined /> </template>
{{
!realTimeData
? t('views.dashboard.cdr.realTimeDataStart')
: t('views.dashboard.cdr.realTimeDataStop')
}}
</a-button>
</a-popconfirm>
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading"
@click.prevent="fnRecordDelete('0')"
>
<template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }}
</a-button>
<a-button type="dashed" @click.prevent="fnExportList()">
<template #icon><ExportOutlined /></template>
{{ t('common.export') }}
</a-button>
</a-space>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.searchBarText') }}</template>
<a-switch
v-model:checked="tableState.seached"
:checked-children="t('common.switch.show')"
:un-checked-children="t('common.switch.hide')"
size="small"
:disabled="realTimeData"
/>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click" placement="bottomRight">
<a-button type="text">
<template #icon><ColumnHeightOutlined /></template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
<a-menu-item key="middle">
{{ t('common.size.middle') }}
</a-menu-item>
<a-menu-item key="small">
{{ t('common.size.small') }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.copyText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordCopy(record.cdrJSON)"
>
<template #icon>
<CopyOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.deleteText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordDelete(record.id)"
>
<template #icon>
<DeleteOutlined />
</template>
</a-button>
</a-tooltip>
</a-space>
</template>
</template>
<template #expandedRowRender="{ record }">
<a-row :gutter="16">
<a-col :lg="8" :md="12" :xs="24" :offset="2">
<a-divider orientation="left">
{{ t('views.dashboard.cdr.cdrInfo') }}
</a-divider>
<div>
<span>{{ t('views.ne.common.neName') }}: </span>
<span>{{ record.neName }}</span>
</div>
<div>
<span>{{ t('views.ne.common.rmUid') }}: </span>
<span>{{ record.rmUID }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.time') }}: </span>
<span>{{ record.cdrJSON.recordOpeningTime }}</span>
</div>
<a-divider orientation="left">
{{ t('views.dashboard.cdr.rowInfo') }}
</a-divider>
<div>
<span>Record Type: </span>
<span>{{ record.cdrJSON.recordType }}</span>
</div>
<div>
<span>Record Opening Time: </span>
<span>{{ record.cdrJSON.recordOpeningTime }}</span>
</div>
<div>
<span>Charging ID: </span>
<span>{{ record.cdrJSON.chargingID }}</span>
</div>
<div>
<span>Duration: </span>
<span>{{ record.cdrJSON.duration }}</span>
</div>
<div>
<span>Record Access Point Name NI: </span>
<span>{{ record.cdrJSON.accessPointNameNI }}</span>
</div>
<div>
<span>Record Cause For Rec Closing: </span>
<span>{{ record.cdrJSON.causeForRecClosing }}</span>
</div>
<div>
<span>Record Sequence Number: </span>
<span>{{ record.cdrJSON.recordSequenceNumber }}</span>
</div>
<div>
<span>Local Record Sequence Number: </span>
<span>{{ record.cdrJSON.localRecordSequenceNumber }}</span>
</div>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-divider orientation="left"> Server Information </a-divider>
<div>
<span>IMSI: </span>
<span> {{ record.cdrJSON.servedIMSI }} </span>
</div>
<div>
<span>MSISDN: </span>
<span> {{ record.cdrJSON.servedMSISDN }} </span>
</div>
<div>
<span>PGW Address Used: </span>
<span> {{ record.cdrJSON.pGWAddressUsed }} </span>
</div>
<div>
<span>SGW Address: </span>
<span> {{ record.cdrJSON.sGWAddress }} </span>
</div>
<div>
<span>RAT Type: </span>
<span> {{ record.cdrJSON.rATType }} </span>
</div>
<a-divider orientation="left"> PDPPD Information </a-divider>
<div>
<span>PDPPDN Type: </span>
<span> {{ record.cdrJSON.pdpPDNType }} </span>
</div>
<div>
<span>PDPPDN Address: </span>
<span> {{ record.cdrJSON.servedPDPPDNAddress }} </span>
</div>
<div>
<span>Node Address: </span>
<span>
{{ record.cdrJSON.servingNodeAddress?.join(', ') }}
</span>
</div>
<div>
<span>Node Type: </span>
<span>
<template v-for="item in record.cdrJSON.servingNodeType">
{{ item.servingNodeType }}
</template>
</span>
</div>
</a-col>
</a-row>
</template>
</a-table>
</a-card>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -19,6 +19,8 @@ import {
import { OptionsType, WS } from '@/plugins/ws-websocket';
import PQueue from 'p-queue';
import saveAs from 'file-saver';
import { useClipboard } from '@vueuse/core';
const { copy } = useClipboard({ legacy: true });
const { t } = useI18n();
const ws = new WS();
const queue = new PQueue({ concurrency: 1, autoStart: true });
@@ -94,7 +96,7 @@ let tableColumns: ColumnsType = [
width: 100,
},
{
title: t('views.dashboard.cdr.smfChargingID'), // 计费ID
title: t('views.dashboard.cdr.chargingID'), // 计费ID
dataIndex: 'cdrJSON',
align: 'left',
width: 100,
@@ -137,11 +139,15 @@ let tableColumns: ColumnsType = [
) {
return 0;
}
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
return 0;
let dataVolumeUplink = 0;
for (const v of listOfMultipleUnitUsage) {
if (Array.isArray(v.usedUnitContainer)) {
for (const used of v.usedUnitContainer) {
dataVolumeUplink += +used.dataVolumeUplink;
}
}
}
return usedUnitContainer[0].dataVolumeUplink;
return dataVolumeUplink;
},
},
{
@@ -158,11 +164,15 @@ let tableColumns: ColumnsType = [
) {
return 0;
}
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
return 0;
let dataVolumeDownlink = 0;
for (const v of listOfMultipleUnitUsage) {
if (Array.isArray(v.usedUnitContainer)) {
for (const used of v.usedUnitContainer) {
dataVolumeDownlink += +used.dataVolumeDownlink;
}
}
}
return usedUnitContainer[0].dataVolumeDownlink;
return dataVolumeDownlink;
},
},
{
@@ -179,15 +189,19 @@ let tableColumns: ColumnsType = [
) {
return 0;
}
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
return 0;
let dataTotalVolume = 0;
for (const v of listOfMultipleUnitUsage) {
if (Array.isArray(v.usedUnitContainer)) {
for (const used of v.usedUnitContainer) {
dataTotalVolume += +used.dataTotalVolume;
}
}
}
return usedUnitContainer[0].dataTotalVolume;
return dataTotalVolume;
},
},
{
title: t('views.dashboard.cdr.smfDuration'), // 持续时间
title: t('views.dashboard.cdr.durationTime'), // 持续时间
dataIndex: 'cdrJSON',
align: 'left',
width: 100,
@@ -197,7 +211,7 @@ let tableColumns: ColumnsType = [
},
},
{
title: t('views.dashboard.cdr.smfInvocationTime'), // 调用时间
title: t('views.dashboard.cdr.invocationTime'), // 调用时间
dataIndex: 'cdrJSON',
align: 'left',
width: 200,
@@ -306,6 +320,18 @@ function fnRecordDelete(id: string) {
});
}
/**
* 复制CDR
* @param jsonStr JSON字符串
*/
function fnRecordCopy(jsonStr: string) {
if (!jsonStr) return;
const text = JSON.stringify(jsonStr, null, 2);
copy(text).then(() => {
message.success(t('common.copyOk'), 3);
});
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
@@ -406,7 +432,9 @@ function fnRealTime() {
subGroupID: `1006_${queryParams.neId}`,
},
onmessage: wsMessage,
onerror: wsError,
onerror: (ev: any) => {
console.error(ev);
},
};
ws.connect(options);
} else {
@@ -416,12 +444,6 @@ function fnRealTime() {
}
}
/**接收数据后回调 */
function wsError(ev: any) {
// 接收数据后回调
console.error(ev);
}
/**接收数据后回调 */
function wsMessage(res: Record<string, any>) {
const { code, requestId, data } = res;
@@ -510,6 +532,7 @@ onBeforeUnmount(() => {
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
@change="fnQueryReset()"
/>
</a-form-item>
</a-col>
@@ -669,6 +692,17 @@ onBeforeUnmount(() => {
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.copyText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordCopy(record.cdrJSON)"
>
<template #icon>
<CopyOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.deleteText') }}</template>
<a-button
@@ -742,24 +776,33 @@ onBeforeUnmount(() => {
<a-divider orientation="left">
List Of Multiple Unit Usage
</a-divider>
<div v-for="u in record.cdrJSON.listOfMultipleUnitUsage">
<!-- <div>RatingGroup: {{ u.ratingGroup }}</div> -->
<div v-for="udata in u.usedUnitContainer">
<div>RatingGroup: {{ u.ratingGroup }}</div>
<div
v-for="(udata, i) in u.usedUnitContainer"
style="display: flex"
>
<strong style="margin-right: 12px">
{{ i }}
</strong>
<div>
<span>Data Total Volume: </span>
<span>{{ udata.dataTotalVolume }}</span>
</div>
<div>
<span>Data Volume Downlink: </span>
<span>{{ udata.dataVolumeDownlink }}</span>
</div>
<div>
<span>Data Volume Uplink: </span>
<span>{{ udata.dataVolumeUplink }}</span>
</div>
<div>
<span>Time: </span>
<span>{{ udata.time }}</span>
<div>
<span>Data Total Volume: </span>
<span>{{ udata.dataTotalVolume }}</span>
</div>
<div>
<span>Data Volume Downlink: </span>
<span>{{ udata.dataVolumeDownlink }}</span>
</div>
<div>
<span>Data Volume Uplink: </span>
<span>{{ udata.dataVolumeUplink }}</span>
</div>
<div>
<span>Time: </span>
<span>{{ udata.time }}</span>
</div>
</div>
</div>
</div>

View File

@@ -21,6 +21,8 @@ import { parseDateToStr } from '@/utils/date-utils';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import saveAs from 'file-saver';
import PQueue from 'p-queue';
import { useClipboard } from '@vueuse/core';
const { copy } = useClipboard({ legacy: true });
const { getDict } = useDictStore();
const { t } = useI18n();
const ws = new WS();
@@ -144,7 +146,7 @@ let tableColumns: ColumnsType = [
dataIndex: 'cdrJSON',
key: 'callerParty',
align: 'left',
width: 120,
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.callerParty;
@@ -155,7 +157,7 @@ let tableColumns: ColumnsType = [
dataIndex: 'cdrJSON',
key: 'calledParty',
align: 'left',
width: 120,
width: 150,
customRender(opt) {
const cdrJSON = opt.value;
return cdrJSON.calledParty;
@@ -173,7 +175,7 @@ let tableColumns: ColumnsType = [
title: t('views.dashboard.cdr.time'),
dataIndex: 'cdrJSON',
align: 'left',
width: 150,
width: 200,
customRender(opt) {
const cdrJSON = opt.value;
if (typeof cdrJSON.updateTime === 'number') {
@@ -282,6 +284,18 @@ function fnRecordDelete(id: string) {
});
}
/**
* 复制CDR
* @param jsonStr JSON字符串
*/
function fnRecordCopy(jsonStr: string) {
if (!jsonStr) return;
const text = JSON.stringify(jsonStr, null, 2);
copy(text).then(() => {
message.success(t('common.copyOk'), 3);
});
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
@@ -382,7 +396,9 @@ function fnRealTime() {
subGroupID: `1007_${queryParams.neId}`,
},
onmessage: wsMessage,
onerror: wsError,
onerror: (ev: any) => {
console.error(ev);
},
};
ws.connect(options);
} else {
@@ -392,12 +408,6 @@ function fnRealTime() {
}
}
/**接收数据后回调 */
function wsError(ev: any) {
// 接收数据后回调
console.error(ev);
}
/**接收数据后回调 */
function wsMessage(res: Record<string, any>) {
const { code, requestId, data } = res;
@@ -492,6 +502,7 @@ onBeforeUnmount(() => {
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
@change="fnQueryReset()"
/>
</a-form-item>
</a-col>
@@ -665,7 +676,7 @@ onBeforeUnmount(() => {
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
:scroll="{ x: tableColumns.length * 180, y: 'calc(100vh - 480px)' }"
:row-selection="{
type: 'checkbox',
columnWidth: '48px',
@@ -689,6 +700,17 @@ onBeforeUnmount(() => {
</template>
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.copyText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordCopy(record.cdrJSON)"
>
<template #icon>
<CopyOutlined />
</template>
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.deleteText') }}</template>
<a-button
@@ -704,58 +726,62 @@ onBeforeUnmount(() => {
</template>
</template>
<template #expandedRowRender="{ record }">
<div style="width: 46%; padding-left: 32px; padding-bottom: 16px">
<a-divider orientation="left">
{{ t('views.dashboard.cdr.cdrInfo') }}
</a-divider>
<div>
<span>{{ t('views.ne.common.neName') }}: </span>
<span>{{ record.neName }}</span>
</div>
<div>
<span>{{ t('views.ne.common.rmUid') }}: </span>
<span>{{ record.rmUID }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.time') }}: </span>
<span>
{{
typeof record.cdrJSON.updateTime === 'number'
? parseDateToStr(+record.cdrJSON.updateTime * 1000)
: record.cdrJSON.updateTime
}}
</span>
</div>
<a-divider orientation="left">
{{ t('views.dashboard.cdr.rowInfo') }}
</a-divider>
<div>
<span>{{ t('views.dashboard.cdr.type') }}: </span>
<span>{{ record.cdrJSON.serviceType }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.caller') }}: </span>
<span>{{ record.cdrJSON.callerParty }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.called') }}: </span>
<span>{{ record.cdrJSON.calledParty }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.result') }}: </span>
<span v-if="record.cdrJSON.result === 0">
{{ t('views.dashboard.cdr.resultFail') }},
<DictTag
:options="dict.cdrCauseCode"
:value="record.cdrJSON.cause"
value-default="0"
/>
</span>
<span v-else>
{{ t('views.dashboard.cdr.resultOk') }}
</span>
</div>
</div>
<a-row :gutter="16">
<a-col :lg="8" :md="12" :xs="24" :offset="2">
<a-divider orientation="left">
{{ t('views.dashboard.cdr.cdrInfo') }}
</a-divider>
<div>
<span>{{ t('views.ne.common.neName') }}: </span>
<span>{{ record.neName }}</span>
</div>
<div>
<span>{{ t('views.ne.common.rmUid') }}: </span>
<span>{{ record.rmUID }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.time') }}: </span>
<span>
{{
typeof record.cdrJSON.updateTime === 'number'
? parseDateToStr(+record.cdrJSON.updateTime * 1000)
: record.cdrJSON.updateTime
}}
</span>
</div>
</a-col>
<a-col :lg="8" :md="12" :xs="24">
<a-divider orientation="left">
{{ t('views.dashboard.cdr.rowInfo') }}
</a-divider>
<div>
<span>{{ t('views.dashboard.cdr.type') }}: </span>
<span>{{ record.cdrJSON.serviceType }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.caller') }}: </span>
<span>{{ record.cdrJSON.callerParty }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.called') }}: </span>
<span>{{ record.cdrJSON.calledParty }}</span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.result') }}: </span>
<span v-if="record.cdrJSON.result === 0">
{{ t('views.dashboard.cdr.resultFail') }},
<DictTag
:options="dict.cdrCauseCode"
:value="record.cdrJSON.cause"
value-default="0"
/>
</span>
<span v-else>
{{ t('views.dashboard.cdr.resultOk') }}
</span>
</div>
</a-col>
</a-row>
</template>
</a-table>
</a-card>

View File

@@ -92,13 +92,13 @@ const graphNodeMenu = new Menu({
${neState.neName ?? '--'}
</h3>
<div id="restart" style="cursor: pointer; margin-bottom: 4px">
> ${t('views.configManage.neManage.restart')}
> ${t('views.ne.common.restart')}
</div>
<div id="stop" style="cursor: pointer; margin-bottom: 4px;">
> ${t('views.configManage.neManage.stop')}
> ${t('views.ne.common.stop')}
</div>
<div id="log" style="cursor: pointer; margin-bottom: 4px;">
> ${t('views.configManage.neManage.log')}
> ${t('views.ne.common.log')}
</div>
</div>
`;

View File

@@ -4,19 +4,20 @@ import {
editNeConfigData,
} from '@/api/ne/neConfig';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { Modal,message } from 'ant-design-vue/es';
import { Modal, message } from 'ant-design-vue/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { reactive, watch } from 'vue';
/**
* 参数配置array类型
* @param param 父级传入 { t, treeState, neTypeSelect, fnActiveConfigNode, ruleVerification, modalState, fnModalCancel}
* @param param 父级传入 { t, treeState, neTypeSelect, neIdSelect, fnActiveConfigNode, ruleVerification, modalState, fnModalCancel}
* @returns
*/
export default function useConfigArray({
t,
treeState,
neTypeSelect,
neIdSelect,
fnActiveConfigNode,
ruleVerification,
modalState,
@@ -130,29 +131,61 @@ export default function useConfigArray({
data[key] = from[key]['value'];
}
// 发送
// 请求
const reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
paramData: data,
loc: loc,
})
);
}
} else {
reqArr.push(
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc: loc,
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
arrayEditClose();
return;
}
const hide = message.loading(t('common.loading'), 0);
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc: loc,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.warning({
content: t('views.ne.neConfig.updateItemErr'),
duration: 3,
});
} else {
message.success({
content: t('views.ne.neConfig.updateItem', {
num: modalState.title,
}),
duration: 3,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
fnActiveConfigNode('#');
} else {
message.warning({
content: t('views.ne.neConfig.updateItemErr'),
duration: 3,
});
}
})
.finally(() => {
@@ -172,28 +205,65 @@ export default function useConfigArray({
num: title,
}),
onOk() {
delNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
loc: loc,
}).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.ne.neConfig.delItemOk', {
num: title,
}),
duration: 2,
});
arrayEditClose();
fnActiveConfigNode('#');
} else {
message.error({
content: `${res.msg}`,
duration: 2,
});
// 请求
const reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
delNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
loc: loc,
})
);
}
});
} else {
reqArr.push(
delNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
loc: loc,
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
arrayEditClose();
return;
}
const hide = message.loading(t('common.loading'), 0);
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.error({
content: `${rejected.reason}`,
duration: 2,
});
} else {
message.success({
content: t('views.ne.neConfig.delItemOk', {
num: title,
}),
duration: 2,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
fnActiveConfigNode('#');
}
})
.finally(() => {
hide();
arrayEditClose();
});
},
});
}
@@ -264,29 +334,61 @@ export default function useConfigArray({
data[key] = from[key]['value'];
}
// 发送
// 请求
const reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
addNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
paramData: data,
loc: `${from['index']['value']}`,
})
);
}
} else {
reqArr.push(
addNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc: `${from['index']['value']}`,
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
arrayEditClose();
return;
}
const hide = message.loading(t('common.loading'), 0);
addNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc: `${from['index']['value']}`,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.warning({
content: t('views.ne.neConfig.addItemErr'),
duration: 3,
});
} else {
message.success({
content: t('views.ne.neConfig.addItemOk', {
num: modalState.title,
}),
duration: 3,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
fnActiveConfigNode('#');
} else {
message.warning({
content: t('views.ne.neConfig.addItemErr'),
duration: 3,
});
}
})
.finally(() => {

View File

@@ -10,13 +10,14 @@ import { nextTick, reactive } from 'vue';
/**
* 参数配置array类型的嵌套array
* @param param 父级传入 { t, treeState, neTypeSelect, fnActiveConfigNode, ruleVerification, modalState, arrayState, arrayInitEdit, arrayInitAdd, arrayEditClose}
* @param param 父级传入 { t, treeState, neTypeSelect, neIdSelect, fnActiveConfigNode, ruleVerification, modalState, arrayState, arrayInitEdit, arrayInitAdd, arrayEditClose}
* @returns
*/
export default function useConfigArrayChild({
t,
treeState,
neTypeSelect,
neIdSelect,
fnActiveConfigNode,
ruleVerification,
modalState,
@@ -198,29 +199,61 @@ export default function useConfigArrayChild({
data[key] = from[key]['value'];
}
// 发送
// 请求
const reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
);
}
} else {
reqArr.push(
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
arrayEditClose();
return;
}
const hide = message.loading(t('common.loading'), 0);
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.warning({
content: t('views.ne.neConfig.updateItemErr'),
duration: 3,
});
} else {
message.success({
content: t('views.ne.neConfig.updateItem', {
num: modalState.title,
}),
duration: 3,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
fnActiveConfigNode('#');
} else {
message.warning({
content: t('views.ne.neConfig.updateItemErr'),
duration: 3,
});
}
})
.finally(() => {
@@ -241,28 +274,65 @@ export default function useConfigArrayChild({
num: title,
}),
onOk() {
delNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
loc,
}).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.ne.neConfig.delItemOk', {
num: title,
}),
duration: 2,
});
arrayEditClose();
fnActiveConfigNode('#');
} else {
message.error({
content: `${res.msg}`,
duration: 2,
});
// 请求
const reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
delNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
loc,
})
);
}
});
} else {
reqArr.push(
delNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
loc,
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
arrayEditClose();
return;
}
const hide = message.loading(t('common.loading'), 0);
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.error({
content: `${rejected.reason}`,
duration: 2,
});
} else {
message.success({
content: t('views.ne.neConfig.delItemOk', {
num: title,
}),
duration: 2,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
fnActiveConfigNode('#');
}
})
.finally(() => {
hide();
arrayEditClose();
});
},
});
}
@@ -309,29 +379,61 @@ export default function useConfigArrayChild({
data[key] = from[key]['value'];
}
// 发送
// 请求
const reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
addNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
);
}
} else {
reqArr.push(
addNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
arrayEditClose();
return;
}
const hide = message.loading(t('common.loading'), 0);
addNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.warning({
content: t('views.ne.neConfig.addItemErr'),
duration: 3,
});
} else {
message.success({
content: t('views.ne.neConfig.addItemOk', {
num: modalState.title,
}),
duration: 3,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
fnActiveConfigNode('#');
} else {
message.warning({
content: t('views.ne.neConfig.addItemErr'),
duration: 3,
});
}
})
.finally(() => {

View File

@@ -6,13 +6,14 @@ import { reactive, toRaw } from 'vue';
/**
* list类型参数处理
* @param param 父级传入 {t, treeState, neTypeSelect, ruleVerification}
* @param param 父级传入 {t, treeState, neTypeSelect, neIdSelect, ruleVerification}
* @returns
*/
export default function useConfigList({
t,
treeState,
neTypeSelect,
neIdSelect,
ruleVerification,
}: any) {
/**单列表状态类型 */
@@ -83,25 +84,64 @@ export default function useConfigList({
return;
}
// 发送
// 请求
const reqArr = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
for (const neId of neIdSelect.value) {
reqArr.push(
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neId,
paramName: treeState.selectNode.paramName,
paramData: {
[from['name']]: from['value'],
},
})
);
}
} else {
reqArr.push(
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: {
[from['name']]: from['value'],
},
})
);
}
// 无请求提示
if (reqArr.length === 0) {
message.warning({
content: t('views.ne.neConfig.neIdSyncPleace'),
duration: 3,
});
listState.confirmLoading = false;
listState.editRecord = {};
return;
}
listState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
editNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: treeState.selectNode.paramName,
paramData: {
[from['name']]: from['value'],
},
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
Promise.allSettled(reqArr)
.then(resArr => {
const rejected = resArr.find(res => res.status === 'rejected');
if (rejected) {
message.warning({
content: t('views.ne.neConfig.updateValueErr'),
duration: 3,
});
} else {
message.success({
content: t('views.ne.neConfig.updateValue', {
num: from['display'],
}),
duration: 3,
});
}
const fulfilled = resArr.find(res => res.status === 'fulfilled');
if (fulfilled) {
// 改变表格数据
const item = listState.data.find(
(item: Record<string, any>) => from['name'] === item['name']
@@ -109,11 +149,6 @@ export default function useConfigList({
if (item) {
Object.assign(item, listState.editRecord);
}
} else {
message.warning({
content: t('views.ne.neConfig.updateValueErr'),
duration: 3,
});
}
})
.finally(() => {

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw, watch } from 'vue';
import { reactive, ref, onMounted, toRaw, watch, computed } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { ProModal } from 'antdv-pro-modal';
import { message } from 'ant-design-vue/es';
import { message, TreeSelect, type TreeSelectProps } from 'ant-design-vue/es';
import { DataNode } from 'ant-design-vue/es/tree';
import useI18n from '@/hooks/useI18n';
import TableColumnsDnd from '@/components/TableColumnsDnd/index.vue';
@@ -19,11 +19,102 @@ const { ruleVerification, smfByUPFIdLoadData, smfByUPFIdOptions } = useOptions({
t,
});
/**网元类型_多neId */
/**网元类型 type,[](type,id) */
let neCascaderOptions = ref<Record<string, any>[]>([]);
/**网元类型 [](label,value,children) */
let neSelectTreeDate = ref<TreeSelectProps['treeData']>([]);
/**网元类型选择 type,id */
let neTypeSelect = ref<string[]>(['', '']);
/**网元类型选择 id */
let neIdSelect = ref<string[]>([]);
let neTypeSelectStatus = ref(true);
/**网元类型neType选择 */
async function fnSelectNeType(_: any, info: any) {
if (!info) return;
await fnGetNeConfig(info.value);
if (treeState.data.length === 0) {
message.warning({
content: `${t('views.ne.neConfig.noConfigData')}`,
duration: 3,
});
treeState.selectLoading = true;
neIdSelect.value = [];
return;
}
neTypeSelect.value[0] = info.value;
neTypeSelect.value[1] = 'SYNC';
treeState.selectLoading = true;
neTypeSelectStatus.value = true;
neIdSelect.value = [];
// 构建可选树形数据
if (Array.isArray(info.children) && info.children.length > 0) {
const neArr = info.children.concat();
for (let index = 0; index < neArr.length; index++) {
const v = neArr[index];
const ne = {
label: v.neName,
value: v.neId,
disabled: false,
};
// 检查下级网元是否可用
const res = await getNeConfigData({
neType: v.neType,
neId: v.neId,
paramName: `${treeState.data[0].key}`,
});
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
ne.disabled = !res.data.length;
} else {
ne.disabled = true;
}
// 添加到树形数据
const root = neSelectTreeDate.value?.find(s => s.label === v.province);
if (root && Array.isArray(root.children)) {
root.children.push(ne);
} else {
neSelectTreeDate.value?.push({
label: v.province,
value: 'SYNC_' + v.province,
children: [ne],
});
}
const key = 'SYNC_' + v.province;
// 初始区域
if (neIdSelect.value.length === 0) {
neTypeSelect.value[1] = key;
}
// 同区域内添加
if (neTypeSelect.value[1] === key) {
neIdSelect.value.push(v.neId);
}
}
}
fnActiveConfigNode(treeState.data[0].key);
neTypeSelectStatus.value = false;
}
/**网元类型neId选择 */
function fnSelectNeId(_: any, info: any) {
if (info.children && Array.isArray(info.children)) {
const okArr = info.children.filter((item: any) => !item.disabled);
if (Array.isArray(okArr) && okArr.length === 0) {
message.warning({
content: `${t('views.ne.neConfig.noConfigdDisabled')}`,
duration: 3,
});
neIdSelect.value = [];
return;
}
neTypeSelect.value[1] = info.value;
neIdSelect.value = okArr.map((item: any) => item.value);
} else {
neTypeSelect.value[1] = info.value;
}
fnActiveConfigNode(treeState.data[0].key);
}
/**左侧导航是否可收起 */
let collapsible = ref<boolean>(true);
@@ -49,6 +140,7 @@ type TreeStateType = {
paramType: string;
paramPerms: string[];
paramData: Record<string, any>[];
visible: string;
};
/**选择 loading */
selectLoading: boolean;
@@ -63,6 +155,7 @@ let treeState: TreeStateType = reactive({
paramType: '',
paramPerms: [],
paramData: [],
visible: 'public',
// 树形节点需要有
title: '',
key: '',
@@ -100,22 +193,134 @@ function fnActiveConfigNode(key: string | number) {
}
treeState.selectNode = JSON.parse(JSON.stringify(param));
let neId = neTypeSelect.value[1];
// 无neId时取首个可连接的
if (neId.startsWith('SYNC')) {
const oneNeId = neIdSelect.value[0];
if (oneNeId) {
neId = oneNeId;
} else {
return;
}
}
// 获取网元端的配置数据
getNeConfigData({
neType: neTypeSelect.value[0],
neId: neTypeSelect.value[1],
paramName: key,
}).then(res => {
fnGetNeConfigData(neTypeSelect.value[0], neId, key);
}
/**
* 查询配置可选属性值列表
* neTypeSelect.value[0]
*/
async function fnGetNeConfig(neType: string) {
if (!neType) {
message.warning({
content: t('views.ne.neConfig.neTypePleace'),
duration: 3,
});
return;
}
treeState.loading = true;
// 获取数据
const res = await getAllNeConfig(neType);
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
const arr = [];
for (const v of res.data) {
const item = JSON.parse(JSON.stringify(v));
// 规则项
let paramData: Record<string, string>[] = [];
for (let index = 0; index < item.paramData.length; index++) {
const element = item.paramData[index];
if (!element['visible']) {
element['visible'] = 'public';
}
paramData.push(element);
}
// 权限控制
let paramPerms: string[] = [];
if (item.paramPerms) {
paramPerms = item.paramPerms.split(',');
} else {
paramPerms = ['post', 'put', 'delete'];
}
arr.push({
children: undefined,
title: item.paramDisplay,
key: item.paramName,
paramName: item.paramName,
paramDisplay: item.paramDisplay,
paramType: item.paramType,
paramPerms: paramPerms,
paramData: paramData,
visible: item.visible,
});
}
treeState.data = arr;
treeState.loading = false;
} else {
treeState.data = [];
neTypeSelectStatus.value = false;
}
}
/**过滤可见项 */
const treeStateData = computed(() => {
// 公共
if (neTypeSelect.value[1].startsWith('SYNC')) {
return treeState.data.filter(item => item.visible === 'public');
}
// 具体网元
const arr: DataNode[] = [];
for (const item of treeState.data) {
if (item.visible === 'self') {
arr.push(item);
} else if (item.paramType === 'list') {
for (let index = 0; index < item.paramData.length; index++) {
const element = item.paramData[index];
if (element['visible'] === 'self') {
arr.push(item);
break;
}
}
}
}
return arr;
});
/**
* 查询配置属性值数据
* paramName = treeState.data[0].key
*/
function fnGetNeConfigData(
neType: string,
neId: string,
paramName: string | number
) {
const param = treeState.selectNode;
// 获取网元端的配置数据
getNeConfigData({ neType, neId, paramName }).then(res => {
// 数据处理
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
const ruleArr = param.paramData;
const ruleArr: Record<string, any>[] = JSON.parse(
JSON.stringify(param.paramData)
);
const dataArr = res.data;
if (param.paramType === 'list') {
// 过滤可见规则项
let ruleArrFilter: Record<string, any>[] = [];
if (neTypeSelect.value[1].startsWith('SYNC')) {
ruleArrFilter = ruleArr.filter(item => item['visible'] === 'public');
} else {
ruleArrFilter = ruleArr.filter(item => item['visible'] === 'self');
}
// 列表项数据
const dataList = [];
for (const item of dataArr) {
for (const key in item) {
// 规则为准
for (const rule of ruleArr) {
for (const rule of ruleArrFilter) {
// 取到对应规则key设置值
if (rule['name'] === key) {
const ruleItem = Object.assign(rule, {
optional: 'true',
@@ -190,64 +395,19 @@ function fnActiveConfigNode(key: string | number) {
tablePagination.current = 1;
arrayEditClose();
}
// 有数据关闭loading
setTimeout(() => {
treeState.selectLoading = false;
}, 300);
} else {
message.warning({
content: `${param.paramDisplay} ${t(
'views.configManage.configParamForm.noConfigData'
)}`,
content: `${param.paramDisplay} ${t('views.ne.neConfig.noConfigData')}`,
duration: 3,
});
}
});
}
/**查询配置可选属性值列表 */
function fnGetNeConfig() {
const neType = neTypeSelect.value[0];
if (!neType) {
message.warning({
content: t('views.configManage.configParamForm.neTypePleace'),
duration: 3,
});
return;
}
treeState.loading = true;
// 获取数据
getAllNeConfig(neType).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
const arr = [];
for (const item of res.data) {
let paramPerms: string[] = [];
if (item.paramPerms) {
paramPerms = item.paramPerms.split(',');
} else {
paramPerms = ['post', 'put', 'delete'];
}
arr.push({
...item,
children: undefined,
title: item.paramDisplay,
key: item.paramName,
paramPerms,
});
}
treeState.data = arr;
treeState.loading = false;
// 取首个tag
if (res.data.length > 0) {
const item = JSON.parse(JSON.stringify(treeState.data[0]));
treeState.selectNode = item;
treeState.selectLoading = false;
fnActiveConfigNode(item.key);
}
}
});
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**添加框是否显示 */
@@ -315,13 +475,18 @@ watch(
val => {
// SMF需要选择配置的UPF id
if (val && neTypeSelect.value[0] === 'SMF') {
smfByUPFIdLoadData(neTypeSelect.value[1]);
if (neTypeSelect.value[1].startsWith('SYNC')) {
smfByUPFIdLoadData(neTypeSelect.value[1]);
return;
} else {
smfByUPFIdLoadData(neTypeSelect.value[1]);
}
}
}
);
const { tablePagination, listState, listEdit, listEditClose, listEditOk } =
useConfigList({ t, treeState, neTypeSelect, ruleVerification });
useConfigList({ t, treeState, neTypeSelect, neIdSelect, ruleVerification });
const {
arrayState,
@@ -337,6 +502,7 @@ const {
t,
treeState,
neTypeSelect,
neIdSelect,
fnActiveConfigNode,
ruleVerification,
modalState,
@@ -355,6 +521,7 @@ const {
t,
treeState,
neTypeSelect,
neIdSelect,
fnActiveConfigNode,
ruleVerification,
modalState,
@@ -385,13 +552,14 @@ onMounted(() => {
// 默认选择AMF
const item = neCascaderOptions.value.find(s => s.value === 'AMF');
if (item && item.children) {
const info = item.children[0];
neTypeSelect.value = [info.neType, info.neId];
fnSelectNeType(null, item);
// const info = item.children[0];
// neTypeSelect.value = [info.neType, info.neId];
} else {
const info = neCascaderOptions.value[0].children[0];
neTypeSelect.value = [info.neType, info.neId];
fnSelectNeType(null, neCascaderOptions.value[0]);
// const info = neCascaderOptions.value[0].children[0];
// neTypeSelect.value = [info.neType, info.neId];
}
fnGetNeConfig();
}
} else {
message.warning({
@@ -416,20 +584,43 @@ onMounted(() => {
<!-- 网元类型 -->
<a-card size="small" :bordered="false" :loading="treeState.loading">
<template #title>
{{ t('views.configManage.configParamForm.treeTitle') }}
{{ t('views.ne.neConfig.treeTitle') }}
</template>
<a-form layout="vertical" autocomplete="off">
<a-form-item name="neId ">
<a-cascader
v-model:value="neTypeSelect"
:options="neCascaderOptions"
:allow-clear="false"
@change="fnGetNeConfig"
/>
<a-form-item name="neTypeSelect ">
<a-input-group compact>
<a-select
:disabled="neTypeSelectStatus"
:value="neTypeSelect[0]"
:options="neCascaderOptions"
:allow-clear="false"
@change="fnSelectNeType"
style="width: 40%"
>
</a-select>
<a-tree-select
v-model:value="neTypeSelect[1]"
:status="neIdSelect.length === 0 ? 'warning' : ''"
:disabled="treeState.selectLoading"
:tree-data="neSelectTreeDate"
:maxTagCount="1"
:show-search="true"
:allow-clear="false"
:tree-default-expand-all="true"
:tree-checkable="false"
:show-checked-strategy="TreeSelect.SHOW_PARENT"
placement="bottomRight"
tree-node-filter-prop="label"
style="width: 60%"
@select="fnSelectNeId"
>
</a-tree-select>
</a-input-group>
</a-form-item>
<a-form-item name="listeningPort">
<a-form-item name="treeStateData">
<a-tree
:tree-data="treeState.data"
:disabled="neTypeSelectStatus"
:tree-data="treeStateData"
:selected-keys="[treeState.selectNode.paramName]"
@select="fnSelectConfigNode"
>
@@ -461,12 +652,12 @@ onMounted(() => {
{{ treeState.selectNode.paramDisplay }}
</a-typography-text>
<a-typography-text type="danger" v-else>
{{ t('views.configManage.configParamForm.treeSelectTip') }}
{{ t('views.ne.neConfig.treeSelectTip') }}
</a-typography-text>
</template>
<template #extra>
<a-space :size="8" align="center" v-show="!treeState.selectLoading">
<a-tooltip>
<a-tooltip placement="topRight">
<template #title>{{ t('common.reloadText') }}</template>
<a-button
type="default"
@@ -549,10 +740,9 @@ onMounted(() => {
<template #title> {{ t('common.ok') }} </template>
<a-popconfirm
:title="
t(
'views.configManage.configParamForm.editOkTip',
{ num: record['display'] }
)
t('views.ne.neConfig.editOkTip', {
num: record['display'],
})
"
placement="topRight"
:disabled="listState.confirmLoading"
@@ -610,7 +800,7 @@ onMounted(() => {
</a-table>
<!-- array类型 -->
<template v-if="treeState.selectNode.paramType === 'array'">
<template v-else-if="treeState.selectNode.paramType === 'array'">
<a-table
class="table"
row-key="index"
@@ -689,9 +879,7 @@ onMounted(() => {
"
>
<template #icon><BarsOutlined /></template>
{{
t('views.configManage.configParamForm.arrayMore')
}}
{{ t('views.ne.neConfig.arrayMore') }}
</a-button>
<!--特殊字段拓展显示-->
<span
@@ -792,11 +980,7 @@ onMounted(() => {
<template v-if="text.array">
<a-button type="default" size="small">
<template #icon><BarsOutlined /></template>
{{
t(
'views.configManage.configParamForm.arrayMore'
)
}}
{{ t('views.ne.neConfig.arrayMore') }}
</a-button>
</template>
@@ -816,6 +1000,10 @@ onMounted(() => {
</template>
</a-table>
</template>
<template v-else>
<a-alert type="warning" show-icon message="No Data" />
</template>
</a-card>
</a-col>
</a-row>

View File

@@ -357,33 +357,38 @@ function fnGetList(pageNum?: number) {
if (pageNum) {
queryParams.pageNum = pageNum;
}
listNeInfo(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选
if (tableState.selectedRowKeys.length > 0) {
tableState.selectedRowKeys = [];
}
tablePagination.total = res.total;
// 遍历处理资源情况数值
tableState.data = res.rows.map(item => {
let resouresUsage = {
sysDiskUsage: 0,
sysMemUsage: 0,
sysCpuUsage: 0,
nfCpuUsage: 0,
};
const neState = item.serverState;
if (neState) {
resouresUsage = parseResouresUsage(neState);
} else {
item.serverState = { online: false };
listNeInfo(toRaw(queryParams))
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选
if (tableState.selectedRowKeys.length > 0) {
tableState.selectedRowKeys = [];
}
Reflect.set(item, 'resoures', resouresUsage);
return item;
});
}
tableState.loading = false;
});
tablePagination.total = res.total;
// 遍历处理资源情况数值
tableState.data = res.rows.map(item => {
let resouresUsage = {
sysDiskUsage: 0,
sysMemUsage: 0,
sysCpuUsage: 0,
nfCpuUsage: 0,
};
const neState = item.serverState;
if (neState) {
resouresUsage = parseResouresUsage(neState);
} else {
item.serverState = { online: false };
}
Reflect.set(item, 'resoures', resouresUsage);
return item;
});
}
tableState.loading = false;
})
.finally(() => {
// 刷新缓存的网元信息
useNeInfoStore().fnRefreshNelist();
});
}
/**解析网元状态携带的资源利用率 */
@@ -441,13 +446,8 @@ onMounted(() => {
}
});
// 刷新缓存的网元信息
useNeInfoStore()
.fnRefreshNelist()
.finally(() => {
// 获取列表数据
fnGetList();
});
// 获取列表数据
fnGetList();
});
</script>

View File

@@ -166,8 +166,8 @@ function fnBeforeUploadFile(file: FileType) {
const suff = fileName.substring(fileName.lastIndexOf('.'));
if (!['.deb', '.rpm'].includes(suff)) {
message.error(
t('views.configManage.softwareManage.onlyAble', {
fileText: '(.deb、.rpm)',
t('views.ne.neSoftware.fileTypeNotEq', {
txt: '(.deb、.rpm)',
}),
3
);
@@ -238,8 +238,8 @@ function fnBeforeUploadFileDep(file: FileType) {
const suff = fileName.substring(fileName.lastIndexOf('.'));
if (!['.deb', '.rpm'].includes(suff)) {
message.error(
t('views.configManage.softwareManage.onlyAble', {
fileText: '(.deb、.rpm)',
t('views.ne.neSoftware.fileTypeNotEq', {
txt: '(.deb、.rpm)',
}),
3
);

View File

@@ -171,8 +171,8 @@ function fnBeforeUploadFile(file: FileType) {
const suff = fileName.substring(fileName.lastIndexOf('.'));
if (!['.deb', '.rpm'].includes(suff)) {
message.error(
t('views.configManage.softwareManage.onlyAble', {
fileText: '(.deb、.rpm)',
t('views.ne.neSoftware.fileTypeNotEq', {
txt: '(.deb、.rpm)',
}),
3
);
@@ -286,8 +286,8 @@ function fnBeforeUploadFileDep(file: FileType) {
const suff = fileName.substring(fileName.lastIndexOf('.'));
if (!['.deb', '.rpm'].includes(suff)) {
message.error(
t('views.configManage.softwareManage.onlyAble', {
fileText: '(.deb、.rpm)',
t('views.ne.neSoftware.fileTypeNotEq', {
txt: '(.deb、.rpm)',
}),
3
);

View File

@@ -6,7 +6,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 { listSMFSubscribers } from '@/api/neData/smf';
import { listSMFSubList } from '@/api/neData/smf';
import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
@@ -224,7 +224,7 @@ function fnGetList(pageNum?: number) {
if (pageNum) {
queryParams.pageNum = pageNum;
}
listSMFSubscribers(toRaw(queryParams)).then(res => {
listSMFSubList(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
tablePagination.total = res.total;
tableState.data = res.rows;

View File

@@ -391,9 +391,7 @@ function fnRecordRun(row: Record<string, any>) {
threRun(row).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.msgSuccess', {
msg: t('views.configManage.softwareManage.runBtn'),
}),
content: 'Run',
key,
duration: 2,
});
@@ -610,7 +608,7 @@ onMounted(() => {
<a-space :size="8" align="center">
<a-tooltip>
<template #title>
{{ t('views.configManage.softwareManage.runBtn') }}
Run
</template>
<a-button
type="link"

View File

@@ -616,9 +616,7 @@ function fnRecordRun(row: Record<string, any>) {
taskRun(row).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.msgSuccess', {
msg: t('views.configManage.softwareManage.runBtn'),
}),
content: 'Run',
key,
duration: 2,
});
@@ -861,7 +859,7 @@ onMounted(() => {
>
<a-menu-item key="run">
<ThunderboltOutlined />
{{ t('views.configManage.softwareManage.runBtn') }}
Run
</a-menu-item>
<a-menu-item key="stop">
<UndoOutlined />

View File

@@ -230,7 +230,7 @@ function fnNeTypeChange(v: any, data: any) {
function fnModalVisibleByEdit(record?: any) {
if (!record) {
//modalStateFrom.resetFields();
modalState.title = t('views.configManage.neManage.addNe');
modalState.title = t('views.ne.neInfo.addTitle');
const neId = `${new Date().getMilliseconds()}`.padStart(3, '0');
modalState.from = {
id: undefined,
@@ -287,7 +287,7 @@ function fnModalVisibleByEdit(record?: any) {
hide();
if (res.code === RESULT_CODE_SUCCESS) {
Object.assign(modalState.from, res.data);
modalState.title = t('views.configManage.neManage.editNe');
modalState.title = t('views.ne.neInfo.editTitle');
modalState.openByEdit = true;
} else {
message.error(t('common.getInfoFail'), 2);

View File

@@ -23,7 +23,11 @@ import { saveAs } from 'file-saver';
import { parseDateToStr } from '@/utils/date-utils';
import useDictStore from '@/store/modules/dict';
import { DataNode } from 'ant-design-vue/es/tree';
import { parseTreeKeys, parseTreeNodeKeys } from '@/utils/parse-tree-utils';
import {
parseTreeKeys,
parseTreeNodeKeys,
parseTreeNodeKeysByChecked,
} from '@/utils/parse-tree-utils';
import { hasPermissions } from '@/plugins/auth-user';
import { MENU_PATH_INLINE } from '@/constants/menu-constants';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
@@ -328,7 +332,12 @@ function fnModalVisibleByVive(roleId: string | number) {
menuTree.treeData = menus;
modalState.menuTree.treeData = menus;
modalState.menuTree.checkedKeys = checkedKeys;
modalState.from.menuIds = checkedKeys;
if (modalState.from.menuCheckStrictly === '1') {
const ids = parseTreeNodeKeysByChecked(menus, checkedKeys, 'id');
modalState.from.menuIds = ids.concat(checkedKeys);
} else {
modalState.from.menuIds = checkedKeys;
}
}
modalState.title = t('views.system.role.roleInfo');
modalState.openByView = true;
@@ -385,7 +394,12 @@ function fnModalVisibleByEdit(roleId?: string | number) {
menuTree.treeData = menus;
modalState.menuTree.treeData = menus;
modalState.menuTree.checkedKeys = checkedKeys;
modalState.from.menuIds = checkedKeys;
if (modalState.from.menuCheckStrictly === '1') {
const ids = parseTreeNodeKeysByChecked(menus, checkedKeys, 'id');
modalState.from.menuIds = ids.concat(checkedKeys);
} else {
modalState.from.menuIds = checkedKeys;
}
}
modalState.title =
t('common.editText') + t('views.system.role.roleInfo');
@@ -567,7 +581,12 @@ function fnRecordDataScope(roleId: string | number) {
deptTree.treeData = depts;
modalState.deptTree.treeData = depts;
modalState.deptTree.checkedKeys = checkedKeys;
modalState.from.deptIds = checkedKeys;
if (modalState.from.deptCheckStrictly === '1') {
const ids = parseTreeNodeKeysByChecked(depts, checkedKeys, 'id');
modalState.from.deptIds = ids.concat(checkedKeys);
} else {
modalState.from.deptIds = checkedKeys;
}
}
modalState.title = t('views.system.role.distribute');
modalState.openByDataScope = true;