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 ## 2.2508.4-20250829
- 新增 网元授权显示用户数/基站数 - 新增 网元授权显示用户数/基站数

View File

@@ -140,3 +140,17 @@ export function exportUDMAuth(data: Record<string, any>) {
timeout: 180_000, 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', result:'Result',
success:'Success', success:'Success',
default:'Default', default:'Default',
allsuccess:'All NE have been configured successfully!',
}, },
quickUpload: { quickUpload: {
title: 'Quick License Upload', title: 'Quick License Upload',

View File

@@ -600,6 +600,7 @@ export default {
result:'操作结果', result:'操作结果',
success:'成功', success:'成功',
default:'失败', default:'失败',
allsuccess:'所有网元配置成功!',
}, },
quickUpload: { quickUpload: {
title: '快速许可证上传', title: '快速许可证上传',

View File

@@ -33,10 +33,10 @@ const queue = new PQueue({ concurrency: 1, autoStart: true });
/**字典数据 */ /**字典数据 */
let dict: { let dict: {
/**CDR 响应原因代码类别类型 */ /**CDR SMSC 响应原因代码类别类型 */
cdrCauseCode: DictType[]; cdrSMSCCauseCode: DictType[];
} = reactive({ } = reactive({
cdrCauseCode: [], cdrSMSCCauseCode: [],
}); });
/**网元可选 */ /**网元可选 */
@@ -186,12 +186,19 @@ let tableColumns: ColumnsType = [
}, },
{ {
title: t('views.dashboard.cdr.result'), title: t('views.dashboard.cdr.resultCode'),
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
key: 'cause', key: 'cause',
align: 'left', align: 'left',
width: 200, width: 200,
}, },
{
title: t('views.dashboard.cdr.resultCause'),
dataIndex: 'cdrJSON',
key: 'result',
align: 'left',
width: 200,
},
{ {
title: t('views.dashboard.cdr.time'), title: t('views.dashboard.cdr.time'),
dataIndex: 'cdrJSON', dataIndex: 'cdrJSON',
@@ -472,9 +479,9 @@ function wsMessage(res: Record<string, any>) {
onMounted(() => { onMounted(() => {
// 初始字典数据 // 初始字典数据
Promise.allSettled([getDict('cdr_cause_code')]).then(resArr => { Promise.allSettled([getDict('cdr_cause_smsc')]).then(resArr => {
if (resArr[0].status === 'fulfilled') { 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 #bodyCell="{ column, record }">
<template v-if="column.key === 'cause'"> <template v-if="column.key === 'cause'">
<span v-if="record.cdrJSON.result === 0"> <span v-if="record.cdrJSON.result === 0"> FAILED </span>
{{ t('views.dashboard.cdr.resultFail') }}, <span v-else> SUCCESS </span>
<DictTag </template>
:options="dict.cdrCauseCode" <template v-if="column.key === 'result'">
:value="record.cdrJSON.cause" <DictTag
value-default="0" :options="dict.cdrSMSCCauseCode"
/> :value="record.cdrJSON.cause"
</span> value-default="0"
<span v-else> />
{{ t('views.dashboard.cdr.resultOk') }}
</span>
</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">
@@ -794,18 +799,19 @@ onBeforeUnmount(() => {
<span>{{ record.cdrJSON.calledParty }}</span> <span>{{ record.cdrJSON.calledParty }}</span>
</div> </div>
<div> <div>
<span>{{ t('views.dashboard.cdr.result') }}: </span> <span>{{ t('views.dashboard.cdr.resultCode') }}: </span>
<span v-if="record.cdrJSON.result === 0"> <span v-if="record.cdrJSON.result === 0"> FAILED </span>
{{ t('views.dashboard.cdr.resultFail') }}, <span v-else> SUCCESS </span>
</div>
<div>
<span>{{ t('views.dashboard.cdr.resultCause') }}: </span>
<span>
<DictTag <DictTag
:options="dict.cdrCauseCode" :options="dict.cdrSMSCCauseCode"
:value="record.cdrJSON.cause" :value="record.cdrJSON.cause"
value-default="0" value-default="0"
/> />
</span> </span>
<span v-else>
{{ t('views.dashboard.cdr.resultOk') }}
</span>
</div> </div>
</a-col> </a-col>
<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); 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) { } catch (error: any) {
message.error(t('common.errorFields', { num: error.errorFields?.length || 0 }), 3); message.error(t('common.errorFields', { num: error.errorFields?.length || 0 }), 3);
} finally { } finally {
@@ -271,6 +272,13 @@ function fnModalCancel() {
emit('update:open', false); emit('update:open', false);
} }
/**
* 手动关闭弹窗(用于操作完成后)
*/
function fnModalClose() {
fnModalCancel();
}
/**监听是否显示,初始数据 */ /**监听是否显示,初始数据 */
watch( watch(
() => props.open, () => props.open,
@@ -365,12 +373,12 @@ watch(
{{ modalState.progress.current }} / {{ modalState.progress.total }} {{ modalState.progress.current }} / {{ modalState.progress.total }}
</span> </span>
</div> </div>
<!-- <Progress--> <!-- <Progress-->
<!-- :percent="Math.round((modalState.progress.current / modalState.progress.total) * 100)"--> <!-- :percent="Math.round((modalState.progress.current / modalState.progress.total) * 100)"-->
<!-- :status="modalState.progress.current === modalState.progress.total ? 'success' : 'active'"--> <!-- :status="modalState.progress.current === modalState.progress.total ? 'success' : 'active'"-->
<!-- :show-info="false"--> <!-- :show-info="false"-->
<!-- style="overflow: hidden;"--> <!-- style="overflow: hidden;"-->
<!-- />--> <!-- />-->
<div style="width: 100%; overflow: hidden;"> <div style="width: 100%; overflow: hidden;">
<Progress <Progress
:percent="Math.round((modalState.progress.current / modalState.progress.total) * 100)" :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;"> <div v-if="modalState.results.length > 0" style="margin-top: 20px;">
<a-divider>{{ t('views.ne.neInfo.quickOam.result') }}</a-divider> <a-divider>{{ t('views.ne.neInfo.quickOam.result') }}</a-divider>
<div style="max-height: 200px; overflow-y: auto;"> <div style="max-height: 200px; overflow-y: auto;">
<!-- 只显示失败的网元 -->
<div <div
v-for="(result, index) in modalState.results" v-for="(result, index) in modalState.results.filter(r => !r.success)"
:key="index" :key="index"
style="margin-bottom: 8px; padding: 8px; border-radius: 4px;" style="margin-bottom: 8px; padding: 8px; border-radius: 4px;"
:style="{ :style="{
backgroundColor: result.success ? '#f6ffed' : '#fff2f0', backgroundColor: '#fff2f0',
border: `1px solid ${result.success ? '#b7eb8f' : '#ffccc7'}`, border: '1px solid #ffccc7',
}" }"
> >
<div style="display: flex; justify-content: space-between; align-items: center;"> <div style="display: flex; justify-content: space-between; align-items: center;">
<span> <span>
<CheckCircleOutlined
v-if="result.success"
:style="{ color: '#52c41a', marginRight: '8px' }"
/>
<CloseCircleOutlined <CloseCircleOutlined
v-else
:style="{ color: '#ff4d4f', marginRight: '8px' }" :style="{ color: '#ff4d4f', marginRight: '8px' }"
/> />
{{ result.neType }}-{{ result.neId }} ({{ result.neName }}) {{ result.neType }}-{{ result.neId }} ({{ result.neName }})
</span> </span>
<span :style="{ color: result.success ? '#52c41a' : '#ff4d4f', fontSize: '12px' }"> <span :style="{ color: '#ff4d4f', fontSize: '12px' }">
{{ result.message }} {{ result.message }}
</span> </span>
</div> </div>
</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>
</div> </div>
</ProModal> </ProModal>

View File

@@ -23,9 +23,11 @@ import {
importUDMAuth, importUDMAuth,
resetUDMAuth, resetUDMAuth,
listUDMAuth, listUDMAuth,
exportUDMDecAuth,
} from '@/api/neData/udm_auth'; } from '@/api/neData/udm_auth';
import { uploadFile } from '@/api/tool/file'; import { uploadFile } from '@/api/tool/file';
import { getNeViewFile } from '@/api/tool/neFile'; import { getNeViewFile } from '@/api/tool/neFile';
import { parseDateToStr } from '@/utils/date-utils';
const { t } = useI18n(); const { t } = useI18n();
const neListStore = useNeListStore(); const neListStore = useNeListStore();
/**网元参数 */ /**网元参数 */
@@ -117,6 +119,16 @@ let tableColumns = ref<ColumnsType>([
align: 'center', align: 'center',
width: 100, 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'), title: t('common.operate'),
key: 'imsi', 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() { function fnLoadData() {
const neId = queryParams.neId; const neId = queryParams.neId;
@@ -883,6 +914,14 @@ onMounted(() => {
{{ t('views.neUser.auth.export') }} {{ t('views.neUser.auth.export') }}
</a-button> </a-button>
</a-popconfirm> </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 <a-button
type="default" type="default"

View File

@@ -27,6 +27,7 @@ import {
import { getNeConfigData, addNeConfigData } from '@/api/ne/neConfig'; import { getNeConfigData, addNeConfigData } from '@/api/ne/neConfig';
import { uploadFile } from '@/api/tool/file'; import { uploadFile } from '@/api/tool/file';
import { getNeViewFile } from '@/api/tool/neFile'; import { getNeViewFile } from '@/api/tool/neFile';
import { parseDateToStr } from '@/utils/date-utils';
const { t } = useI18n(); const { t } = useI18n();
const neListStore = useNeListStore(); const neListStore = useNeListStore();
/**网元参数 */ /**网元参数 */
@@ -159,6 +160,16 @@ let tableColumns = ref<ColumnsType>([
minWidth: 150, minWidth: 150,
maxWidth: 500, 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'), title: t('common.operate'),
key: 'imsi', key: 'imsi',
@@ -1331,7 +1342,11 @@ onMounted(() => {
<!-- 插槽-卡片左侧侧 --> <!-- 插槽-卡片左侧侧 -->
<template #title> <template #title>
<a-flex wrap="wrap" gap="small"> <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> <template #icon>
<PlusOutlined /> <PlusOutlined />
</template> </template>
@@ -1372,7 +1387,11 @@ onMounted(() => {
</a-button> </a-button>
</a-popconfirm> </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> <template #icon>
<ImportOutlined /> <ImportOutlined />
</template> </template>

View File

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

View File

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