diff --git a/CHANGELOG.md b/CHANGELOG.md index 94bff81b..8fc5857b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # 版本发布日志 +## 2.2506.3-20250620 + +- 新增 UDM签约cnType新增可选1仅5G/2仅4G +- 新增 给AMF/MME对应的IMEI白名单添加批量导入和批量删除功能 +- 新增 UDM voup/ims导入失败文件下载 + ## 2.2506.2-20250613 - 修复 数值控件属性maxlength拼写错误 diff --git a/public/neDataImput/import_amf_imeiWhitelist_template.xlsx b/public/neDataImput/import_amf_imeiWhitelist_template.xlsx new file mode 100644 index 00000000..8716464a Binary files /dev/null and b/public/neDataImput/import_amf_imeiWhitelist_template.xlsx differ diff --git a/public/neDataImput/import_amf_whitelist_template.xlsx b/public/neDataImput/import_amf_whitelist_template.xlsx new file mode 100644 index 00000000..26b66e5a Binary files /dev/null and b/public/neDataImput/import_amf_whitelist_template.xlsx differ diff --git a/public/neDataImput/import_mme_imeiWhitelist_template.xlsx b/public/neDataImput/import_mme_imeiWhitelist_template.xlsx new file mode 100644 index 00000000..44957994 Binary files /dev/null and b/public/neDataImput/import_mme_imeiWhitelist_template.xlsx differ diff --git a/src/api/neData/udm_volte_ims.ts b/src/api/neData/udm_volte_ims.ts index 7a96bd30..7b65f0fb 100644 --- a/src/api/neData/udm_volte_ims.ts +++ b/src/api/neData/udm_volte_ims.ts @@ -71,13 +71,16 @@ export function batchAddUDMVolteIMS(data: Record, num: number) { /** * UDMVolteIMS用户删除 - * @param data 签约对象 + * @param neId 网元ID + * @param imsi_msisdn IMSI/MSISDN + * @param tag 标签 0-voip 1-volte * @returns object */ -export function delUDMVolteIMS(neId: string, imsi: string) { +export function delUDMVolteIMS(neId: string, imsi_msisdn: string, tag: string) { return request({ - url: `/neData/udm/volte-ims/${neId}/${imsi}`, + url: `/neData/udm/volte-ims/${neId}/${imsi_msisdn}`, method: 'DELETE', + params: { volte: tag }, timeout: 180_000, }); } @@ -87,12 +90,19 @@ export function delUDMVolteIMS(neId: string, imsi: string) { * @param neId 网元ID * @param imsi IMSI * @param num 数量 + * @param tag 标签 0-voip 1-volte * @returns object */ -export function batchDelUDMVolteIMS(neId: string, imsi: string, num: number) { +export function batchDelUDMVolteIMS( + neId: string, + imsi: string, + num: number, + tag: string +) { return request({ url: `/neData/udm/volte-ims/${neId}/${imsi}/${num}`, method: 'DELETE', + params: { volte: tag }, timeout: 180_000, }); } diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index 2571e585..485d9614 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -877,6 +877,10 @@ export default { rfspTip:'RFSP index, in NG-RAN, the index of a specific RRM configuration, parameter between 0 and 127', ueTypeTip: 'Operator-defined subscriber UE Usage Type, integer, parameter between 0 and 127', cnFlag: 'Whether to enable 5G Core Network service', + cnFlag0: 'No Access Allowed', + cnFlag1: 'Access Only 5G', + cnFlag2: 'Access Only 4G', + cnFlag3: 'Access 4G/5G', epsFlagTip: 'Whether to enable 4G EPS service', contextIdTip: 'To sign up for an APN Context ID, you must select it from the APN Context list.', apnContextTip: 'The list of APNs available to the phone, up to six, is defined in the HSS.', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 740035f4..6afa5faa 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -877,6 +877,10 @@ export default { rfspTip:'RFSP 索引,在 NG-RAN 中,特定 RRM 配置的索引,参数介于0到127之间', ueTypeTip: '运营商定义的用户 UE Usage Type,整型,参数介于0到127之间', cnFlag: '是否开启 5G Core Network 服务', + cnFlag0: '不允许接入', + cnFlag1: '只能接入 5G', + cnFlag2: '只能接入 4G', + cnFlag3: '允许接入 4G/5G', epsFlagTip: '是否开启 4G EPS 服务', contextIdTip: '签约APN 上下文ID,必须从APN Context list 中选择。', apnContextTip: '手机可用的APN列表,最多六个,在HSS中定义。', diff --git a/src/views/ne/neConfig/hooks/useArrayBatchDel.ts b/src/views/ne/neConfig/hooks/useArrayBatchDel.ts new file mode 100644 index 00000000..78857c28 --- /dev/null +++ b/src/views/ne/neConfig/hooks/useArrayBatchDel.ts @@ -0,0 +1,85 @@ +import { delNeConfigData } from '@/api/ne/neConfig'; +import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; +import { message } from 'ant-design-vue'; +import { reactive, toRaw } from 'vue'; + +/** + * 批量删除array + * @param param 父级传入 { t, neTypeSelect, fnActiveConfigNode } + * @returns + */ +export default function useArrayBatch({ + t, + neTypeSelect, + fnActiveConfigNode, +}: any) { + /**状态属性 */ + const batchState = reactive({ + open: false, + loading: false, //批量删除 + paramName: '', + startIndex: 1, + num: 1, + }); + + /**对话框表格信息导入弹出窗口 */ + function modalBatchOpen(paramName: string) { + batchState.paramName = paramName; + batchState.open = true; + } + function modalBatchClose() { + if (batchState.loading) { + message.error({ + content: 'Delete is in progress, please wait for it to complete', + duration: 3, + }); + return; + } + batchState.open = false; + batchState.loading = false; + batchState.startIndex = 1; + batchState.num = 1; + fnActiveConfigNode('#'); + } + + async function modalBatchOk() { + let okNum = 0; + let failNum = 0; + const endIndex = batchState.startIndex + batchState.num - 1; + for (let i = endIndex; i >= batchState.startIndex; i--) { + const res = await delNeConfigData({ + neType: neTypeSelect.value[0], + neId: neTypeSelect.value[1], + paramName: batchState.paramName, + loc: `${i}`, + }); + if (res.code === RESULT_CODE_SUCCESS) { + okNum++; + } else { + failNum++; + break; + } + } + + if (okNum > 0) { + message.success({ + content: `Successfully deleted ${okNum} items`, + duration: 3, + }); + } + if (failNum > 0) { + message.error({ + content: `Delete failed, please check the index range`, + duration: 3, + }); + } + modalBatchClose(); + } + + return { + batchState, + modalBatchOpen, + modalBatchClose, + modalBatchOk, + }; +} diff --git a/src/views/ne/neConfig/hooks/useArrayImport.ts b/src/views/ne/neConfig/hooks/useArrayImport.ts new file mode 100644 index 00000000..2dd28b68 --- /dev/null +++ b/src/views/ne/neConfig/hooks/useArrayImport.ts @@ -0,0 +1,205 @@ +import { addNeConfigData, editNeConfigData } from '@/api/ne/neConfig'; +import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; +import { readSheet } from '@/utils/execl-utils'; +import { message } from 'ant-design-vue'; +import { reactive, toRaw } from 'vue'; +import saveAs from 'file-saver'; + +/** + * 导入文件加array + * @param param 父级传入 { t, neTypeSelect, arrayState, fnActiveConfigNode } + * @returns + */ +export default function useArrayImport({ + t, + neTypeSelect, + arrayState, + fnActiveConfigNode, +}: any) { + /**网元导入模板解析 */ + const m: Record = { + AMF: { + imeiWhitelist: { + filename: 'import_amf_imeiWhitelist_template', + fileetx: '.xlsx', + itemKey: 'imeiPrefixValue', + item: (row: Record) => { + return { + imeiPrefixValue: row['IMEI Prefix'], + index: 0, + }; + }, + }, + whitelist: { + filename: 'import_amf_whitelist_template', + fileetx: '.xlsx', + itemKey: 'imsiValue', + item: (row: Record) => { + return { + imsiValue: row['IMSI Value'], + imeiValue: row['IMEI Value/Prefix'], + index: 0, + }; + }, + }, + }, + MME: { + white_list: { + filename: 'import_mme_imeiWhitelist_template', + fileetx: '.xlsx', + itemKey: 'imei', + item: (row: Record) => { + return { + imei: row['IMEI'], + index: 0, + }; + }, + }, + }, + }; + + /**状态属性 */ + const importState = reactive({ + open: false, + msgArr: [] as string[], + loading: false, //开始导入 + itemKey: '', // 解析item的key + item: null as any, // 解析item方法 + paramName: '', + filename: '', + fileetx: '', + }); + + /**对话框表格信息导入弹出窗口 */ + function modalImportOpen(neType: string, paramName: string) { + const tmpM = m[neType][paramName]; + importState.itemKey = tmpM.itemKey; + importState.item = tmpM.item; + importState.paramName = paramName; + importState.filename = tmpM.filename; + importState.fileetx = tmpM.fileetx; + importState.open = true; + } + function modalImportClose() { + if (importState.loading) { + message.error({ + content: 'Import is in progress, please wait for it to complete', + duration: 3, + }); + return; + } + importState.open = false; + importState.msgArr = []; + importState.loading = false; + fnActiveConfigNode('#'); + } + + /**对话框表格信息导入上传 */ + async function modalImportUpload(file: File) { + const hide = message.loading(t('common.loading'), 0); + const [neType, neId] = neTypeSelect.value; + importState.msgArr = []; + + // 获取最大index + let index = 0; + if (arrayState.columnsData.length <= 0) { + index = 0; + } else { + const last = arrayState.columnsData[arrayState.columnsData.length - 1]; + index = last.index.value + 1; + } + + const reader = new FileReader(); + reader.onload = function (e: any) { + const arrayBuffer = e.target.result; + readSheet(arrayBuffer).then(async rows => { + if (rows.length <= 0) { + hide(); + message.error({ + content: t('views.neData.baseStation.importDataEmpty'), + duration: 3, + }); + return; + } + // 开始导入 + importState.loading = true; + for (const row of rows) { + const rowItem = importState.item(row); + const rowKey = rowItem[importState.itemKey]; + let result: any = null; + // 检查index是否定义 + const has = arrayState.columnsData.find( + (item: any) => item[importState.itemKey].value === rowKey + ); + if (has) { + // 已定义则更新 + rowItem.index = has.index.value; + result = await editNeConfigData({ + neType: neType, + neId: neId, + paramName: importState.paramName, + paramData: rowItem, + loc: `${rowItem.index}`, + }); + let msg = `index:${rowItem.index} update fail`; + if (result.code === RESULT_CODE_SUCCESS) { + msg = `index:${rowItem.index} update success`; + } + importState.msgArr.push(msg); + } else { + // 未定义则新增 + result = await addNeConfigData({ + neType: neType, + neId: neId, + paramName: importState.paramName, + paramData: Object.assign(rowItem, { index }), + loc: `${index}`, + }); + let msg = `index:${index} add fail`; + if (result.code === RESULT_CODE_SUCCESS) { + msg = `index:${index} add success`; + index += 1; + } + importState.msgArr.push(msg); + } + } + + hide(); + importState.loading = false; + }); + }; + reader.onerror = function (e) { + hide(); + console.error('reader file error:', e); + }; + reader.readAsArrayBuffer(file); + } + + /**对话框表格信息导入模板 */ + function modalImportTemplate() { + const hide = message.loading(t('common.loading'), 0); + + const baseUrl = import.meta.env.VITE_HISTORY_BASE_URL; + const templateUrl = `${ + baseUrl.length === 1 && baseUrl.indexOf('/') === 0 + ? '' + : baseUrl.indexOf('/') === -1 + ? '/' + baseUrl + : baseUrl + }/neDataImput`; + saveAs( + `${templateUrl}/${importState.filename}${importState.fileetx}`, + `${importState.filename}_${Date.now()}${importState.fileetx}` + ); + + hide(); + } + + return { + importState, + modalImportOpen, + modalImportClose, + modalImportUpload, + modalImportTemplate, + }; +} diff --git a/src/views/ne/neConfig/index.vue b/src/views/ne/neConfig/index.vue index f410ad2d..1bb48242 100644 --- a/src/views/ne/neConfig/index.vue +++ b/src/views/ne/neConfig/index.vue @@ -12,6 +12,8 @@ import useOptions from './hooks/useOptions'; import useConfigList from './hooks/useConfigList'; import useConfigArray from './hooks/useConfigArray'; import useConfigArrayChild from './hooks/useConfigArrayChild'; +import useArrayImport from './hooks/useArrayImport'; +import useArrayBatchDel from './hooks/useArrayBatchDel'; import { getAllNeConfig, getNeConfigData } from '@/api/ne/neConfig'; const neListStore = useNeStore(); const { t } = useI18n(); @@ -225,10 +227,13 @@ function fnGetNeConfig() { } else { paramPerms = ['post', 'put', 'delete']; } + const title = item.paramDisplay; + // 处理字符串开头特殊字符 + item.paramDisplay = title.replace(/[└─]+/, ''); arr.push({ ...item, children: undefined, - title: item.paramDisplay, + title: title, key: item.paramName, paramPerms, }); @@ -362,6 +367,21 @@ const { arrayEditClose, }); +const { + importState, + modalImportOpen, + modalImportClose, + modalImportUpload, + modalImportTemplate, +} = useArrayImport({ t, neTypeSelect, arrayState, fnActiveConfigNode }); + +const { batchState, modalBatchOpen, modalBatchClose, modalBatchOk } = + useArrayBatchDel({ + t, + neTypeSelect, + fnActiveConfigNode, + }); + onMounted(() => { // 获取网元网元列表 neCascaderOptions.value = neListStore.getNeCascaderOptions.filter( @@ -428,7 +448,7 @@ onMounted(() => { -   +   + { :size="arrayState.size" :pagination="tablePagination" :bordered="true" - :scroll="{ x: arrayState.columnsDnd.length * 200, y: 480 }" + :scroll="{ x: arrayState.columnsDnd.length * 200, y: '500px' }" @resizeColumn="(w:number, col:any) => (col.width = w)" :show-expand-column="false" v-model:expanded-row-keys="arrayState.arrayChildExpandKeys" @@ -628,6 +649,38 @@ onMounted(() => { :columns="treeState.selectNode.paramPerms.includes('get') ? [...arrayState.columns.filter((s:any)=>s.key !== 'index')] : arrayState.columns" v-model:columns-dnd="arrayState.columnsDnd" > + + @@ -905,6 +958,84 @@ onMounted(() => { + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/views/neData/udm-sub/index.vue b/src/views/neData/udm-sub/index.vue index 64591ce8..cc9f6f5d 100644 --- a/src/views/neData/udm-sub/index.vue +++ b/src/views/neData/udm-sub/index.vue @@ -1668,10 +1668,16 @@ onMounted(() => { > - {{ t('views.neUser.sub.enable') }} + {{ t('views.neUser.sub.cnFlag3') }} + + + {{ t('views.neUser.sub.cnFlag2') }} + + + {{ t('views.neUser.sub.cnFlag1') }} - {{ t('views.neUser.sub.disable') }} + {{ t('views.neUser.sub.cnFlag0') }} diff --git a/src/views/neData/udm-volte/index.vue b/src/views/neData/udm-volte/index.vue index 1706d303..246df18b 100644 --- a/src/views/neData/udm-volte/index.vue +++ b/src/views/neData/udm-volte/index.vue @@ -94,7 +94,7 @@ type TabeStateType = { data: object[]; /**勾选记录 */ selectedRowKeys: (string | number)[]; - selectedRowIMSIs: (string | number)[]; + selectedRowIMSIs: Record[]; }; /**表格状态 */ @@ -203,7 +203,13 @@ function fnTableSize({ key }: MenuInfo) { /**表格多选 */ function fnTableSelectedRowKeys(keys: (string | number)[], rows: any[]) { tableState.selectedRowKeys = keys; - tableState.selectedRowIMSIs = rows.map(item => item.imsi); + tableState.selectedRowIMSIs = rows.map(item => { + return { + imsi: item.imsi, + msisdn: item.msisdn, + tag: item.tag, + }; + }); } /**对话框对象信息状态类型 */ @@ -309,7 +315,12 @@ function fnModalOk() { result = batchAddUDMVolteIMS(from, from.num); } if (modalState.type === 'delete') { - result = batchDelUDMVolteIMS(from.neId, from.imsi, from.num); + result = batchDelUDMVolteIMS( + from.neId, + `${from.imsi}_${from.msisdn}`, + from.num, + from.tag + ); } } else { if (modalState.type === 'add') { @@ -375,15 +386,14 @@ function fnModalVisibleByBatch(type: 'delete' | 'add') { /** * 记录删除 - * @param imsi 网元编号ID + * @param id 记录ID */ -function fnRecordDelete(imsi: string) { +function fnRecordDelete(id: string) { const neID = queryParams.neId; if (!neID) return; - let msg = imsi; - if (imsi === '0') { - msg = `${tableState.selectedRowIMSIs[0]}... ${tableState.selectedRowIMSIs.length}`; - imsi = tableState.selectedRowIMSIs.join(','); + let msg = id; + if (id === '0') { + msg = `${tableState.selectedRowIMSIs[0].imsi}... ${tableState.selectedRowIMSIs.length}`; } Modal.confirm({ @@ -391,17 +401,34 @@ function fnRecordDelete(imsi: string) { content: t('views.neData.udmVolteIMS.delTip', { num: msg }), onOk() { const hide = message.loading(t('common.loading'), 0); - delUDMVolteIMS(neID, imsi) - .then(res => { - if (res.code === RESULT_CODE_SUCCESS) { - message.success(t('common.operateOk'), 3); - fnGetList(); - } else { - message.error({ - content: `${res.msg}`, - duration: 3, - }); - } + let reqArr: any[] = []; + if (id === '0') { + const volteArr = tableState.selectedRowIMSIs + .filter(item => item.tag == '1') + .map(item => `${item.imsi}_${item.msisdn}`) + .join(','); + if (volteArr.length > 0) { + reqArr.push(delUDMVolteIMS(neID, volteArr, '1')); + } + const voipArr = tableState.selectedRowIMSIs + .filter(item => item.tag == '0') + .map(item => `${item.imsi}_${item.msisdn}`) + .join(','); + if (voipArr.length > 0) { + reqArr.push(delUDMVolteIMS(neID, voipArr, '0')); + } + } else { + const record: any = tableState.data.find((item: any) => item.id === id); + if (record) { + reqArr = [ + delUDMVolteIMS(neID, `${record.imsi}_${record.msisdn}`, record.tag), + ]; + } + } + Promise.all(reqArr) + .then(() => { + message.success(t('common.operateOk'), 3); + fnGetList(); }) .finally(() => { hide(); @@ -887,7 +914,7 @@ onMounted(() => { + { : '' " :label=" - modalState.isBatch && modalState.from.tag === '0' + modalState.isBatch ? t('views.neData.udmVolteIMS.startMSISDN') : 'MSISDN' "