Merge branch 'main-v2' into lite-ba

This commit is contained in:
TsMask
2025-09-28 20:18:28 +08:00
10 changed files with 220 additions and 59 deletions

View File

@@ -1,5 +1,21 @@
# 版本发布日志
## 2.2509.4-20250926
- 优化 UDM数据显示创建时间列
- 优化 中英文补充
- 新增 添加系统备份功能包括导入导出OMC的API和界面支持
- 新增 日志备份文件查看kpi文件记录
- 优化 根据搜索条件导出,去除最大记录限制
- 新增 添加活动告警自动刷新功能并优化代码结构
- 修复 奇安信浏览器时区格式错误
- 新增 添加网元配置快速PLMN修改框
- 新增 ue和ne模块权限按钮
- 新增 监控模块按钮权限
- 修复 UDM数据导出提示信息导出总数
- 新增 网元许可快速上传功能
- 新增 网元信息快速OAM功能
## 2.2508.4-20250829
- 新增 网元授权显示用户数/基站数

View File

@@ -140,3 +140,17 @@ export function exportUDMAuth(data: Record<string, any>) {
timeout: 180_000,
});
}
/**
* UDM鉴权用户导出DecAuth
* @param neId 网元ID
* @returns bolb
*/
export function exportUDMDecAuth(neId: string) {
return request({
url: `/neData/udm/auth/export-dec?neId=${neId}`,
method: 'GET',
responseType: 'blob',
timeout: 180_000,
});
}

View File

