fix: CDR数据检查格式,SGWC调试
This commit is contained in:
@@ -421,14 +421,18 @@ export default {
|
|||||||
resultFail: "Fail",
|
resultFail: "Fail",
|
||||||
delTip: "Confirm deletion of the data item numbered [{msg}]?",
|
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.)",
|
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',
|
smfSubscriptionIDData: 'Subscription ID Data',
|
||||||
smfSubscriptionIDType: 'Subscription ID Type',
|
smfSubscriptionIDType: 'Subscription ID Type',
|
||||||
smfDataVolumeUplink: 'Data Volume Uplink',
|
smfDataVolumeUplink: 'Data Volume Uplink',
|
||||||
smfDataVolumeDownlink: 'Data Volume Downlink',
|
smfDataVolumeDownlink: 'Data Volume Downlink',
|
||||||
smfDataTotalVolume: 'Data Total Volume',
|
smfDataTotalVolume: 'Data Total Volume',
|
||||||
smfDuration: 'Duration',
|
durationTime: 'Duration',
|
||||||
smfInvocationTime: 'Invocation Time',
|
invocationTime: 'Invocation Time',
|
||||||
|
sgwcServedIMSI: 'IMSI',
|
||||||
|
sgwcServedMSISDN: 'MSISDN',
|
||||||
|
sgwcVolumeGPRSUplink: 'GPRS Uplink',
|
||||||
|
sgwcVolumeGPRSDownlink: 'GPRS Downlink',
|
||||||
},
|
},
|
||||||
ue: {
|
ue: {
|
||||||
eventType: "Event Type",
|
eventType: "Event Type",
|
||||||
|
|||||||
@@ -421,14 +421,18 @@ export default {
|
|||||||
resultFail: "失败",
|
resultFail: "失败",
|
||||||
delTip: "确认删除编号为【{msg}】的数据项?",
|
delTip: "确认删除编号为【{msg}】的数据项?",
|
||||||
exportTip: "确认导出当前查询条件的话单数据吗?(导出最大支持一万条)",
|
exportTip: "确认导出当前查询条件的话单数据吗?(导出最大支持一万条)",
|
||||||
smfChargingID: '计费ID',
|
chargingID: '计费ID',
|
||||||
smfSubscriptionIDData: '订阅 ID 数据',
|
smfSubscriptionIDData: '订阅 ID 数据',
|
||||||
smfSubscriptionIDType: '订阅 ID 类型',
|
smfSubscriptionIDType: '订阅 ID 类型',
|
||||||
smfDataVolumeUplink: '数据量上行链路',
|
smfDataVolumeUplink: '数据量上行链路',
|
||||||
smfDataVolumeDownlink: '数据量下行链路',
|
smfDataVolumeDownlink: '数据量下行链路',
|
||||||
smfDataTotalVolume: '数据总量',
|
smfDataTotalVolume: '数据总量',
|
||||||
smfDuration: '持续时间',
|
durationTime: '持续时间',
|
||||||
smfInvocationTime: '调用时间',
|
invocationTime: '调用时间',
|
||||||
|
sgwcServedIMSI: 'IMSI',
|
||||||
|
sgwcServedMSISDN: 'MSISDN',
|
||||||
|
sgwcVolumeGPRSUplink: 'GPRS 上行链路',
|
||||||
|
sgwcVolumeGPRSDownlink: 'GPRS 下行链路',
|
||||||
},
|
},
|
||||||
ue: {
|
ue: {
|
||||||
eventType: "事件类型",
|
eventType: "事件类型",
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import { parseDateToStr, parseDuration } from '@/utils/date-utils';
|
|||||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
import saveAs from 'file-saver';
|
import saveAs from 'file-saver';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
|
const { copy } = useClipboard({ legacy: true });
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { getDict } = useDictStore();
|
const { getDict } = useDictStore();
|
||||||
const ws = new WS();
|
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初始页数 */
|
/**查询列表, pageNum初始页数 */
|
||||||
function fnGetList(pageNum?: number) {
|
function fnGetList(pageNum?: number) {
|
||||||
if (tableState.loading) return;
|
if (tableState.loading) return;
|
||||||
@@ -407,7 +421,9 @@ function fnRealTime() {
|
|||||||
subGroupID: `1005_${queryParams.neId}`,
|
subGroupID: `1005_${queryParams.neId}`,
|
||||||
},
|
},
|
||||||
onmessage: wsMessage,
|
onmessage: wsMessage,
|
||||||
onerror: wsError,
|
onerror: (ev: any) => {
|
||||||
|
console.error(ev);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
ws.connect(options);
|
ws.connect(options);
|
||||||
} else {
|
} else {
|
||||||
@@ -417,12 +433,6 @@ function fnRealTime() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**接收数据后回调 */
|
|
||||||
function wsError(ev: any) {
|
|
||||||
// 接收数据后回调
|
|
||||||
console.error(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**接收数据后回调 */
|
/**接收数据后回调 */
|
||||||
function wsMessage(res: Record<string, any>) {
|
function wsMessage(res: Record<string, any>) {
|
||||||
const { code, requestId, data } = res;
|
const { code, requestId, data } = res;
|
||||||
@@ -724,6 +734,17 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'id'">
|
<template v-if="column.key === 'id'">
|
||||||
<a-space :size="8" align="center">
|
<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>
|
<a-tooltip>
|
||||||
<template #title>{{ t('common.deleteText') }}</template>
|
<template #title>{{ t('common.deleteText') }}</template>
|
||||||
<a-button
|
<a-button
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import {
|
|||||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import saveAs from 'file-saver';
|
import saveAs from 'file-saver';
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
|
const { copy } = useClipboard({ legacy: true });
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const ws = new WS();
|
const ws = new WS();
|
||||||
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||||
@@ -34,7 +36,8 @@ let queryParams = reactive({
|
|||||||
/**网元类型 */
|
/**网元类型 */
|
||||||
neType: 'SGWC',
|
neType: 'SGWC',
|
||||||
neId: '001',
|
neId: '001',
|
||||||
subscriberID: '',
|
imsi: '',
|
||||||
|
msisdn: '',
|
||||||
sortField: 'timestamp',
|
sortField: 'timestamp',
|
||||||
sortOrder: 'desc',
|
sortOrder: 'desc',
|
||||||
/**开始时间 */
|
/**开始时间 */
|
||||||
@@ -50,7 +53,8 @@ let queryParams = reactive({
|
|||||||
/**查询参数重置 */
|
/**查询参数重置 */
|
||||||
function fnQueryReset() {
|
function fnQueryReset() {
|
||||||
queryParams = Object.assign(queryParams, {
|
queryParams = Object.assign(queryParams, {
|
||||||
subscriberID: '',
|
imsi: '',
|
||||||
|
msisdn: '',
|
||||||
startTime: '',
|
startTime: '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
@@ -89,12 +93,12 @@ let tableState: TabeStateType = reactive({
|
|||||||
let tableColumns: ColumnsType = [
|
let tableColumns: ColumnsType = [
|
||||||
{
|
{
|
||||||
title: t('common.rowId'),
|
title: t('common.rowId'),
|
||||||
dataIndex: 'left',
|
dataIndex: 'id',
|
||||||
align: 'center',
|
align: 'left',
|
||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.dashboard.cdr.smfChargingID'), // 计费ID
|
title: t('views.dashboard.cdr.chargingID'), // 计费ID
|
||||||
dataIndex: 'cdrJSON',
|
dataIndex: 'cdrJSON',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -104,90 +108,71 @@ let tableColumns: ColumnsType = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.dashboard.cdr.smfSubscriptionIDType'), // 订阅 ID 类型
|
title: t('views.dashboard.cdr.sgwcServedIMSI'), // IMSI
|
||||||
dataIndex: 'cdrJSON',
|
dataIndex: 'cdrJSON',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 150,
|
width: 150,
|
||||||
customRender(opt) {
|
customRender(opt) {
|
||||||
const cdrJSON = opt.value;
|
const cdrJSON = opt.value;
|
||||||
return cdrJSON.subscriberIdentifier?.subscriptionIDType;
|
return cdrJSON.servedIMSI;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.dashboard.cdr.smfSubscriptionIDData'), // 订阅 ID 数据
|
title: t('views.dashboard.cdr.sgwcServedMSISDN'), // MSISDN
|
||||||
dataIndex: 'cdrJSON',
|
dataIndex: 'cdrJSON',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 150,
|
width: 150,
|
||||||
customRender(opt) {
|
customRender(opt) {
|
||||||
const cdrJSON = opt.value;
|
const cdrJSON = opt.value;
|
||||||
return cdrJSON.subscriberIdentifier?.subscriptionIDData;
|
return cdrJSON.servedMSISDN;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.dashboard.cdr.smfDataVolumeUplink'), // 数据量上行链路
|
title: t('views.dashboard.cdr.sgwcVolumeGPRSUplink'), // GPRS 上行链路
|
||||||
dataIndex: 'cdrJSON',
|
dataIndex: 'cdrJSON',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 150,
|
width: 150,
|
||||||
customRender(opt) {
|
customRender(opt) {
|
||||||
const cdrJSON = opt.value;
|
const cdrJSON = opt.value;
|
||||||
const listOfMultipleUnitUsage = cdrJSON.listOfMultipleUnitUsage;
|
const listOfTrafficVolumes = cdrJSON.listOfTrafficVolumes;
|
||||||
if (
|
if (
|
||||||
!Array.isArray(listOfMultipleUnitUsage) ||
|
!Array.isArray(listOfTrafficVolumes) ||
|
||||||
listOfMultipleUnitUsage.length < 1
|
listOfTrafficVolumes.length < 1
|
||||||
) {
|
) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
|
let dataVolumeGPRSUplink = 0;
|
||||||
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
|
for (const used of listOfTrafficVolumes) {
|
||||||
return 0;
|
const v = +used.dataVolumeGPRSUplink;
|
||||||
|
dataVolumeGPRSUplink += isNaN(v) ? 0 : v;
|
||||||
}
|
}
|
||||||
return usedUnitContainer[0].dataVolumeUplink;
|
return dataVolumeGPRSUplink;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.dashboard.cdr.smfDataVolumeDownlink'), // 数据量下行链路
|
title: t('views.dashboard.cdr.sgwcVolumeGPRSDownlink'), // GPRS 下行链路
|
||||||
dataIndex: 'cdrJSON',
|
dataIndex: 'cdrJSON',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 180,
|
width: 180,
|
||||||
customRender(opt) {
|
customRender(opt) {
|
||||||
const cdrJSON = opt.value;
|
const cdrJSON = opt.value;
|
||||||
const listOfMultipleUnitUsage = cdrJSON.listOfMultipleUnitUsage;
|
const listOfTrafficVolumes = cdrJSON.listOfTrafficVolumes;
|
||||||
if (
|
if (
|
||||||
!Array.isArray(listOfMultipleUnitUsage) ||
|
!Array.isArray(listOfTrafficVolumes) ||
|
||||||
listOfMultipleUnitUsage.length < 1
|
listOfTrafficVolumes.length < 1
|
||||||
) {
|
) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
|
let dataVolumeGPRSDownlink = 0;
|
||||||
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
|
for (const used of listOfTrafficVolumes) {
|
||||||
return 0;
|
const v = +used.dataVolumeGPRSDownlink;
|
||||||
|
dataVolumeGPRSDownlink += isNaN(v) ? 0 : v;
|
||||||
}
|
}
|
||||||
return usedUnitContainer[0].dataVolumeDownlink;
|
return dataVolumeGPRSDownlink;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.dashboard.cdr.smfDataTotalVolume'), // 数据总量
|
title: t('views.dashboard.cdr.durationTime'), // 持续时间
|
||||||
dataIndex: 'cdrJSON',
|
|
||||||
align: 'left',
|
|
||||||
width: 150,
|
|
||||||
customRender(opt) {
|
|
||||||
const cdrJSON = opt.value;
|
|
||||||
const listOfMultipleUnitUsage = cdrJSON.listOfMultipleUnitUsage;
|
|
||||||
if (
|
|
||||||
!Array.isArray(listOfMultipleUnitUsage) ||
|
|
||||||
listOfMultipleUnitUsage.length < 1
|
|
||||||
) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
|
|
||||||
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return usedUnitContainer[0].dataTotalVolume;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('views.dashboard.cdr.smfDuration'), // 持续时间
|
|
||||||
dataIndex: 'cdrJSON',
|
dataIndex: 'cdrJSON',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -197,13 +182,13 @@ let tableColumns: ColumnsType = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.dashboard.cdr.smfInvocationTime'), // 调用时间
|
title: t('views.dashboard.cdr.invocationTime'), // 操作时间
|
||||||
dataIndex: 'cdrJSON',
|
dataIndex: 'cdrJSON',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 200,
|
width: 200,
|
||||||
customRender(opt) {
|
customRender(opt) {
|
||||||
const cdrJSON = opt.value;
|
const cdrJSON = opt.value;
|
||||||
return cdrJSON.invocationTimestamp;
|
return cdrJSON.recordOpeningTime;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -306,6 +291,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初始页数 */
|
/**查询列表, pageNum初始页数 */
|
||||||
function fnGetList(pageNum?: number) {
|
function fnGetList(pageNum?: number) {
|
||||||
if (tableState.loading) return;
|
if (tableState.loading) return;
|
||||||
@@ -406,7 +403,9 @@ function fnRealTime() {
|
|||||||
subGroupID: `1008_${queryParams.neId}`,
|
subGroupID: `1008_${queryParams.neId}`,
|
||||||
},
|
},
|
||||||
onmessage: wsMessage,
|
onmessage: wsMessage,
|
||||||
onerror: wsError,
|
onerror: (ev: any) => {
|
||||||
|
console.error(ev);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
ws.connect(options);
|
ws.connect(options);
|
||||||
} else {
|
} else {
|
||||||
@@ -416,12 +415,6 @@ function fnRealTime() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**接收数据后回调 */
|
|
||||||
function wsError(ev: any) {
|
|
||||||
// 接收数据后回调
|
|
||||||
console.error(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**接收数据后回调 */
|
/**接收数据后回调 */
|
||||||
function wsMessage(res: Record<string, any>) {
|
function wsMessage(res: Record<string, any>) {
|
||||||
const { code, requestId, data } = res;
|
const { code, requestId, data } = res;
|
||||||
@@ -435,7 +428,7 @@ function wsMessage(res: Record<string, any>) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// cdrEvent CDR会话事件
|
// cdrEvent CDR会话事件
|
||||||
if (data.groupId === `1006_${queryParams.neId}`) {
|
if (data.groupId === `1008_${queryParams.neId}`) {
|
||||||
const cdrEvent = data.data;
|
const cdrEvent = data.data;
|
||||||
queue.add(async () => {
|
queue.add(async () => {
|
||||||
modalState.maxId += 1;
|
modalState.maxId += 1;
|
||||||
@@ -515,17 +508,44 @@ onBeforeUnmount(() => {
|
|||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="6" :md="12" :xs="24">
|
<a-col :lg="6" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('views.dashboard.cdr.smfSubscriptionIDData')"
|
:label="t('views.dashboard.cdr.sgwcServedIMSI')"
|
||||||
name="subscriberID"
|
name="imsi"
|
||||||
>
|
>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:value="queryParams.subscriberID"
|
v-model:value="queryParams.imsi"
|
||||||
allow-clear
|
allow-clear
|
||||||
:placeholder="t('common.inputPlease')"
|
:placeholder="t('common.inputPlease')"
|
||||||
:maxlength="40"
|
:maxlength="40"
|
||||||
></a-input>
|
></a-input>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</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-col :lg="8" :md="12" :xs="24">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
:label="t('views.dashboard.cdr.time')"
|
:label="t('views.dashboard.cdr.time')"
|
||||||
@@ -542,20 +562,6 @@ onBeforeUnmount(() => {
|
|||||||
></a-range-picker>
|
></a-range-picker>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="4" :md="12" :xs="24">
|
|
||||||
<a-form-item>
|
|
||||||
<a-space :size="8">
|
|
||||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
|
||||||
<template #icon><SearchOutlined /></template>
|
|
||||||
{{ t('common.search') }}
|
|
||||||
</a-button>
|
|
||||||
<a-button type="default" @click.prevent="fnQueryReset">
|
|
||||||
<template #icon><ClearOutlined /></template>
|
|
||||||
{{ t('common.reset') }}
|
|
||||||
</a-button>
|
|
||||||
</a-space>
|
|
||||||
</a-form-item>
|
|
||||||
</a-col>
|
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-card>
|
</a-card>
|
||||||
@@ -669,6 +675,17 @@ onBeforeUnmount(() => {
|
|||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'id'">
|
<template v-if="column.key === 'id'">
|
||||||
<a-space :size="8" align="center">
|
<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>
|
<a-tooltip>
|
||||||
<template #title>{{ t('common.deleteText') }}</template>
|
<template #title>{{ t('common.deleteText') }}</template>
|
||||||
<a-button
|
<a-button
|
||||||
@@ -699,15 +716,11 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>{{ t('views.dashboard.cdr.time') }}: </span>
|
<span>{{ t('views.dashboard.cdr.time') }}: </span>
|
||||||
<span>{{ record.cdrJSON.invocationTimestamp }}</span>
|
<span>{{ record.cdrJSON.recordOpeningTime }}</span>
|
||||||
</div>
|
</div>
|
||||||
<a-divider orientation="left">
|
<a-divider orientation="left">
|
||||||
{{ t('views.dashboard.cdr.rowInfo') }}
|
{{ t('views.dashboard.cdr.rowInfo') }}
|
||||||
</a-divider>
|
</a-divider>
|
||||||
<div>
|
|
||||||
<span>Record Network Function ID: </span>
|
|
||||||
<span>{{ record.cdrJSON.recordingNetworkFunctionID }}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<span>Record Type: </span>
|
<span>Record Type: </span>
|
||||||
<span>{{ record.cdrJSON.recordType }}</span>
|
<span>{{ record.cdrJSON.recordType }}</span>
|
||||||
@@ -724,92 +737,66 @@ onBeforeUnmount(() => {
|
|||||||
<span>Duration: </span>
|
<span>Duration: </span>
|
||||||
<span>{{ record.cdrJSON.duration }}</span>
|
<span>{{ record.cdrJSON.duration }}</span>
|
||||||
</div>
|
</div>
|
||||||
<a-divider orientation="left"> Subscriber Identifier </a-divider>
|
|
||||||
<div>
|
<div>
|
||||||
<span>Subscription ID Type: </span>
|
<span>Record Access Point Name NI: </span>
|
||||||
<span>
|
<span>{{ record.cdrJSON.accessPointNameNI }}</span>
|
||||||
{{ record.cdrJSON.subscriberIdentifier?.subscriptionIDType }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>Subscription ID Data: </span>
|
<span>Record Cause For Rec Closing: </span>
|
||||||
<span>
|
<span>{{ record.cdrJSON.causeForRecClosing }}</span>
|
||||||
{{ record.cdrJSON.subscriberIdentifier?.subscriptionIDData }}
|
</div>
|
||||||
</span>
|
<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>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :lg="8" :md="12" :xs="24">
|
<a-col :lg="8" :md="12" :xs="24">
|
||||||
<a-divider orientation="left">
|
<a-divider orientation="left"> Server Information </a-divider>
|
||||||
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>
|
|
||||||
<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>
|
|
||||||
<a-divider orientation="left">
|
|
||||||
PDU Session Charging Information
|
|
||||||
</a-divider>
|
|
||||||
<div>
|
<div>
|
||||||
<span>User Identifier: </span>
|
<span>IMSI: </span>
|
||||||
<span>{{
|
<span> {{ record.cdrJSON.servedIMSI }} </span>
|
||||||
record.cdrJSON.pDUSessionChargingInformation?.userIdentifier
|
</div>
|
||||||
}}</span>
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<span>SSC Mode: </span>
|
|
||||||
<span>{{
|
|
||||||
record.cdrJSON.pDUSessionChargingInformation?.sSCMode
|
|
||||||
}}</span>
|
|
||||||
|
|
||||||
<span>RAT Type: </span>
|
<span>RAT Type: </span>
|
||||||
<span>{{
|
<span> {{ record.cdrJSON.rATType }} </span>
|
||||||
record.cdrJSON.pDUSessionChargingInformation?.rATType
|
</div>
|
||||||
}}</span>
|
<a-divider orientation="left"> PDPPD Information </a-divider>
|
||||||
|
<div>
|
||||||
<span>DNN ID: </span>
|
<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>
|
<span>
|
||||||
{{ record.cdrJSON.pDUSessionChargingInformation?.dNNID }}
|
{{ record.cdrJSON.servingNodeAddress?.join(', ') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span>PDU Type: </span>
|
<span>Node Type: </span>
|
||||||
<span>
|
<span>
|
||||||
{{ record.cdrJSON.pDUSessionChargingInformation?.pDUType }}
|
<template v-for="item in record.cdrJSON.servingNodeType">
|
||||||
</span>
|
{{ item.servingNodeType }}
|
||||||
</div>
|
</template>
|
||||||
<div>
|
|
||||||
<span>PDU IPv4 Address: </span>
|
|
||||||
<span>
|
|
||||||
{{
|
|
||||||
record.cdrJSON.pDUSessionChargingInformation?.pDUAddress
|
|
||||||
?.pDUIPv4Address
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span>PDU IPv6 Addres Swith Prefix: </span>
|
|
||||||
<span>
|
|
||||||
{{
|
|
||||||
record.cdrJSON.pDUSessionChargingInformation?.pDUAddress
|
|
||||||
?.pDUIPv6AddresswithPrefix
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</a-col>
|
</a-col>
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ import {
|
|||||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
import saveAs from 'file-saver';
|
import saveAs from 'file-saver';
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
|
const { copy } = useClipboard({ legacy: true });
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const ws = new WS();
|
const ws = new WS();
|
||||||
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
const queue = new PQueue({ concurrency: 1, autoStart: true });
|
||||||
@@ -94,7 +96,7 @@ let tableColumns: ColumnsType = [
|
|||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.dashboard.cdr.smfChargingID'), // 计费ID
|
title: t('views.dashboard.cdr.chargingID'), // 计费ID
|
||||||
dataIndex: 'cdrJSON',
|
dataIndex: 'cdrJSON',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -137,11 +139,15 @@ let tableColumns: ColumnsType = [
|
|||||||
) {
|
) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
|
let dataVolumeUplink = 0;
|
||||||
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
|
for (const v of listOfMultipleUnitUsage) {
|
||||||
return 0;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
|
let dataVolumeDownlink = 0;
|
||||||
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
|
for (const v of listOfMultipleUnitUsage) {
|
||||||
return 0;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
const usedUnitContainer = listOfMultipleUnitUsage[0].usedUnitContainer;
|
let dataTotalVolume = 0;
|
||||||
if (!Array.isArray(usedUnitContainer) || usedUnitContainer.length < 1) {
|
for (const v of listOfMultipleUnitUsage) {
|
||||||
return 0;
|
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',
|
dataIndex: 'cdrJSON',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 100,
|
width: 100,
|
||||||
@@ -197,7 +211,7 @@ let tableColumns: ColumnsType = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('views.dashboard.cdr.smfInvocationTime'), // 调用时间
|
title: t('views.dashboard.cdr.invocationTime'), // 调用时间
|
||||||
dataIndex: 'cdrJSON',
|
dataIndex: 'cdrJSON',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
width: 200,
|
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初始页数 */
|
/**查询列表, pageNum初始页数 */
|
||||||
function fnGetList(pageNum?: number) {
|
function fnGetList(pageNum?: number) {
|
||||||
if (tableState.loading) return;
|
if (tableState.loading) return;
|
||||||
@@ -406,7 +432,9 @@ function fnRealTime() {
|
|||||||
subGroupID: `1006_${queryParams.neId}`,
|
subGroupID: `1006_${queryParams.neId}`,
|
||||||
},
|
},
|
||||||
onmessage: wsMessage,
|
onmessage: wsMessage,
|
||||||
onerror: wsError,
|
onerror: (ev: any) => {
|
||||||
|
console.error(ev);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
ws.connect(options);
|
ws.connect(options);
|
||||||
} else {
|
} else {
|
||||||
@@ -416,12 +444,6 @@ function fnRealTime() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**接收数据后回调 */
|
|
||||||
function wsError(ev: any) {
|
|
||||||
// 接收数据后回调
|
|
||||||
console.error(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**接收数据后回调 */
|
/**接收数据后回调 */
|
||||||
function wsMessage(res: Record<string, any>) {
|
function wsMessage(res: Record<string, any>) {
|
||||||
const { code, requestId, data } = res;
|
const { code, requestId, data } = res;
|
||||||
@@ -669,6 +691,17 @@ onBeforeUnmount(() => {
|
|||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'id'">
|
<template v-if="column.key === 'id'">
|
||||||
<a-space :size="8" align="center">
|
<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>
|
<a-tooltip>
|
||||||
<template #title>{{ t('common.deleteText') }}</template>
|
<template #title>{{ t('common.deleteText') }}</template>
|
||||||
<a-button
|
<a-button
|
||||||
@@ -742,24 +775,33 @@ onBeforeUnmount(() => {
|
|||||||
<a-divider orientation="left">
|
<a-divider orientation="left">
|
||||||
List Of Multiple Unit Usage
|
List Of Multiple Unit Usage
|
||||||
</a-divider>
|
</a-divider>
|
||||||
|
|
||||||
<div v-for="u in record.cdrJSON.listOfMultipleUnitUsage">
|
<div v-for="u in record.cdrJSON.listOfMultipleUnitUsage">
|
||||||
<!-- <div>RatingGroup: {{ u.ratingGroup }}</div> -->
|
<div>RatingGroup: {{ u.ratingGroup }}</div>
|
||||||
<div v-for="udata in u.usedUnitContainer">
|
<div
|
||||||
|
v-for="(udata, i) in u.usedUnitContainer"
|
||||||
|
style="display: flex"
|
||||||
|
>
|
||||||
|
<strong style="margin-right: 12px">
|
||||||
|
{{ i }}
|
||||||
|
</strong>
|
||||||
<div>
|
<div>
|
||||||
<span>Data Total Volume: </span>
|
<div>
|
||||||
<span>{{ udata.dataTotalVolume }}</span>
|
<span>Data Total Volume: </span>
|
||||||
</div>
|
<span>{{ udata.dataTotalVolume }}</span>
|
||||||
<div>
|
</div>
|
||||||
<span>Data Volume Downlink: </span>
|
<div>
|
||||||
<span>{{ udata.dataVolumeDownlink }}</span>
|
<span>Data Volume Downlink: </span>
|
||||||
</div>
|
<span>{{ udata.dataVolumeDownlink }}</span>
|
||||||
<div>
|
</div>
|
||||||
<span>Data Volume Uplink: </span>
|
<div>
|
||||||
<span>{{ udata.dataVolumeUplink }}</span>
|
<span>Data Volume Uplink: </span>
|
||||||
</div>
|
<span>{{ udata.dataVolumeUplink }}</span>
|
||||||
<div>
|
</div>
|
||||||
<span>Time: </span>
|
<div>
|
||||||
<span>{{ udata.time }}</span>
|
<span>Time: </span>
|
||||||
|
<span>{{ udata.time }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import { parseDateToStr } from '@/utils/date-utils';
|
|||||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||||
import saveAs from 'file-saver';
|
import saveAs from 'file-saver';
|
||||||
import PQueue from 'p-queue';
|
import PQueue from 'p-queue';
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
|
const { copy } = useClipboard({ legacy: true });
|
||||||
const { getDict } = useDictStore();
|
const { getDict } = useDictStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const ws = new WS();
|
const ws = new WS();
|
||||||
@@ -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初始页数 */
|
/**查询列表, pageNum初始页数 */
|
||||||
function fnGetList(pageNum?: number) {
|
function fnGetList(pageNum?: number) {
|
||||||
if (tableState.loading) return;
|
if (tableState.loading) return;
|
||||||
@@ -382,7 +396,9 @@ function fnRealTime() {
|
|||||||
subGroupID: `1007_${queryParams.neId}`,
|
subGroupID: `1007_${queryParams.neId}`,
|
||||||
},
|
},
|
||||||
onmessage: wsMessage,
|
onmessage: wsMessage,
|
||||||
onerror: wsError,
|
onerror: (ev: any) => {
|
||||||
|
console.error(ev);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
ws.connect(options);
|
ws.connect(options);
|
||||||
} else {
|
} else {
|
||||||
@@ -392,12 +408,6 @@ function fnRealTime() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**接收数据后回调 */
|
|
||||||
function wsError(ev: any) {
|
|
||||||
// 接收数据后回调
|
|
||||||
console.error(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**接收数据后回调 */
|
/**接收数据后回调 */
|
||||||
function wsMessage(res: Record<string, any>) {
|
function wsMessage(res: Record<string, any>) {
|
||||||
const { code, requestId, data } = res;
|
const { code, requestId, data } = res;
|
||||||
@@ -665,7 +675,7 @@ onBeforeUnmount(() => {
|
|||||||
:data-source="tableState.data"
|
:data-source="tableState.data"
|
||||||
:size="tableState.size"
|
:size="tableState.size"
|
||||||
:pagination="tablePagination"
|
:pagination="tablePagination"
|
||||||
:scroll="{ x: tableColumns.length * 150, y: 'calc(100vh - 480px)' }"
|
:scroll="{ x: tableColumns.length * 180, y: 'calc(100vh - 480px)' }"
|
||||||
:row-selection="{
|
:row-selection="{
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
columnWidth: '48px',
|
columnWidth: '48px',
|
||||||
@@ -689,6 +699,17 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
<template v-if="column.key === 'id'">
|
<template v-if="column.key === 'id'">
|
||||||
<a-space :size="8" align="center">
|
<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>
|
<a-tooltip>
|
||||||
<template #title>{{ t('common.deleteText') }}</template>
|
<template #title>{{ t('common.deleteText') }}</template>
|
||||||
<a-button
|
<a-button
|
||||||
|
|||||||
Reference in New Issue
Block a user