Merge branch 'main' into multi-tenant
This commit is contained in:
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
||||
VITE_APP_CODE = "OMC"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "2.240809"
|
||||
VITE_APP_VERSION = "2.240815"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
@@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC"
|
||||
VITE_APP_CODE = "OMC"
|
||||
|
||||
# 应用版本
|
||||
VITE_APP_VERSION = "2.240809"
|
||||
VITE_APP_VERSION = "2.240815"
|
||||
|
||||
# 接口基础URL地址-不带/后缀
|
||||
VITE_API_BASE_URL = "/omc-api"
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@codemirror/lang-yaml": "^6.1.1",
|
||||
"@codemirror/merge": "^6.6.3",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@tato30/vue-pdf": "~1.9.7",
|
||||
"@tato30/vue-pdf": "^1.10.0",
|
||||
"@vueuse/core": "~10.10.1",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
@@ -26,10 +26,11 @@
|
||||
"antdv-pro-layout": "~3.3.5",
|
||||
"antdv-pro-modal": "^3.1.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.11",
|
||||
"echarts": "~5.5.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"intl-tel-input": "~23.0.12",
|
||||
"intl-tel-input": "^23.8.1",
|
||||
"js-base64": "^3.7.7",
|
||||
"js-cookie": "^3.0.5",
|
||||
"localforage": "^1.10.0",
|
||||
@@ -43,6 +44,7 @@
|
||||
"xlsx": "~0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^18.0.0",
|
||||
|
||||
@@ -7,6 +7,7 @@ export function login(data: Record<string, string>) {
|
||||
method: 'post',
|
||||
data: data,
|
||||
whithToken: false,
|
||||
crypto: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,6 +22,7 @@ export function register(data: Record<string, any>) {
|
||||
method: 'post',
|
||||
data: data,
|
||||
whithToken: false,
|
||||
crypto: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,5 +6,6 @@ export function getLoad(query: Record<string, any>) {
|
||||
url: '/monitor/load',
|
||||
method: 'get',
|
||||
params: query,
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@ export function getSystemInfo() {
|
||||
return request({
|
||||
url: '/monitor/system-info',
|
||||
method: 'get',
|
||||
timeout: 60_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ export function addNeInfo(data: Record<string, any>) {
|
||||
url: `/ne/info`,
|
||||
method: 'post',
|
||||
data: data,
|
||||
crypto: true,
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,6 +51,8 @@ export function updateNeInfo(data: Record<string, any>) {
|
||||
url: `/ne/info`,
|
||||
method: 'put',
|
||||
data: data,
|
||||
crypto: true,
|
||||
timeout: 30_000,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,3 +3,6 @@ export const APP_REQUEST_HEADER_CODE = 'X-App-Code';
|
||||
|
||||
/**应用-请求头-系统版本 */
|
||||
export const APP_REQUEST_HEADER_VERSION = 'X-App-Version';
|
||||
|
||||
/**应用-请求数据-密钥 */
|
||||
export const APP_DATA_API_KEY = 'T9ox2DCzpLfJIPzkH9pKhsOTMOEMJcFv';
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/**响应-code加密数据 */
|
||||
export const RESULT_CODE_ENCRYPT = 2;
|
||||
|
||||
/**响应-msg加密数据 */
|
||||
export const RESULT_MSG_ENCRYPT: Record<string, string> = {
|
||||
zh_CN: '加密!',
|
||||
en_US: 'encrypt!',
|
||||
};
|
||||
|
||||
/**响应-code正常成功 */
|
||||
export const RESULT_CODE_SUCCESS = 1;
|
||||
|
||||
|
||||
@@ -13,10 +13,13 @@ import {
|
||||
import {
|
||||
APP_REQUEST_HEADER_CODE,
|
||||
APP_REQUEST_HEADER_VERSION,
|
||||
APP_DATA_API_KEY,
|
||||
} from '@/constants/app-constants';
|
||||
import {
|
||||
RESULT_CODE_ENCRYPT,
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
RESULT_MSG_ENCRYPT,
|
||||
RESULT_MSG_ERROR,
|
||||
RESULT_MSG_NOT_TYPE,
|
||||
RESULT_MSG_SERVER_ERROR,
|
||||
@@ -25,11 +28,12 @@ import {
|
||||
RESULT_MSG_URL_NOTFOUND,
|
||||
RESULT_MSG_URL_RESUBMIT,
|
||||
} from '@/constants/result-constants';
|
||||
import { decryptAES, encryptAES } from '@/utils/encrypt-utils';
|
||||
|
||||
/**响应结果类型 */
|
||||
export type ResultType = {
|
||||
/**响应码 */
|
||||
code: number | 1 | 0;
|
||||
code: number;
|
||||
/**信息 */
|
||||
msg: string;
|
||||
/**数据 */
|
||||
@@ -76,6 +80,8 @@ type OptionsType = {
|
||||
body?: BodyInit;
|
||||
/**防止数据重复提交 */
|
||||
repeatSubmit?: boolean;
|
||||
/**接口数据加密 */
|
||||
crypto?: boolean;
|
||||
/**携带授权Token请求头 */
|
||||
whithToken?: boolean;
|
||||
/**中断控制信号,timeout不会生效 */
|
||||
@@ -167,24 +173,40 @@ function beforeRequest(options: OptionsType): OptionsType | Promise<any> {
|
||||
|
||||
// 请求拼接地址栏参数
|
||||
if (options.params) {
|
||||
let paramStr = '';
|
||||
const params = options.params;
|
||||
const queryParams: string[] = [];
|
||||
for (const key in params) {
|
||||
const value = params[key];
|
||||
// 空字符或未定义的值不作为参数发送
|
||||
if (value === '' || value === undefined) continue;
|
||||
paramStr += `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
||||
const str = `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
||||
queryParams.push(str);
|
||||
}
|
||||
if (paramStr && paramStr.startsWith('&')) {
|
||||
options.url = `${options.url}?${paramStr.substring(1)}`;
|
||||
const paramStr = queryParams.join('&');
|
||||
if (paramStr) {
|
||||
const separator = options.url.includes('?') ? '&' : '?';
|
||||
// 请求加密
|
||||
if (options.crypto) {
|
||||
debugger;
|
||||
const data = encryptAES(JSON.stringify(paramStr), APP_DATA_API_KEY);
|
||||
options.url += `${separator}data=${encodeURIComponent(data)}`;
|
||||
} else {
|
||||
options.url += `${separator}${paramStr}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 非get参数提交
|
||||
if (options.data instanceof FormData) {
|
||||
options.body = options.data;
|
||||
} else {
|
||||
options.body = JSON.stringify(options.data);
|
||||
let body = options.data
|
||||
if (body instanceof FormData) {
|
||||
options.body = body;
|
||||
} else if (body) {
|
||||
// 请求加密
|
||||
if (options.crypto) {
|
||||
const data = encryptAES(JSON.stringify(body), APP_DATA_API_KEY);
|
||||
body = { data };
|
||||
}
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
@@ -199,6 +221,28 @@ function interceptorResponse(res: ResultType): ResultType | Promise<any> {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
// 响应数据解密
|
||||
if (res.code === RESULT_CODE_ENCRYPT) {
|
||||
const str = decryptAES(res.data, APP_DATA_API_KEY);
|
||||
let data = {};
|
||||
try {
|
||||
data = JSON.parse(str);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
if (Object.keys(data).length === 0) {
|
||||
return Promise.resolve({
|
||||
code: RESULT_CODE_ERROR,
|
||||
msg: RESULT_MSG_ENCRYPT[language],
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
code: RESULT_CODE_SUCCESS,
|
||||
msg: RESULT_MSG_SUCCESS[language],
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
// 风格处理
|
||||
if (!Reflect.has(res, 'code')) {
|
||||
return Promise.resolve({
|
||||
@@ -266,7 +310,7 @@ export async function request(options: OptionsType): Promise<ResultType> {
|
||||
case 'text': // 文本数据
|
||||
const str = await res.text();
|
||||
return {
|
||||
code: 1,
|
||||
code: RESULT_CODE_SUCCESS,
|
||||
msg: str,
|
||||
};
|
||||
case 'json': // json格式数据
|
||||
|
||||
50
src/utils/encrypt-utils.ts
Normal file
50
src/utils/encrypt-utils.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import CryptoJS from 'crypto-js';
|
||||
import { isValid, decode } from 'js-base64';
|
||||
|
||||
/**
|
||||
* AES 加密并转为 base64
|
||||
* @param plaintext 数据字符串
|
||||
* @param aeskey 密钥
|
||||
* @returns 加密字符串
|
||||
*/
|
||||
export function encryptAES(plaintext: string, aeskey: string): string {
|
||||
const nowRoaund = new Date().getTime().toString(6);
|
||||
const key = CryptoJS.enc.Utf8.parse(aeskey);
|
||||
const iv = CryptoJS.enc.Utf8.parse(nowRoaund);
|
||||
const encrypted = CryptoJS.AES.encrypt(`${nowRoaund}${plaintext}`, key, {
|
||||
iv: iv,
|
||||
blockSize: 16,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
format: CryptoJS.format.OpenSSL,
|
||||
});
|
||||
return encrypted.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* AES 解密
|
||||
* @param ciphertext 加密字符串
|
||||
* @param aeskey 密钥
|
||||
* @returns 数据字符串
|
||||
*/
|
||||
export function decryptAES(ciphertext: string, aeskey: string): string {
|
||||
const nowRoaund = new Date().getTime().toString(6);
|
||||
const key = CryptoJS.enc.Utf8.parse(aeskey);
|
||||
const iv = CryptoJS.enc.Utf8.parse(nowRoaund);
|
||||
const decrypted = CryptoJS.AES.decrypt(ciphertext, key, {
|
||||
iv: iv,
|
||||
blockSize: 16,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
format: CryptoJS.format.OpenSSL,
|
||||
});
|
||||
const base64Str = decrypted.toString(CryptoJS.enc.Base64);
|
||||
if (isValid(base64Str)) {
|
||||
const str = decode(base64Str);
|
||||
const idx = str.indexOf(':)', 10);
|
||||
if (idx > 10) {
|
||||
return str.substring(idx + 2);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -53,6 +53,13 @@ export default function useConfigList({
|
||||
|
||||
/**单列表编辑 */
|
||||
function listEdit(row: Record<string, any>) {
|
||||
if (
|
||||
listState.confirmLoading ||
|
||||
['read-only', 'read', 'ro'].includes(row.access)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
listState.editRecord = Object.assign({}, row);
|
||||
}
|
||||
|
||||
|
||||
@@ -597,9 +597,8 @@ onMounted(() => {
|
||||
@click="listEdit(record)"
|
||||
style="margin-left: 18px"
|
||||
v-if="
|
||||
!['read-only', 'read', 'ro'].includes(
|
||||
record.access
|
||||
) && !listState.confirmLoading
|
||||
!listState.confirmLoading &&
|
||||
!['read-only', 'read', 'ro'].includes(record.access)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
@@ -658,7 +657,14 @@ onMounted(() => {
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-if="treeState.selectNode.paramPerms.includes('delete')"
|
||||
v-if="
|
||||
treeState.selectNode.paramPerms.includes('delete') &&
|
||||
!(
|
||||
neTypeSelect[0] === 'IMS' &&
|
||||
treeState.selectNode.paramName === 'plmn' &&
|
||||
text['value'] === 0
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button type="link" @click.prevent="arrayDelete(text)">
|
||||
|
||||
@@ -364,8 +364,18 @@ function fnGetList(pageNum?: number) {
|
||||
tablePagination.total = res.total;
|
||||
// 遍历处理资源情况数值
|
||||
tableState.data = res.rows.map(item => {
|
||||
let resouresUsage = {
|
||||
sysDiskUsage: 0,
|
||||
sysMemUsage: 0,
|
||||
sysCpuUsage: 0,
|
||||
nfCpuUsage: 0,
|
||||
};
|
||||
const neState = item.serverState;
|
||||
const resouresUsage = parseResouresUsage(neState);
|
||||
if (neState) {
|
||||
resouresUsage = parseResouresUsage(neState);
|
||||
} else {
|
||||
item.serverState = { online: false };
|
||||
}
|
||||
Reflect.set(item, 'resoures', resouresUsage);
|
||||
return item;
|
||||
});
|
||||
|
||||
@@ -47,13 +47,6 @@ function fnNext() {
|
||||
|
||||
<div style="padding: 24px 12px 0; text-align: end">
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="state.confirmLoading"
|
||||
@click="fnNext()"
|
||||
>
|
||||
{{ t('views.ne.neQuickSetup.stepNext') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
:disabled="state.confirmLoading"
|
||||
@@ -62,6 +55,13 @@ function fnNext() {
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
{{ t('views.ne.neQuickSetup.reloadPara5G') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="state.confirmLoading"
|
||||
@click="fnNext()"
|
||||
>
|
||||
{{ t('views.ne.neQuickSetup.stepNext') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
@@ -157,6 +157,53 @@ function fnGet45GList(pageNum?: number) {
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
if (!queryParams.neType) {
|
||||
promises.value = [];
|
||||
//同时获取45G基站信息 且在每条信息中添加45G字段(原始数据没有) 已经筛选后的
|
||||
neCascaderOptions.value.map((item: any) => {
|
||||
item.children.forEach((child: any) => {
|
||||
promises.value.push(
|
||||
listBase5G({
|
||||
neId: child.neId,
|
||||
neType: child.neType,
|
||||
nbId: queryParams.id,
|
||||
pageNum: queryParams.pageNum,
|
||||
pageSize: 10000,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Promise.allSettled(promises.value).then(results => {
|
||||
results.forEach(result => {
|
||||
if (result.status === 'fulfilled') {
|
||||
const allBaseData = result.value;
|
||||
if (
|
||||
allBaseData.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(allBaseData.rows)
|
||||
) {
|
||||
// 处理成功结果
|
||||
tablePagination.total += allBaseData.total;
|
||||
tableState.data = [...tableState.data, ...allBaseData.rows];
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
} else {
|
||||
//AMF返回404是代表没找到这个数据 GNB_NOT_FOUND
|
||||
tablePagination.total = 0;
|
||||
tableState.data = [];
|
||||
}
|
||||
tableState.loading = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const typeArr: any = [];
|
||||
const typeMapping: any = {
|
||||
|
||||
@@ -20,7 +20,7 @@ export default defineConfig(({ mode }) => {
|
||||
proxy: {
|
||||
// https://cn.vitejs.dev/config/#server-proxy
|
||||
[env.VITE_API_BASE_URL]: {
|
||||
// target: 'http://192.168.2.166:3030',
|
||||
// target: 'http://192.168.2.166:33030',
|
||||
target: 'http://192.168.5.58:33040',
|
||||
changeOrigin: true,
|
||||
rewrite: p => p.replace(env.VITE_API_BASE_URL, ''),
|
||||
@@ -54,6 +54,7 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
},
|
||||
build: {
|
||||
target: 'esnext', // Use 'esnext' to support the latest features
|
||||
sourcemap: false,
|
||||
chunkSizeWarningLimit: 500, // 调整区块大小警告限制(以kB为单位)
|
||||
rollupOptions: {
|
||||
@@ -74,6 +75,11 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
supported: {
|
||||
'top-level-await': true,
|
||||
},
|
||||
},
|
||||
include: ['@ant-design/icons-vue', 'ant-design-vue'],
|
||||
},
|
||||
plugins: [
|
||||
|
||||
Reference in New Issue
Block a user