@@ -600,6 +600,7 @@ export default {
result:'Result',
success:'Success',
default:'Default',
allsuccess:'All NE have been configured successfully!',
},
quickUpload: {
title: 'Quick License Upload',
@@ -1371,7 +1372,7 @@ export default {
kpiPCF:'KPI PCF',
kpiUPF:'KPI UPF',
kpiMME:'KPI MME',
kpiSMSC:'KPI SMSC',
kpiSMSC:'KPI SMSC',
}
},
monitor: {

View File

@@ -600,6 +600,7 @@ export default {
result:'操作结果',
success:'成功',
default:'失败',
allsuccess:'所有网元配置成功!',
},
quickUpload: {
title: '快速许可证上传',
@@ -1371,7 +1372,7 @@ export default {
kpiPCF:'KPI PCF',
kpiUPF:'KPI UPF',
kpiMME:'KPI MME',
kpiSMSC:'KPI SMSC',
kpiSMSC:'KPI SMSC',
}
},
monitor: {

View File

@@ -33,10 +33,10 @@ const queue = new PQueue({ concurrency: 1, autoStart: true });
/**字典数据 */
let dict: {
/**CDR 响应原因代码类别类型 */
cdrCauseCode: DictType[];
/**CDR SMSC 响应原因代码类别类型 */
cdrSMSCCauseCode: DictType[];
} = reactive({
cdrCauseCode: [],
cdrSMSCCauseCode: [],
});
/**网元可选 */
@@ -186,12 +186,19 @@ let tableColumns: ColumnsType = [
},
{
title: t('views.dashboard.cdr.result'),
title: t('views.dashboard.cdr.resultCode'),
dataIndex: 'cdrJSON',
key: 'cause',
align: 'left',
width: 200,
},
{
title: t('views.dashboard.cdr.resultCause'),
dataIndex: 'cdrJSON',
key: 'result',
align: 'left',
width: 200,
},
{
title: t('views.dashboard.cdr.time'),
dataIndex: 'cdrJSON',
@@ -472,9 +479,9 @@ function wsMessage(res: Record<string, any>) {
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('cdr_cause_code')]).then(resArr => {
Promise.allSettled([getDict('cdr_cause_smsc')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.cdrCauseCode = resArr[0].value;
dict.cdrSMSCCauseCode = resArr[0].value;
}
});
@@ -712,17 +719,15 @@ onBeforeUnmount(() => {
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'cause'">
<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>
<span v-if="record.cdrJSON.result === 0"> FAILED </span>
<span v-else> SUCCESS </span>
</template>
<template v-if="column.key === 'result'">
<DictTag
:options="dict.cdrSMSCCauseCode"
:value="record.cdrJSON.cause"
value-default="0"
/>
</template>
<template v-if="column.key === 'id'">
<a-space :size="8" align="center">
@@ -794,18 +799,19 @@ onBeforeUnmount(() => {
<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') }},
<span>{{ t('views.dashboard.cdr.resultCode') }}: </span>
<span v-if="record.cdrJSON.result === 0"> FAILED </span>
<span v-else> SUCCESS </span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.resultCause') }}: </span>
<span>
<DictTag
:options="dict.cdrCauseCode"
:options="dict.cdrSMSCCauseCode"
:value="record.cdrJSON.cause"
value-default="0"
/>
</span>
<span v-else>
{{ t('views.dashboard.cdr.resultOk') }}
</span>
</div>
</a-col>
<a-col

View File

@@ -245,8 +245,9 @@ async function fnModalOk() {
message.warning(`${t('views.ne.neInfo.quickOam.success')} ${successCount} ${t('views.ne.neInfo.quickOam.default')} ${failCount} `, 5);
}
emit('ok');
fnModalCancel();
// 不自动关闭弹窗,让用户查看结果
// emit('ok');
// fnModalCancel();
} catch (error: any) {
message.error(t('common.errorFields', { num: error.errorFields?.length || 0 }), 3);
} finally {
@@ -271,6 +272,13 @@ function fnModalCancel() {
emit('update:open', false);
}
/**
* 手动关闭弹窗(用于操作完成后)
*/
function fnModalClose() {
fnModalCancel();
}
/**监听是否显示,初始数据 */
watch(
() => props.open,
@@ -365,12 +373,12 @@ watch(
{{ modalState.progress.current }} / {{ modalState.progress.total }}
</span>
</div>
<!-- <Progress-->
<!-- :percent="Math.round((modalState.progress.current / modalState.progress.total) * 100)"-->
<!-- :status="modalState.progress.current === modalState.progress.total ? 'success' : 'active'"-->
<!-- :show-info="false"-->
<!-- style="overflow: hidden;"-->
<!-- />-->
<!-- <Progress-->
<!-- :percent="Math.round((modalState.progress.current / modalState.progress.total) * 100)"-->
<!-- :status="modalState.progress.current === modalState.progress.total ? 'success' : 'active'"-->
<!-- :show-info="false"-->
<!-- style="overflow: hidden;"-->
<!-- />-->
<div style="width: 100%; overflow: hidden;">
<Progress
:percent="Math.round((modalState.progress.current / modalState.progress.total) * 100)"
@@ -385,32 +393,36 @@ watch(
<div v-if="modalState.results.length > 0" style="margin-top: 20px;">
<a-divider>{{ t('views.ne.neInfo.quickOam.result') }}</a-divider>
<div style="max-height: 200px; overflow-y: auto;">
<!-- 只显示失败的网元 -->
<div
v-for="(result, index) in modalState.results"
v-for="(result, index) in modalState.results.filter(r => !r.success)"
:key="index"
style="margin-bottom: 8px; padding: 8px; border-radius: 4px;"
:style="{
backgroundColor: result.success ? '#f6ffed' : '#fff2f0',
border: `1px solid ${result.success ? '#b7eb8f' : '#ffccc7'}`,
backgroundColor: '#fff2f0',
border: '1px solid #ffccc7',
}"
>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>
<CheckCircleOutlined
v-if="result.success"
:style="{ color: '#52c41a', marginRight: '8px' }"
/>
<CloseCircleOutlined
v-else
:style="{ color: '#ff4d4f', marginRight: '8px' }"
/>
{{ result.neType }}-{{ result.neId }} ({{ result.neName }})
</span>
<span :style="{ color: result.success ? '#52c41a' : '#ff4d4f', fontSize: '12px' }">
<span :style="{ color: '#ff4d4f', fontSize: '12px' }">
{{ result.message }}
</span>
</div>
</div>
<!-- 如果没有失败的网元显示提示信息 -->
<div
v-if="modalState.results.filter(r => !r.success).length === 0"
style="text-align: center; padding: 20px; color: #52c41a;"
>
<CheckCircleOutlined :style="{ marginRight: '8px' }" />
{{ t('views.ne.neInfo.quickOam.allsuccess') }}
</div>
</div>
</div>
</ProModal>

View File

@@ -23,9 +23,11 @@ import {
importUDMAuth,
resetUDMAuth,
listUDMAuth,
exportUDMDecAuth,
} from '@/api/neData/udm_auth';
import { uploadFile } from '@/api/tool/file';
import { getNeViewFile } from '@/api/tool/neFile';
import { parseDateToStr } from '@/utils/date-utils';
const { t } = useI18n();
const neListStore = useNeListStore();
/**网元参数 */
@@ -117,6 +119,16 @@ let tableColumns = ref<ColumnsType>([
align: 'center',
width: 100,
},
{
title: 'Create Time',
dataIndex: 'createTime',
align: 'left',
width: 250,
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(+opt.value);
},
},
{
title: t('common.operate'),
key: 'imsi',
@@ -521,6 +533,25 @@ function fnExportList(type: string) {
});
}
/** UDM鉴权用户导出DecAuth */
function fnExportDec() {
const neId = queryParams.neId;
if (!neId) return;
const hide = message.loading(t('common.loading'), 0);
exportUDMDecAuth(neId)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.msgSuccess', { msg: t('common.export') }), 3);
saveAs(res.data, `UDMAuth_dec_${Date.now()}.txt`);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
}
/**重新加载数据 */
function fnLoadData() {
const neId = queryParams.neId;
@@ -883,6 +914,14 @@ onMounted(() => {
{{ t('views.neUser.auth.export') }}
</a-button>
</a-popconfirm>
<a-button
type="dashed"
@click="fnExportDec()"
v-perms:has="['neData:udm-auth:export-dec']"
>
<template #icon><ExportOutlined /></template>
Export DecData
</a-button>
<a-button
type="default"

View File

@@ -27,6 +27,7 @@ import {
import { getNeConfigData, addNeConfigData } from '@/api/ne/neConfig';
import { uploadFile } from '@/api/tool/file';
import { getNeViewFile } from '@/api/tool/neFile';
import { parseDateToStr } from '@/utils/date-utils';
const { t } = useI18n();
const neListStore = useNeListStore();
/**网元参数 */
@@ -159,6 +160,16 @@ let tableColumns = ref<ColumnsType>([
minWidth: 150,
maxWidth: 500,
},
{
title: 'Create Time',
dataIndex: 'createTime',
align: 'left',
width: 250,
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(+opt.value);
},
},
{
title: t('common.operate'),
key: 'imsi',
@@ -1331,7 +1342,11 @@ onMounted(() => {
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-flex wrap="wrap" gap="small">
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()" v-perms:has="['neData:udm-sub:add']">
<a-button
type="primary"
@click.prevent="fnModalVisibleByEdit()"
v-perms:has="['neData:udm-sub:add']"
>
<template #icon>
<PlusOutlined />
</template>
@@ -1372,7 +1387,11 @@ onMounted(() => {
</a-button>
</a-popconfirm>
<a-button type="dashed" @click.prevent="fnModalUploadImportOpen" v-perms:has="['neData:udm-sub:import']">
<a-button
type="dashed"
@click.prevent="fnModalUploadImportOpen"
v-perms:has="['neData:udm-sub:import']"
>
<template #icon>
<ImportOutlined />
</template>

View File

@@ -29,6 +29,7 @@ import { saveAs } from 'file-saver';
import { uploadFile } from '@/api/tool/file';
import { getNeViewFile } from '@/api/tool/neFile';
import { hasPermissions } from '@/plugins/auth-user';
import { parseDateToStr } from '@/utils/date-utils';
const { t } = useI18n();
const neListStore = useNeListStore();
/**网元参数 */
@@ -51,9 +52,10 @@ let queryParams = reactive({
});
const hasAnyBatchPermission = computed(() => {
return hasPermissions(['neData:udm-voip:add']) ||
hasPermissions(['neData:udm-voip:delete']) ;
return (
hasPermissions(['neData:udm-voip:add']) ||
hasPermissions(['neData:udm-voip:delete'])
);
});
/**查询参数重置 */
@@ -101,6 +103,16 @@ let tableColumns = ref<TableColumnsType>([
minWidth: 100,
maxWidth: 300,
},
{
title: 'Create Time',
dataIndex: 'createTime',
align: 'left',
width: 250,
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(+opt.value);
},
},
{
title: t('common.operate'),
key: 'id',
@@ -668,7 +680,11 @@ onMounted(() => {
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()" v-perms:has="['neData:udm-voip:add']">
<a-button
type="primary"
@click.prevent="fnModalVisibleByEdit()"
v-perms:has="['neData:udm-voip:add']"
>
<template #icon>
<PlusOutlined />
</template>
@@ -694,11 +710,17 @@ onMounted(() => {
</a-button>
<template #overlay>
<a-menu @click="({ key }:any) => fnModalVisibleByBatch(key)">
<a-menu-item key="add" v-if="hasPermissions(['neData:udm-voip:add'])">
<a-menu-item
key="add"
v-if="hasPermissions(['neData:udm-voip:add'])"
>
<PlusOutlined />
{{ t('views.neData.common.batchAddText') }}
</a-menu-item>
<a-menu-item key="delete" v-if="hasPermissions(['neData:udm-voip:delete'])">
<a-menu-item
key="delete"
v-if="hasPermissions(['neData:udm-voip:delete'])"
>
<DeleteOutlined />
{{ t('views.neData.common.batchDelText') }}
</a-menu-item>
@@ -726,7 +748,11 @@ onMounted(() => {
</a-button>
</a-popconfirm>
<a-button type="dashed" @click.prevent="fnModalUploadImportOpen" v-perms:has="['neData:udm-voip:import']">
<a-button
type="dashed"
@click.prevent="fnModalUploadImportOpen"
v-perms:has="['neData:udm-voip:import']"
>
<template #icon><ImportOutlined /></template>
{{ t('common.import') }}
</a-button>

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw ,computed} from 'vue';
import { reactive, ref, onMounted, toRaw, computed } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { ProModal } from 'antdv-pro-modal';
import {
@@ -29,11 +29,14 @@ import {
resetUDMVolteIMS,
} from '@/api/neData/udm_volte_ims';
import { hasPermissions } from '@/plugins/auth-user';
import { parseDateToStr } from '@/utils/date-utils';
const { t } = useI18n();
const neListStore = useNeListStore();
const hasAnyBatchPermission = computed(() => {
return hasPermissions(['neData:udm-volte:add']) ||
hasPermissions(['neData:udm-volte:delete']) ;
return (
hasPermissions(['neData:udm-volte:add']) ||
hasPermissions(['neData:udm-volte:delete'])
);
});
/**字典数据 */
let dict: {
@@ -150,6 +153,16 @@ let tableColumns = ref<TableColumnsType>([
minWidth: 250,
maxWidth: 400,
},
{
title: 'Create Time',
dataIndex: 'createTime',
align: 'left',
width: 250,
customRender(opt) {
if (!opt.value) return '';
return parseDateToStr(+opt.value);
},
},
{
title: t('common.operate'),
key: 'id',
@@ -762,7 +775,11 @@ onMounted(() => {
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()" v-perms:has="['neData:udm-volte:add']">
<a-button
type="primary"
@click.prevent="fnModalVisibleByEdit()"
v-perms:has="['neData:udm-volte:add']"
>
<template #icon>
<PlusOutlined />
</template>
@@ -788,11 +805,17 @@ onMounted(() => {
</a-button>
<template #overlay>
<a-menu @click="({ key }:any) => fnModalVisibleByBatch(key)">
<a-menu-item key="add" v-if="hasPermissions(['neData:udm-volte:add'])">
<a-menu-item
key="add"
v-if="hasPermissions(['neData:udm-volte:add'])"
>
<PlusOutlined />
{{ t('views.neData.common.batchAddText') }}
</a-menu-item>
<a-menu-item key="delete" v-if="hasPermissions(['neData:udm-volte:delete'])">
<a-menu-item
key="delete"
v-if="hasPermissions(['neData:udm-volte:delete'])"
>
<DeleteOutlined />
{{ t('views.neData.common.batchDelText') }}
</a-menu-item>
@@ -820,7 +843,11 @@ onMounted(() => {
</a-button>
</a-popconfirm>
<a-button type="dashed" @click.prevent="fnModalUploadImportOpen" v-perms:has="['neData:udm-volte:import']">
<a-button
type="dashed"
@click.prevent="fnModalUploadImportOpen"
v-perms:has="['neData:udm-volte:import']"
>
<template #icon><ImportOutlined /></template>
{{ t('common.import') }}
</a-button>