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

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;