Merge branch 'main-v2' into lite

This commit is contained in:
TsMask
2025-07-01 10:29:56 +08:00
20 changed files with 883 additions and 129 deletions

View File

@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
VITE_APP_CODE = "OMC"
# 应用版本
VITE_APP_VERSION = "2.250529"
VITE_APP_VERSION = "2.2506.4"
# 接口基础URL地址-不带/后缀
VITE_API_BASE_URL = "/omc-api"

View File

@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
VITE_APP_CODE = "OMC"
# 应用版本
VITE_APP_VERSION = "2.250529"
VITE_APP_VERSION = "2.2506.4"
# 接口基础URL地址-不带/后缀
VITE_API_BASE_URL = "/omc-api"

View File

@@ -1,5 +1,110 @@
# 版本发布日志
## 2.2506.4-20250627
- 修复 UDM-IMS数据批量新增/批量删除命令调整
- 优化 AMF/MME对应的IMEI白名单模板去除index
## 2.2506.3-20250620
- 新增 UDM签约cnType新增可选1仅5G/2仅4G
- 新增 给AMF/MME对应的IMEI白名单添加批量导入和批量删除功能
- 新增 UDM voup/ims导入失败文件下载
## 2.2506.2-20250613
- 修复 数值控件属性maxlength拼写错误
- 修复 空格内容无法编辑,提示信息显示方向
- 优化 抓包文件目录列表显示文件大小
## 2.2506.1-20250607
- 优化 请求响应码常量,身份信息更换逻辑优化
- 修复 更新tcpdump路径从/tmp到/usr/local以符合新目录结构
## 2.2505.4-20250530
- 优化 网元参数配置修改tooltip位置为bottomLeft防止遮挡
- 修复 服务器时间格式化指定时区
- 优化 网元授权状态更新容量和更新时间局部变化
- 修复 网元信令跟踪支持选择轻量版UPF
- 修复 看板数据加载间隔从5秒调整为10秒
- 修复 看板UDM选择添加下拉菜单的弹出容器属性
## 2.2505.3-20250523
- 修复 修复同步操作加载提示未隐藏的问题
- 修复 告警确认相关提示Tip多语言显示
- 修复 网元授权许可证状态统一刷新功能
- 修复 活动告警启用历史同步按钮
- 修复 更新批量刷新信息容量仅UDM/AMF/MME
- 修复 仪表盘数据会话属累加
- 修复 网元主机信息编辑表单不验证免密类型, 主机列表的查询类型参数
## 2.2505.2-20250516
- 修复 编译类型检查错误
- 新增 网元授权上传变更确认延迟2s刷新授权状态
- 修复 英文修改基站名称为 RAN Node Name
- 新增 根据网元类型过滤导出备份数据可选的数据来源
- 新增 跟踪任务添加标题,编号后面加任务详情
## 2.2505.1-20250509
- 新增 更新日志文件导出列表,添加系统登录和操作日志
- 新增 网元授权添加用户容量列
- 修复 OAM读取结构错误
- 修复 仪表盘用户数据刷新时显示0闪烁
- 新增 看板含有网元显示区域
- 新增 根据网元显示特有菜单
- 修复 移除网络错误时的退出登录状态处理逻辑
## 2.2504.4-20250430
- 新增 PCF用户策略页面多语言翻译
- 新增 在上传切片文件到网元端时添加删除临时文件选项
- 修复 优化PCF用户策略页面及接口调整
- 修复 时间控件格式移除插件属性value-format="x"
- 新增 服务器时区UTC格式转换
- 新增 添加网络错误处理,退出登录状态
- 修复 替换获取服务器时间接口
- 移除 移除部分旧/apt/rest/直连接口
- 新增 网元服务操作局部状态刷新,补充提示信息
- 修复 时间格式常量定义及默认格式
- 新增 添加IMS SIP响应码原因显示
- 修复 看板流量图确保时间以HH:mm:ss格式显示
- 修复 看板用户活动乱序问题
- 修复 修复引导获取token存储问题
## 2.2503.4-20250331
- 更新 依赖项版本
- 修复 去除请求方法PATCH
- 重构 重构令牌管理逻辑,,统一状态码识别
- 更新 编译文件引用缺失
- 新增 UDMVolte用户特殊VoIP数据
- 优化 补充多语言翻译
- 修复 调度任务参数最大输入2000字符。日志查看id错误修复
- 重构 统一ftp操作功能备份文件查看下载删除功能
- 优化 neFile目录移动到ne
- 新增 终端目录部分调整添加udm-voip/volte功能页面
- 新增 修改OAM配置中的同步功能为重启功能
- 新增 添加告警信息导出功能,并优化历史告警页面
- 修复 统一国际时区格式
- 新增 更新锁屏密码Basd64编码处理无密码直接进入
- 新增 PCAP文件解析功能页面优化
- 修复 移除信令接口查询
- 新增 支持ws工具心跳ping消息
- 重构 重构信令跟踪功能
- 重构 重构网元跟踪任务功能页面
- 修复 看板用户数初始neId传入失败禁止选择当前项
- 新增 添加网元配置文件备份的FTP配置管理功能
- 修复 mml补充UDM特殊命令处理
- 新增 UE添加导入模板下载功能
- 修复 网元授权上传后点击ok检查状态
- 新增 添加密码过期功能支持,密码有效期时间天
## 2.2404.1-20240402
- 新增 网元安装流程相关页面与操作相关接口联调

