Merge branch 'main-v2' into lite-ba
This commit is contained in:
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,5 +1,21 @@
|
||||
# 版本发布日志
|
||||
|
||||
## 2.2509.4-20250926
|
||||
|
||||
- 优化 UDM数据显示创建时间列
|
||||
- 优化 中英文补充
|
||||
- 新增 添加系统备份功能,包括导入导出OMC的API和界面支持
|
||||
- 新增 日志备份文件查看kpi文件记录
|
||||
- 优化 根据搜索条件导出,去除最大记录限制
|
||||
- 新增 添加活动告警自动刷新功能并优化代码结构
|
||||
- 修复 奇安信浏览器时区格式错误
|
||||
- 新增 添加网元配置快速PLMN修改框
|
||||
- 新增 ue和ne模块权限按钮
|
||||
- 新增 监控模块按钮权限
|
||||
- 修复 UDM数据导出提示信息,导出总数
|
||||
- 新增 网元许可快速上传功能
|
||||
- 新增 网元信息快速OAM功能
|
||||
|
||||
## 2.2508.4-20250829
|
||||
|
||||
- 新增 网元授权显示用户数/基站数
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user