diff --git a/.env.development b/.env.development index 155b8ccf..75d40de0 100644 --- a/.env.development +++ b/.env.development @@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC" VITE_APP_CODE = "OMC" # 应用版本 -VITE_APP_VERSION = "2.241220" +VITE_APP_VERSION = "2.240103" # 接口基础URL地址-不带/后缀 VITE_API_BASE_URL = "/omc-api" diff --git a/.env.production b/.env.production index 155b8ccf..75d40de0 100644 --- a/.env.production +++ b/.env.production @@ -11,7 +11,7 @@ VITE_APP_NAME = "Core Network OMC" VITE_APP_CODE = "OMC" # 应用版本 -VITE_APP_VERSION = "2.241220" +VITE_APP_VERSION = "2.240103" # 接口基础URL地址-不带/后缀 VITE_API_BASE_URL = "/omc-api" diff --git a/package.json b/package.json index 489df9d0..3ef1814a 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,13 @@ }, "dependencies": { "@ant-design/icons-vue": "^7.0.1", - "@antv/g6": "~4.8.24", + "@antv/g6": "4.8.24", "@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-yaml": "^6.1.2", - "@codemirror/merge": "^6.7.4", + "@codemirror/merge": "^6.8.0", "@codemirror/theme-one-dark": "^6.1.2", "@tato30/vue-pdf": "^1.11.3", - "@vueuse/core": "^12.0.0", + "@vueuse/core": "^12.3.0", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", "ant-design-vue": "^4.2.6", @@ -28,10 +28,10 @@ "codemirror": "^6.0.1", "crypto-js": "^4.2.0", "dayjs": "^1.11.11", - "echarts": "~5.5.0", + "echarts": "~5.6.0", "file-saver": "^2.0.5", "grid-layout-plus": "^1.0.6", - "intl-tel-input": "^25.2.0", + "intl-tel-input": "~25.2.0", "js-base64": "^3.7.7", "js-cookie": "^3.0.5", "localforage": "^1.10.0", @@ -39,7 +39,7 @@ "p-queue": "~8.0.1", "pinia": "^2.3.0", "vue": "^3.5.13", - "vue-i18n": "^10.0.5", + "vue-i18n": "^11.0.1", "vue-router": "^4.5.0", "vue3-smooth-dnd": "^0.0.6", "xlsx": "~0.18.5" @@ -53,8 +53,8 @@ "@vitejs/plugin-vue": "^5.2.1", "less": "^4.2.1", "typescript": "~5.6.3", - "unplugin-vue-components": "^0.27.5", - "vite": "^6.0.3", + "unplugin-vue-components": "^0.28.0", + "vite": "^6.0.6", "vite-plugin-compression": "~0.5.1", "vue-tsc": "~2.1.10" } diff --git a/public/svg/base4G.svg b/public/svg/base4G.svg new file mode 100644 index 00000000..b8f0d2f3 --- /dev/null +++ b/public/svg/base4G.svg @@ -0,0 +1,28 @@ + + + + + background + + + + + + + Layer 1 + + + + + + + + + + + + + + 4G + + \ No newline at end of file diff --git a/public/svg/base5G.svg b/public/svg/base5G.svg new file mode 100644 index 00000000..9c37b576 --- /dev/null +++ b/public/svg/base5G.svg @@ -0,0 +1,28 @@ + + + + + background + + + + + + + Layer 1 + + + + + + + + + + + + + + 5G + + \ No newline at end of file diff --git a/src/api/neData/amf.ts b/src/api/neData/amf.ts index 2059e260..fd5d57f4 100644 --- a/src/api/neData/amf.ts +++ b/src/api/neData/amf.ts @@ -10,6 +10,7 @@ export function listAMFDataUE(query: Record) { url: '/neData/amf/ue/list', method: 'get', params: query, + timeout: 60_000, }); } @@ -40,3 +41,90 @@ export function exportAMFDataUE(data: Record) { timeout: 60_000, }); } + +/** + * AMF-接入基站信息列表 + * @param query 查询参数 neId=001&id=1 + * @returns object + */ +export function listAMFNblist(query: Record) { + return request({ + url: '/neData/amf/nb/list', + method: 'get', + params: query, + timeout: 60_000, + }); +} + +/** + * AMF-接入基站状态信息列表 + * @param query 查询参数 neId=001&state=1 + * @returns object + */ +export function listAMFNbStatelist(query: Record) { + return request({ + url: '/neData/amf/nb/list-cfg', + method: 'get', + params: query, + timeout: 60_000, + }); +} + +/** + * AMF-接入基站状态信息新增 + * @param neId 网元ID + * @param data 数据 { "index": 1, "name": "Gnb", "address": "192.168.8.1", "position": "Area-B" } + * @returns object + */ +export function addAMFNbState(neId: string, data: Record) { + return request({ + url: `/ne/config/data`, + method: 'post', + data: { + neType: 'AMF', + neId: neId, + paramName: 'gnbList', + paramData: data, + loc: `${data.index}`, + }, + }); +} + +/** + * AMF-接入基站状态信息修改 + * @param neId 网元ID + * @param data 数据 { "index": 1, "name": "Gnb", "address": "192.168.8.1", "position": "Area-B" } + * @returns object + */ +export function editAMFNbState(neId: string, data: Record) { + return request({ + url: `/ne/config/data`, + method: 'put', + data: { + neType: 'AMF', + neId: neId, + paramName: 'gnbList', + paramData: data, + loc: `${data.index}`, + }, + }); +} + +/** + * AMF-接入基站状态信息删除 + * @param neId 网元ID + * @param index 数据index + * @returns object + */ +export function delAMFNbState(neId: string, index: string | number) { + return request({ + url: `/ne/config/data`, + method: 'delete', + params: { + neType: 'AMF', + neId: neId, + paramName: 'gnbList', + loc: `${index}`, + }, + }); +} diff --git a/src/api/neData/ims.ts b/src/api/neData/ims.ts index 1f1d67bc..a0e53cb0 100644 --- a/src/api/neData/ims.ts +++ b/src/api/neData/ims.ts @@ -10,6 +10,7 @@ export function listIMSDataCDR(query: Record) { url: '/neData/ims/cdr/list', method: 'get', params: query, + timeout: 60_000, }); } diff --git a/src/api/neData/mme.ts b/src/api/neData/mme.ts index 766431ca..cc875983 100644 --- a/src/api/neData/mme.ts +++ b/src/api/neData/mme.ts @@ -10,6 +10,7 @@ export function listMMEDataUE(query: Record) { url: '/neData/mme/ue/list', method: 'get', params: query, + timeout: 60_000, }); } diff --git a/src/api/neData/sgwc.ts b/src/api/neData/sgwc.ts index 8b383256..b7a0a498 100644 --- a/src/api/neData/sgwc.ts +++ b/src/api/neData/sgwc.ts @@ -10,6 +10,7 @@ export function listSGWCDataCDR(query: Record) { url: '/neData/sgwc/cdr/list', method: 'get', params: query, + timeout: 60_000, }); } diff --git a/src/api/neData/smf.ts b/src/api/neData/smf.ts index a7d8c3c0..05050abc 100644 --- a/src/api/neData/smf.ts +++ b/src/api/neData/smf.ts @@ -10,6 +10,7 @@ export function listSMFDataCDR(query: Record) { url: '/neData/smf/cdr/list', method: 'get', params: query, + timeout: 60_000, }); } diff --git a/src/api/neData/smsc.ts b/src/api/neData/smsc.ts index a05c233e..501c7446 100644 --- a/src/api/neData/smsc.ts +++ b/src/api/neData/smsc.ts @@ -10,6 +10,7 @@ export function listSMSCDataCDR(query: Record) { url: '/neData/smsc/cdr/list', method: 'get', params: query, + timeout: 60_000, }); } diff --git a/src/api/perfManage/customData.ts b/src/api/perfManage/customData.ts index 237b0a65..802b32bc 100644 --- a/src/api/perfManage/customData.ts +++ b/src/api/perfManage/customData.ts @@ -1,7 +1,4 @@ -import { RESULT_CODE_SUCCESS } from '@/constants/result-constants'; import { request } from '@/plugins/http-fetch'; -import { parseObjLineToHump } from '@/utils/parse-utils'; -import { parseDateToStr } from '@/utils/date-utils'; /** * 新 查询自定义指标数据 @@ -14,6 +11,7 @@ export async function listCustomData(query: Record) { url: `/pm/kpiC/report`, method: 'get', params: query, + timeout: 60_000, }); return result; } diff --git a/src/utils/parse-utils.ts b/src/utils/parse-utils.ts index e091aa8e..49a89ddd 100644 --- a/src/utils/parse-utils.ts +++ b/src/utils/parse-utils.ts @@ -171,8 +171,8 @@ export function parseSizeFromKbs(sizeByte: number, timeInterval: number): any { } /** - * 字节数转换单位 - * @param bits 字节Bit大小 64009540 = 512.08 MB + * 位数据转换单位 + * @param bits 位Bit大小 64009540 = 512.08 MB * @returns xx B / KB / MB / GB / TB / PB / EB / ZB / YB */ export function parseSizeFromBits(bits: number | string): string { @@ -181,7 +181,28 @@ export function parseSizeFromBits(bits: number | string): string { bits = bits * 8; const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const unitIndex = Math.floor(Math.log2(bits) / 10); - const value = (bits / Math.pow(1000, unitIndex)).toFixed(2); + const value = bits / Math.pow(1000, unitIndex); const unti = units[unitIndex]; + if (unitIndex > 0) { + return `${value.toFixed(2)} ${unti}`; + } + return `${value} ${unti}`; +} + +/** + * 字节数转换单位 + * @param byte 字节Byte大小 64009540 = 512.08 MB + * @returns xx B / KB / MB / GB / TB / PB / EB / ZB / YB + */ +export function parseSizeFromByte(byte: number | string): string { + byte = Number(byte) || 0; + if (byte <= 0) return '0 B'; + const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const unitIndex = Math.floor(Math.log2(byte) / 10); + const unti = units[unitIndex]; + const value = byte / Math.pow(1000, unitIndex); + if (unitIndex > 0) { + return `${value.toFixed(2)} ${unti}`; + } return `${value} ${unti}`; } diff --git a/src/views/dashboard/amfUE/index.vue b/src/views/dashboard/amfUE/index.vue index 267259a9..bdd9f270 100644 --- a/src/views/dashboard/amfUE/index.vue +++ b/src/views/dashboard/amfUE/index.vue @@ -12,17 +12,24 @@ import { RESULT_CODE_SUCCESS, } from '@/constants/result-constants'; import useDictStore from '@/store/modules/dict'; +import useNeInfoStore from '@/store/modules/neinfo'; import { listAMFDataUE, delAMFDataUE, exportAMFDataUE } from '@/api/neData/amf'; +import { parseDateToStr } from '@/utils/date-utils'; import { OptionsType, WS } from '@/plugins/ws-websocket'; +import dayjs, { Dayjs } from 'dayjs'; import saveAs from 'file-saver'; import PQueue from 'p-queue'; import useUserStore from '@/store/modules/user'; import { TENANTADMIN_ROLE_KEY } from '@/constants/admin-constants'; import { listTenant } from '@/api/system/tenant'; +import { useClipboard } from '@vueuse/core'; +const { copy } = useClipboard({ legacy: true }); const { t } = useI18n(); const { getDict } = useDictStore(); const ws = new WS(); const queue = new PQueue({ concurrency: 1, autoStart: true }); +/**网元可选 */ +let neOtions = ref[]>([]); /**字典数据 */ let dict: { @@ -39,7 +46,10 @@ let dict: { }); /**开始结束时间 */ -let queryRangePicker = ref<[string, string]>(['', '']); +let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>([ + dayjs().startOf('hour'), + dayjs().endOf('hour'), +]); /**查询参数 */ let queryParams = reactive({ @@ -53,9 +63,9 @@ let queryParams = reactive({ sortField: 'timestamp', sortOrder: 'desc', /**开始时间 */ - startTime: '', + startTime: undefined as undefined | number, /**结束时间 */ - endTime: '', + endTime: undefined as undefined | number, /**当前页数 */ pageNum: 1, /**每页条数 */ @@ -74,7 +84,7 @@ function fnQueryReset() { pageNum: 1, pageSize: 20, }); - queryRangePicker.value = ['', '']; + queryRangePicker.value = [dayjs().startOf('hour'), dayjs().endOf('hour')]; tablePagination.current = 1; tablePagination.pageSize = 20; fnGetList(); @@ -151,9 +161,15 @@ let tableColumns: ColumnsType = [ { title: t('views.dashboard.ue.time'), dataIndex: 'eventJSON', - key: 'time', align: 'left', width: 150, + customRender(opt) { + const record = opt.value; + if (record?.time) { + return record.time; + } + return parseDateToStr(+record.timestamp * 1000); + }, }, { title: t('views.dashboard.cdr.tenantName'), @@ -269,11 +285,19 @@ function fnGetList(pageNum?: number) { if (pageNum) { queryParams.pageNum = pageNum; } - if (!queryRangePicker.value) { - queryRangePicker.value = ['', '']; + + // 时间范围 + if ( + Array.isArray(queryRangePicker.value) && + queryRangePicker.value.length > 0 + ) { + queryParams.startTime = queryRangePicker.value[0].valueOf(); + queryParams.endTime = queryRangePicker.value[1].valueOf(); + } else { + queryParams.startTime = undefined; + queryParams.endTime = undefined; } - queryParams.startTime = queryRangePicker.value[0]; - queryParams.endTime = queryRangePicker.value[1]; + listAMFDataUE(toRaw(queryParams)).then(res => { if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) { // 取消勾选 @@ -341,6 +365,18 @@ function fnExportList() { }); } +/** + * 复制CDR + * @param jsonStr JSON字符串 + */ +function fnRecordCopy(jsonStr: string) { + if (!jsonStr) return; + const text = JSON.stringify(jsonStr, null, 2); + copy(text).then(() => { + message.success(t('common.copyOk'), 3); + }); +} + /**实时数据开关 */ const realTimeData = ref(false); @@ -350,31 +386,30 @@ const realTimeData = ref(false); function fnRealTime() { realTimeData.value = !realTimeData.value; if (realTimeData.value) { + tableState.seached = false; // 建立链接 const options: OptionsType = { url: '/ws', params: { /**订阅通道组 * - * AMF_UE会话事件(GroupID:1010) + * AMF_UE会话事件(GroupID:1010_neId) */ - subGroupID: '1010', + subGroupID: `1010_${queryParams.neId}`, }, onmessage: wsMessage, - onerror: wsError, + onerror: (ev: any) => { + console.error(ev); + }, }; ws.connect(options); } else { ws.close(); + tableState.seached = true; + fnGetList(1); } } -/**接收数据后回调 */ -function wsError(ev: any) { - // 接收数据后回调 - console.error(ev); -} - /**接收数据后回调 */ function wsMessage(res: Record) { const { code, requestId, data } = res; @@ -388,7 +423,7 @@ function wsMessage(res: Record) { return; } // ueEvent AMF_UE会话事件 - if (data.groupId === '1010') { + if (data.groupId === `1010_${queryParams.neId}`) { const ueEvent = data.data; queue.add(async () => { modalState.maxId += 1; @@ -410,24 +445,46 @@ function wsMessage(res: Record) { } } - - onMounted(() => { // 初始字典数据 Promise.allSettled([ getDict('ue_auth_code'), getDict('ue_event_type'), getDict('ue_event_cm_state'), - ]) - .then(resArr => { - if (resArr[0].status === 'fulfilled') { - dict.ueAauthCode = resArr[0].value; - } - if (resArr[1].status === 'fulfilled') { - dict.ueEventType = resArr[1].value; - } - if (resArr[2].status === 'fulfilled') { - dict.ueEventCmState = resArr[2].value; + ]).then(resArr => { + if (resArr[0].status === 'fulfilled') { + dict.ueAauthCode = resArr[0].value; + } + if (resArr[1].status === 'fulfilled') { + dict.ueEventType = resArr[1].value; + } + if (resArr[2].status === 'fulfilled') { + dict.ueEventCmState = resArr[2].value; + } + }); + + // 获取网元网元列表 + useNeInfoStore() + .fnNelist() + .then(res => { + if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) { + if (res.data.length > 0) { + let arr: Record[] = []; + res.data.forEach(i => { + if (i.neType === 'AMF') { + arr.push({ value: i.neId, label: i.neName }); + } + }); + neOtions.value = arr; + if (arr.length > 0) { + queryParams.neId = arr[0].value; + } + } + } else { + message.warning({ + content: t('common.noData'), + duration: 2, + }); } }) .finally(() => { @@ -478,6 +535,16 @@ onBeforeUnmount(() => { + + + + + { > - + { > + + + + + + {{ t('common.search') }} + + + + {{ t('common.reset') }} + + + + { > - - - - - - {{ t('common.search') }} - - - - {{ t('common.reset') }} - - - - @@ -604,6 +671,7 @@ onBeforeUnmount(() => { :checked-children="t('common.switch.show')" :un-checked-children="t('common.switch.hide')" size="small" + :disabled="realTimeData" /> @@ -669,7 +737,7 @@ onBeforeUnmount(() => { @@ -678,32 +746,23 @@ onBeforeUnmount(() => { -