Binary file not shown.

View File

@@ -71,13 +71,16 @@ export function batchAddUDMVolteIMS(data: Record<string, any>, 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,
});
}

View File

@@ -19,6 +19,18 @@ export const RESULT_MSG_SUCCESS: Record<string, string> = {
/**响应-code错误失败 */
export const RESULT_CODE_ERROR = 400001;
/**响应-code身份认证失败或者过期 */
export const RESULT_CODE_AUTH = 401001;
/**响应-code无效身份信息 */
export const RESULT_CODE_AUTH_INVALID = 401002;
/**响应-code令牌字符为空 */
export const RESULT_CODE_AUTH_NOTOKEN = 401003;
/**响应-code设备指纹信息不匹配 */
export const RESULT_CODE_AUTH_DEVICE = 401004;
/**响应-code错误异常 */
export const RESULT_CODE_EXCEPTION = 500001;

View File

@@ -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.',

View File

@@ -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中定义。',

View File

@@ -22,6 +22,10 @@ import {
APP_DATA_API_KEY,
} from '@/constants/app-constants';
import {
RESULT_CODE_AUTH,
RESULT_CODE_AUTH_DEVICE,
RESULT_CODE_AUTH_INVALID,
RESULT_CODE_AUTH_NOTOKEN,
RESULT_CODE_ENCRYPT,
RESULT_CODE_ERROR,
RESULT_CODE_EXCEPTION,
@@ -34,7 +38,7 @@ import {
RESULT_MSG_TIMEOUT,
RESULT_MSG_URL_RESUBMIT,
} from '@/constants/result-constants';
import { decryptAES, encryptAES } from '@/utils/encrypt-utils';
import { decryptAES, encryptAES, hexMD5 } from '@/utils/encrypt-utils';
import { localGet } from '@/utils/cache-local-utils';
import { refreshToken } from '@/api/auth';
@@ -52,9 +56,7 @@ export type ResultType = {
/**防止重复提交类型 */
type RepeatSubmitType = {
/**请求地址 */
url: string;
/**请求数据 */
/**请求数据MD5 */
data: string;
/**请求时间 */
time: number;
@@ -158,19 +160,19 @@ function beforeRequest(options: OptionsType): OptionsType | Promise<any> {
['POST', 'PUT'].includes(options.method)
) {
const requestObj: RepeatSubmitType = {
url: options.url,
data: JSON.stringify(options.data) || '',
data: hexMD5(
JSON.stringify({
url: options.url,
data: JSON.stringify(options.data) || '',
})
),
time: Date.now(),
};
const sessionObj: RepeatSubmitType = sessionGetJSON(CACHE_SESSION_FATCH);
if (sessionObj) {
const { url, data, time } = sessionObj;
const { data, time } = sessionObj;
const interval = 3000; // 间隔时间(ms),小于此时间视为重复提交
if (
requestObj.url === url &&
requestObj.data === data &&
requestObj.time - time < interval
) {
if (requestObj.data === data && requestObj.time - time < interval) {
const message = RESULT_MSG_URL_RESUBMIT[language];
return Promise.resolve({
code: RESULT_CODE_ERROR,
@@ -232,27 +234,33 @@ async function beforeResponse(
): Promise<any> {
// console.log('请求后的拦截', res);
// 登录失效时,移除授权令牌并重新刷新页面
// 登录失效时,移除访问令牌并重新请求
if (res.code === 401001) {
const result = await refreshToken(getRefreshToken());
// 移除授权令牌并重新刷新页面
function clearToken() {
delAccessToken();
delRefreshToken();
window.location.reload();
}
// 令牌失效时
if (res.code === RESULT_CODE_AUTH) {
const refreshTokenStr = getRefreshToken();
if (!refreshTokenStr) {
clearToken();
}
const result = await refreshToken(refreshTokenStr);
// 更新访问令牌和刷新令牌
if (result.code === RESULT_CODE_SUCCESS) {
setAccessToken(result.data.accessToken, result.data.refreshExpiresIn);
setRefreshToken(result.data.refreshToken, result.data.refreshExpiresIn);
return await request(options);
} else if (result.code === RESULT_CODE_AUTH_DEVICE) {
clearToken();
} else {
debugger
console.warn(result)
// delAccessToken();
// delRefreshToken();
window.location.reload();
// clearToken();
}
}
if ([401002, 401003].includes(res.code)) {
delAccessToken();
delRefreshToken();
window.location.reload();
// 令牌解析错误
if ([RESULT_CODE_AUTH_INVALID, RESULT_CODE_AUTH_NOTOKEN].includes(res.code)) {
clearToken();
}
// 响应数据解密

View File

@@ -48,3 +48,12 @@ export function decryptAES(ciphertext: string, aeskey: string): string {
}
return '';
}
/**
* MD5 编码
* @param message 字符串信息
* @returns hax码
*/
export function hexMD5(message: string): string {
return CryptoJS.MD5(message).toString(CryptoJS.enc.Hex);
}

View File

@@ -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,
};
}

View File

@@ -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<string, any> = {
AMF: {
imeiWhitelist: {
filename: 'import_amf_imeiWhitelist_template',
fileetx: '.xlsx',
itemKey: 'imeiPrefixValue',
item: (row: Record<string, any>) => {
return {
imeiPrefixValue: row['IMEI Prefix'],
index: 0,
};
},
},
whitelist: {
filename: 'import_amf_whitelist_template',
fileetx: '.xlsx',
itemKey: 'imsiValue',
item: (row: Record<string, any>) => {
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<string, any>) => {
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,
};
}

View File

@@ -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 = useNeListStore();
const { t } = useI18n();
@@ -219,16 +221,26 @@ function fnGetNeConfig() {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
const arr = [];
for (const item of res.data) {
// 是否可见
if (!item['visible']) {
item['visible'] = 'public';
} else if (item['visible'] == 'hide') {
continue;
}
// 权限控制
let paramPerms: string[] = [];
if (item.paramPerms) {
paramPerms = item.paramPerms.split(',');
} 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 +374,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 +455,7 @@ onMounted(() => {
<a-card
size="small"
:bordered="false"
:body-style="{ maxHeight: '600px', 'overflow-y': 'auto' }"
:body-style="{ maxHeight: '680px', 'overflow-y': 'auto' }"
:loading="treeState.selectLoading"
>
<template #title>
@@ -481,7 +508,7 @@ onMounted(() => {
>
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'value'">
<a-tooltip placement="topLeft">
<a-tooltip placement="bottomLeft">
<template #title v-if="record.comment">
{{ record.comment }}
</template>
@@ -575,9 +602,11 @@ onMounted(() => {
@dblclick="listEdit(record)"
>
<template v-if="record['type'] === 'enum'">
{{ JSON.parse(record['filter'])[text] || '&nbsp;' }}
{{ JSON.parse(record['filter'])[text] }}
</template>
<template v-else>{{ `${text}` || '&nbsp;' }}</template>
<template v-else>{{ `${text}` }}</template>
&nbsp;
<!-- 空格占位 -->
<EditOutlined
class="editable-cell__icon"
@click="listEdit(record)"
@@ -604,7 +633,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"
@@ -627,6 +656,38 @@ onMounted(() => {
:columns="treeState.selectNode.paramPerms.includes('get') ? [...arrayState.columns.filter((s:any)=>s.key !== 'index')] : arrayState.columns"
v-model:columns-dnd="arrayState.columnsDnd"
></TableColumnsDnd>
<!-- 特殊导入删除-->
<template
v-if="
['AMF', 'MME'].includes(neTypeSelect[0]) &&
['white_list', 'imeiWhitelist', 'whitelist'].includes(
treeState.selectNode.paramName
)
"
>
<a-button
@click.prevent="
modalImportOpen(
neTypeSelect[0],
treeState.selectNode.paramName
)
"
size="small"
>
<template #icon><ImportOutlined /></template>
{{ t('common.import') }}
</a-button>
<a-button
danger
@click.prevent="
modalBatchOpen(treeState.selectNode.paramName)
"
size="small"
>
<template #icon><DeleteOutlined /></template>
{{ t('views.neData.common.batchDelText') }}
</a-button>
</template>
</a-space>
</template>
@@ -660,7 +721,7 @@ onMounted(() => {
</a-space>
</template>
<template v-else-if="text">
<a-tooltip placement="topLeft">
<a-tooltip placement="bottomLeft">
<template #title v-if="text.comment">
{{ text.comment }}
</template>
@@ -698,7 +759,7 @@ onMounted(() => {
{{ JSON.parse(text['filter'])[text.value] }}
</template>
<template v-else>
{{ `${text.value}` || '&nbsp;' }}
{{ `${text.value}` }}
</template>
</div>
</div>
@@ -767,7 +828,7 @@ onMounted(() => {
</a-space>
</template>
<template v-else-if="text">
<a-tooltip placement="topLeft">
<a-tooltip placement="bottomLeft">
<template #title v-if="text.comment">
{{ text.comment }}
</template>
@@ -784,7 +845,7 @@ onMounted(() => {
{{ JSON.parse(text['filter'])[text.value] }}
</template>
<template v-else>
{{ `${text.value}` || '&nbsp;' }}
{{ `${text.value}` }}
</template>
</div>
</div>
@@ -904,6 +965,84 @@ onMounted(() => {
</a-form-item>
</a-form>
</ProModal>
<!-- 上传导入表格数据文件框 -->
<UploadModal
:title="t('common.import')"
@upload="modalImportUpload"
@close="modalImportClose"
v-model:open="importState.open"
:ext="['.xls', '.xlsx']"
:size="10"
>
<template #default>
<a-row justify="space-between" align="middle">
<a-col :span="12"> </a-col>
<a-col :span="6">
<a-button
type="link"
:title="t('views.system.user.downloadObj')"
@click.prevent="modalImportTemplate"
>
{{ t('views.system.user.downloadObj') }}
</a-button>
</a-col>
</a-row>
<a-textarea
:disabled="true"
:hidden="importState.msgArr.length <= 0"
:value="importState.msgArr.join('\r\n')"
:auto-size="{ minRows: 2, maxRows: 8 }"
style="background-color: transparent; color: rgba(0, 0, 0, 0.85)"
/>
</template>
</UploadModal>
<!-- 批量删除框 -->
<ProModal
:drag="true"
:destroyOnClose="true"
:keyboard="false"
:mask-closable="false"
:open="batchState.open"
:title="t('views.neData.common.batchDelText')"
:confirm-loading="batchState.loading"
@ok="modalBatchOk"
@cancel="modalBatchClose"
>
<a-form
name="batchStateForm"
:model="batchState"
layout="horizontal"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-row>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="Start Index" name="startIndex" required>
<a-input-number
v-model:value="batchState.startIndex"
style="width: 100%"
allow-clear
:min="0"
:maxlength="5"
></a-input-number>
</a-form-item>
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="Num" name="num" required>
<a-input-number
v-model:value="batchState.num"
style="width: 100%"
:min="1"
:maxlength="5"
placeholder="<=500"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
</a-form>
</ProModal>
</PageContainer>
</template>

View File

@@ -1779,10 +1779,16 @@ onMounted(() => {
>
<a-select v-model:value="modalState.from.cnType">
<a-select-option value="3">
{{ t('views.neUser.sub.enable') }}
{{ t('views.neUser.sub.cnFlag3') }}
</a-select-option>
<a-select-option value="2">
{{ t('views.neUser.sub.cnFlag2') }}
</a-select-option>
<a-select-option value="1">
{{ t('views.neUser.sub.cnFlag1') }}
</a-select-option>
<a-select-option value="0">
{{ t('views.neUser.sub.disable') }}
{{ t('views.neUser.sub.cnFlag0') }}
</a-select-option>
</a-select>
</a-form-item>

View File

@@ -26,7 +26,8 @@ import useNeListStore from '@/store/modules/ne_list';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { saveAs } from 'file-saver';
import { uploadFileToNE } from '@/api/tool/file';
import { uploadFile } from '@/api/tool/file';
import { getNeViewFile } from '@/api/tool/neFile';
const { t } = useI18n();
const neListStore = useNeListStore();
/**网元参数 */
@@ -489,15 +490,27 @@ function fnModalUploadImportUpload(file: File) {
}
const hide = message.loading(t('common.loading'), 0);
uploadImportState.loading = true;
uploadFileToNE('UDM', neID, file, 5)
// 上传文件
let formData = new FormData();
formData.append('file', file);
formData.append('subPath', 'import');
uploadFile(formData)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
return importUDMVOIP({
neId: neID,
uploadPath: res.data,
});
return res.data.filePath;
} else {
uploadImportState.msg = res.msg;
uploadImportState.loading = false;
return '';
}
return res;
})
.then((filePath: string) => {
if (!filePath) return;
// 文件导入
return importUDMVOIP({
neId: neID,
uploadPath: filePath,
});
})
.then(res => {
if (!res) return;
@@ -517,6 +530,33 @@ function fnModalUploadImportUpload(file: File) {
});
}
/**对话框表格信息导入失败原因 */
function fnModalUploadImportFailReason() {
const neId = queryParams.neId;
if (!neId) return;
const hide = message.loading(t('common.loading'), 0);
getNeViewFile({
neType: 'UDM',
neId: neId,
path: '/tmp',
fileName: 'import_imsuser_err_records.txt',
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
const blob = new Blob([res.data], {
type: 'text/plain',
});
saveAs(blob, `import_udmvoip_err_records_${Date.now()}.txt`);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
}
/**对话框表格信息导入模板 */
function fnModalDownloadImportTemplate() {
const hide = message.loading(t('common.loading'), 0);
@@ -554,7 +594,7 @@ onMounted(() => {
if (neOtions.value.length > 0) {
queryParams.neId = neOtions.value[0].value;
}
// 获取列表数据
fnGetList();
});
@@ -835,7 +875,7 @@ onMounted(() => {
v-model:value="modalState.from.username"
style="width: 100%"
:min="4"
:maxlangth="16"
:maxlength="16"
:placeholder="t('views.neData.udmVOIP.username')"
>
</a-input-number>
@@ -868,14 +908,13 @@ onMounted(() => {
name="username"
v-bind="modalStateFrom.validateInfos.username"
>
<a-input-number
<a-input
v-model:value="modalState.from.username"
style="width: 100%"
:min="4"
:maxlength="16"
:placeholder="t('views.neData.udmVOIP.username')"
>
</a-input-number>
</a-input>
</a-form-item>
<a-form-item
:label="t('views.neData.udmVOIP.password')"
@@ -917,13 +956,23 @@ onMounted(() => {
</a-button>
</a-col>
</a-row>
<a-textarea
:disabled="true"
:hidden="!uploadImportState.msg"
:value="uploadImportState.msg"
:auto-size="{ minRows: 2, maxRows: 8 }"
style="background-color: transparent; color: rgba(0, 0, 0, 0.85)"
/>
<a-alert
:message="uploadImportState.msg"
:type="uploadImportState.hasFail ? 'warning' : 'info'"
v-show="uploadImportState.msg.length > 0"
>
<template #action>
<a-button
size="small"
type="link"
danger
@click="fnModalUploadImportFailReason"
v-if="uploadImportState.hasFail"
>
{{ t('views.neUser.auth.importFail') }}
</a-button>
</template>
</a-alert>
</template>
</UploadModal>
</PageContainer>

View File

@@ -16,7 +16,8 @@ import useNeListStore from '@/store/modules/ne_list';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { saveAs } from 'file-saver';
import { uploadFileToNE } from '@/api/tool/file';
import { uploadFile } from '@/api/tool/file';
import { getNeViewFile } from '@/api/tool/neFile';
import {
addUDMVolteIMS,
batchAddUDMVolteIMS,
@@ -93,7 +94,7 @@ type TabeStateType = {
data: object[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
selectedRowIMSIs: (string | number)[];
selectedRowIMSIs: Record<string, any>[];
};
/**表格状态 */
@@ -202,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,
};
});
}
/**对话框对象信息状态类型 */
@@ -308,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') {
@@ -374,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({
@@ -390,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();
@@ -541,15 +569,27 @@ function fnModalUploadImportUpload(file: File) {
}
const hide = message.loading(t('common.loading'), 0);
uploadImportState.loading = true;
uploadFileToNE('UDM', neID, file, 5)
// 上传文件
let formData = new FormData();
formData.append('file', file);
formData.append('subPath', 'import');
uploadFile(formData)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
return importUDMVolteIMS({
neId: neID,
uploadPath: res.data,
});
return res.data.filePath;
} else {
uploadImportState.msg = res.msg;
uploadImportState.loading = false;
return '';
}
return res;
})
.then((filePath: string) => {
if (!filePath) return;
// 文件导入
return importUDMVolteIMS({
neId: neID,
uploadPath: filePath,
});
})
.then(res => {
if (!res) return;
@@ -569,6 +609,33 @@ function fnModalUploadImportUpload(file: File) {
});
}
/**对话框表格信息导入失败原因 */
function fnModalUploadImportFailReason() {
const neId = queryParams.neId;
if (!neId) return;
const hide = message.loading(t('common.loading'), 0);
getNeViewFile({
neType: 'UDM',
neId: neId,
path: '/tmp',
fileName: 'import_imsuser_err_records.txt',
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(t('common.operateOk'), 3);
const blob = new Blob([res.data], {
type: 'text/plain',
});
saveAs(blob, `import_udmvolte_err_records_${Date.now()}.txt`);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
}
/**对话框表格信息导入模板 */
function fnModalDownloadImportTemplate() {
const hide = message.loading(t('common.loading'), 0);
@@ -847,7 +914,7 @@ onMounted(() => {
<template #title>{{ t('common.deleteText') }}</template>
<a-button
type="link"
@click.prevent="fnRecordDelete(record.imsi)"
@click.prevent="fnRecordDelete(record.id)"
>
<template #icon>
<DeleteOutlined />
@@ -906,36 +973,73 @@ onMounted(() => {
>
</a-select>
</a-form-item>
<a-form-item
v-if="modalState.from.tag === '1'"
:label="
modalState.isBatch ? t('views.neData.common.startIMSI') : 'IMSI'
"
name="imsi"
v-bind="modalStateFrom.validateInfos.imsi"
>
<a-input
v-model:value="modalState.from.imsi"
allow-clear
:maxlength="15"
<template v-if="modalState.from.tag === '1'">
<a-form-item
:label="
modalState.isBatch ? t('views.neData.common.startIMSI') : 'IMSI'
"
name="imsi"
v-bind="modalStateFrom.validateInfos.imsi"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neData.common.imsiTip') }}<br />
{{ t('views.neData.common.imsiTip1') }}<br />
{{ t('views.neData.common.imsiTip2') }}<br />
{{ t('views.neData.common.imsiTip3') }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-input
v-model:value="modalState.from.imsi"
allow-clear
:maxlength="15"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neData.common.imsiTip') }}<br />
{{ t('views.neData.common.imsiTip1') }}<br />
{{ t('views.neData.common.imsiTip2') }}<br />
{{ t('views.neData.common.imsiTip3') }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-form-item
:extra="
modalState.from.tag == '0'
? t('views.neData.udmVolteIMS.voipTip')
: ''
"
:label="
modalState.isBatch
? t('views.neData.udmVolteIMS.startMSISDN')
: 'MSISDN'
"
name="msisdn"
v-bind="modalStateFrom.validateInfos.msisdn"
>
<a-input
v-model:value="modalState.from.msisdn"
allow-clear
:maxlength="32"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neData.common.msisdn') }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
</template>
<a-form-item
v-else
v-if="modalState.from.tag === '0'"
:extra="
modalState.from.tag == '0'
? t('views.neData.udmVolteIMS.voipTip')
: ''
"
:label="
modalState.isBatch && modalState.from.tag === '0'
modalState.isBatch
? t('views.neData.udmVolteIMS.startMSISDN')
: 'MSISDN'
"
@@ -1021,7 +1125,7 @@ onMounted(() => {
: ''
"
:label="
modalState.isBatch && modalState.from.tag === '0'
modalState.isBatch
? t('views.neData.udmVolteIMS.startMSISDN')
: 'MSISDN'
"
@@ -1089,13 +1193,23 @@ onMounted(() => {
</a-button>
</a-col>
</a-row>
<a-textarea
:disabled="true"
:hidden="!uploadImportState.msg"
:value="uploadImportState.msg"
:auto-size="{ minRows: 2, maxRows: 8 }"
style="background-color: transparent; color: rgba(0, 0, 0, 0.85)"
/>
<a-alert
:message="uploadImportState.msg"
:type="uploadImportState.hasFail ? 'warning' : 'info'"
v-show="uploadImportState.msg.length > 0"
>
<template #action>
<a-button
size="small"
type="link"
danger
@click="fnModalUploadImportFailReason"
v-if="uploadImportState.hasFail"
>
{{ t('views.neUser.auth.importFail') }}
</a-button>
</template>
</a-alert>
</template>
</UploadModal>
</PageContainer>

View File

@@ -5,6 +5,7 @@ import { SizeType } from 'ant-design-vue/es/config-provider';
import { ColumnsType } from 'ant-design-vue/es/table';
import { Modal, message } from 'ant-design-vue/es';
import { parseDateToStr } from '@/utils/date-utils';
import { parseSizeFromFile } from '@/utils/parse-utils';
import { getNeDirZip, getNeFile, listNeFiles } from '@/api/tool/neFile';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import ViewDrawer from '@/views/ne/neFile/components/ViewDrawer.vue';
@@ -69,6 +70,9 @@ let tableColumns: ColumnsType = reactive([
title: t('views.logManage.neFile.size'),
dataIndex: 'size',
align: 'left',
customRender(opt) {
return parseSizeFromFile(opt.value);
},
width: 100,
},
{
@@ -243,7 +247,7 @@ function fnDirCD(dir: string, index?: number) {
queryParams.search = `${neType}_${queryParams.neId}`;
} else {
nePathArr.value = [
`/tmp/omc/tcpdump/${neType.toLowerCase()}/${queryParams.neId}`,
`/usr/local/omc/tcpdump/${neType.toLowerCase()}/${queryParams.neId}`,
];
queryParams.search = '';
}
@@ -269,7 +273,7 @@ function fnNeChange(keys: any, _: any) {
nePathArr.value = ['/tmp'];
queryParams.search = `${neType}_${neId}`;
} else {
nePathArr.value = [`/tmp/omc/tcpdump/${neType.toLowerCase()}/${neId}`];
nePathArr.value = [`/usr/local/omc/tcpdump/${neType.toLowerCase()}/${neId}`];
queryParams.search = '';
}
fnGetList(1);

View File

@@ -438,7 +438,7 @@ function fnDownPCAP(row?: Record<string, any>) {
);
} else {
const { neType, neId } = from.data;
const path = `/tmp/omc/tcpdump/${neType.toLowerCase()}/${neId}/${taskCode}`;
const path = `/usr/local/omc/tcpdump/${neType.toLowerCase()}/${neId}/${taskCode}`;
reqArr.push(
getNeDirZip({
neType,
@@ -518,7 +518,7 @@ function fnModalVisibleByVive(id: string | number) {
const from = modalState.from[id];
if (!from) return;
const { neType, neId } = from.data;
const path = `/tmp/omc/tcpdump/${neType.toLowerCase()}/${neId}/${
const path = `/usr/local/omc/tcpdump/${neType.toLowerCase()}/${neId}/${
from.taskCode
}`;
const files = from.taskFiles.filter(f => f.endsWith('log'));