ref: v3变更,,根据核心网过滤显示网元
This commit is contained in:
@@ -16,15 +16,15 @@ import {
|
||||
export const CORE_NE_PATH = '/core-ne';
|
||||
|
||||
/**
|
||||
* 获取当前coreUid
|
||||
* @returns coreUid
|
||||
* 获取当前coreInfo
|
||||
* @returns coreInfo
|
||||
*/
|
||||
export function current() {
|
||||
return sessionGetJSON(CACHE_SESSION_CORE) || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前coreUid
|
||||
* 获取当前coreInfo
|
||||
* @returns coreUid
|
||||
*/
|
||||
export function changeCurrent(v: Record<string, any>) {
|
||||
|
||||
@@ -120,7 +120,16 @@ const menuData = computed(() => {
|
||||
rootRoute.children = rootRouteChildren;
|
||||
}
|
||||
}
|
||||
const neTypes = neStore.getNeSelectOtions.map(v => v.value);
|
||||
|
||||
// 过滤网元限定菜单
|
||||
let neTypes = [];
|
||||
// 多核心网切换菜单显示
|
||||
if (appStore.serverType === APP_SERVER_TYPE_M) {
|
||||
neTypes = neStore.getCoreDataNeSelectOtions.map(v => v.value);
|
||||
} else {
|
||||
neTypes = neStore.getNeSelectOtions.map(v => v.value);
|
||||
}
|
||||
|
||||
let routes = clearMenuItem(router.getRoutes());
|
||||
routes = routerStore.clearMenuItemByNeList(routes, neTypes);
|
||||
const { menuData } = getMenuData(routes);
|
||||
|
||||
@@ -20,7 +20,7 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
path: '/index',
|
||||
name: 'Index',
|
||||
meta: { title: 'router.index', icon: 'icon-pcduan' },
|
||||
component: () => import('@/views/index.vue'),
|
||||
component: () => import('@/views/index/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/account',
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
CACHE_LOCAL_I18N,
|
||||
CACHE_SESSION_CRYPTO_API,
|
||||
} from '@/constants/cache-keys-constants';
|
||||
import { RESULT_CODE_EXCEPTION, RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { delAccessToken, delRefreshToken } from '@/plugins/auth-token';
|
||||
import { parseUrlPath } from '@/plugins/file-static-url';
|
||||
import { localGet, localSet } from '@/utils/cache-local-utils';
|
||||
@@ -131,7 +131,7 @@ const useAppStore = defineStore('app', {
|
||||
if (localI18n == null || (!this.i18nOpen && this.i18nDefault)) {
|
||||
localSet(CACHE_LOCAL_I18N, this.i18nDefault);
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -84,6 +84,7 @@ const useCoreStore = defineStore('core', {
|
||||
});
|
||||
}
|
||||
// 当前未选择时
|
||||
this.coreSelectOtions.unshift(this.globalDefaultSelect);
|
||||
if (!this.currentSelect) {
|
||||
this.setCurrent(this.globalDefaultSelect);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from '@/constants/result-constants';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { parseDataToOptions } from '@/utils/parse-tree-utils';
|
||||
import { currentCoreUid } from '@/hooks/useCoreUid';
|
||||
|
||||
/**网元信息类型 */
|
||||
type Ne = {
|
||||
@@ -14,6 +15,10 @@ type Ne = {
|
||||
neCascaderOptions: Record<string, any>[];
|
||||
/**选择器单级父类型 */
|
||||
neSelectOtions: Record<string, any>[];
|
||||
/**Core数据级联options树结构 coreUid */
|
||||
coreDataNeCascaderOptions: Map<string, Record<string, any>[]>;
|
||||
/**Core选择器单级父类型 coreUid */
|
||||
coreDataNeSelectOtions: Map<string, Record<string, any>[]>;
|
||||
};
|
||||
|
||||
const useNeStore = defineStore('ne', {
|
||||
@@ -21,6 +26,10 @@ const useNeStore = defineStore('ne', {
|
||||
list: [],
|
||||
neCascaderOptions: [],
|
||||
neSelectOtions: [],
|
||||
/**Core数据级联options树结构 coreUid */
|
||||
coreDataNeCascaderOptions: new Map(),
|
||||
/**Core选择器单级父类型 coreUid */
|
||||
coreDataNeSelectOtions: new Map(),
|
||||
}),
|
||||
getters: {
|
||||
/**
|
||||
@@ -40,13 +49,31 @@ const useNeStore = defineStore('ne', {
|
||||
return state.neCascaderOptions;
|
||||
},
|
||||
/**
|
||||
* 选择器单级父类型
|
||||
* Core选择器单级父类型
|
||||
* @param state 内部属性不用传入
|
||||
* @returns 选择options
|
||||
*/
|
||||
getNeSelectOtions(state) {
|
||||
return state.neSelectOtions;
|
||||
},
|
||||
/**
|
||||
* Core获取级联options树结构
|
||||
* @param state 内部属性不用传入
|
||||
* @returns 级联options
|
||||
*/
|
||||
getCoreDataNeCascaderOptions(state) {
|
||||
const coreUid = currentCoreUid();
|
||||
return state.coreDataNeCascaderOptions.get(coreUid) || [];
|
||||
},
|
||||
/**
|
||||
* 选择器单级父类型
|
||||
* @param state 内部属性不用传入
|
||||
* @returns 选择options
|
||||
*/
|
||||
getCoreDataNeSelectOtions(state) {
|
||||
const coreUid = currentCoreUid();
|
||||
return state.coreDataNeSelectOtions.get(coreUid) || [];
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 刷新网元列表
|
||||
@@ -70,11 +97,12 @@ const useNeStore = defineStore('ne', {
|
||||
});
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 原始列表
|
||||
this.list = JSON.parse(JSON.stringify(res.data));
|
||||
const originalList = JSON.parse(JSON.stringify(res.data));
|
||||
this.list = originalList;
|
||||
|
||||
// 转级联数据
|
||||
const options = parseDataToOptions(
|
||||
res.data,
|
||||
originalList,
|
||||
'neType',
|
||||
'neName',
|
||||
'neUid'
|
||||
@@ -88,6 +116,39 @@ const useNeStore = defineStore('ne', {
|
||||
value: item.value,
|
||||
};
|
||||
});
|
||||
|
||||
// 根据coreUid分组
|
||||
let groupCore: Record<string, any[]> = {};
|
||||
for (const element of originalList) {
|
||||
if (element.coreId !== 0 && element.coreUid) {
|
||||
if (groupCore[element.coreUid]) {
|
||||
groupCore[element.coreUid].push(element);
|
||||
} else {
|
||||
groupCore[element.coreUid] = [element];
|
||||
}
|
||||
}
|
||||
}
|
||||
// 转Core数据级联options树结构
|
||||
for (const coreUid in groupCore) {
|
||||
const arr = groupCore[coreUid];
|
||||
// 转级联数据
|
||||
const cascaderOptions = parseDataToOptions(
|
||||
arr,
|
||||
'neType',
|
||||
'neName',
|
||||
'neUid'
|
||||
);
|
||||
this.coreDataNeCascaderOptions.set(coreUid, cascaderOptions);
|
||||
|
||||
// 转选择器单级父类型
|
||||
const selectOtions = cascaderOptions.map(item => {
|
||||
return {
|
||||
label: item.label,
|
||||
value: item.value,
|
||||
};
|
||||
});
|
||||
this.coreDataNeSelectOtions.set(coreUid, selectOtions);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
},
|
||||
|
||||
1418
src/views/account/role/index.vue
Normal file
1418
src/views/account/role/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
1122
src/views/account/third-party-login/index.vue
Normal file
1122
src/views/account/third-party-login/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
1642
src/views/account/user/index.vue
Normal file
1642
src/views/account/user/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
16
src/views/core-ne/alarm/event/index.vue
Normal file
16
src/views/core-ne/alarm/event/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/alarm/forward/index.vue
Normal file
16
src/views/core-ne/alarm/forward/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/alarm/historical/index.vue
Normal file
16
src/views/core-ne/alarm/historical/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/alarm/logs/index.vue
Normal file
16
src/views/core-ne/alarm/logs/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/metrics/kpi/index.vue
Normal file
16
src/views/core-ne/metrics/kpi/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/metrics/kpic/index.vue
Normal file
16
src/views/core-ne/metrics/kpic/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/metrics/overview-ims/index.vue
Normal file
16
src/views/core-ne/metrics/overview-ims/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/monitor/dashboard-kpi/index.vue
Normal file
16
src/views/core-ne/monitor/dashboard-kpi/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>指标概览 卡片展示</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/monitor/dashboard/index.vue
Normal file
16
src/views/core-ne/monitor/dashboard/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>数据大屏 实时展示</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/monitor/topology/index.vue
Normal file
16
src/views/core-ne/monitor/topology/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>拓扑图 状态流动</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -8,7 +8,7 @@ onMounted(() => {});
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>核心网列表</h1>
|
||||
<h1>UE接入事件 实时</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
16
src/views/core-ne/monitor/vm/index.vue
Normal file
16
src/views/core-ne/monitor/vm/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>网元VM资源 周期获取</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/ne-data/cdr/ims-cdr/index.vue
Normal file
16
src/views/core-ne/ne-data/cdr/ims-cdr/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/ne-data/cdr/sgwc-cdr/index.vue
Normal file
16
src/views/core-ne/ne-data/cdr/sgwc-cdr/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/ne-data/cdr/smf-cdr/index.vue
Normal file
16
src/views/core-ne/ne-data/cdr/smf-cdr/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/ne-data/cdr/smsc-cdr/index.vue
Normal file
16
src/views/core-ne/ne-data/cdr/smsc-cdr/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/ne-data/ims-session/index.vue
Normal file
16
src/views/core-ne/ne-data/ims-session/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/ne-data/n3iwf-session/index.vue
Normal file
16
src/views/core-ne/ne-data/n3iwf-session/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/ne-data/nssf-amf/index.vue
Normal file
16
src/views/core-ne/ne-data/nssf-amf/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/ne-data/nssf-session/index.vue
Normal file
16
src/views/core-ne/ne-data/nssf-session/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/ne-data/radio-state/index.vue
Normal file
16
src/views/core-ne/ne-data/radio-state/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/ne-data/smf-session/index.vue
Normal file
16
src/views/core-ne/ne-data/smf-session/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/ne-data/ue-event/index.vue
Normal file
16
src/views/core-ne/ne-data/ue-event/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
85
src/views/core-ne/ne/config/hooks/useArrayBatchDel.ts
Normal file
85
src/views/core-ne/ne/config/hooks/useArrayBatchDel.ts
Normal 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 } 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],
|
||||
neUid: 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,
|
||||
};
|
||||
}
|
||||
207
src/views/core-ne/ne/config/hooks/useArrayImport.ts
Normal file
207
src/views/core-ne/ne/config/hooks/useArrayImport.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
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 } 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: 'index',
|
||||
item: (row: Record<string, any>) => {
|
||||
const index = row['Index'] || 0;
|
||||
return {
|
||||
imeiPrefixValue: `${row['IMEI Prefix']}`,
|
||||
index: parseInt(index),
|
||||
};
|
||||
},
|
||||
},
|
||||
whitelist: {
|
||||
filename: 'import_amf_whitelist_template',
|
||||
fileetx: '.xlsx',
|
||||
itemKey: 'index',
|
||||
item: (row: Record<string, any>) => {
|
||||
const index = row['Index'] || 0;
|
||||
return {
|
||||
imsiValue: `${row['IMSI Value']}`,
|
||||
imeiValue: `${row['IMEI Value/Prefix']}`,
|
||||
index: parseInt(index),
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
MME: {
|
||||
white_list: {
|
||||
filename: 'import_mme_imeiWhitelist_template',
|
||||
fileetx: '.xlsx',
|
||||
itemKey: 'index',
|
||||
item: (row: Record<string, any>) => {
|
||||
const index = row['Index'] || 0;
|
||||
return {
|
||||
imei: `${row['IMEI']}`,
|
||||
index: parseInt(index),
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**状态属性 */
|
||||
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);
|
||||
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] || -1;
|
||||
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: neTypeSelect.value[0],
|
||||
neUid: neTypeSelect.value[1],
|
||||
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: neTypeSelect.value[0],
|
||||
neUid: neTypeSelect.value[1],
|
||||
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,
|
||||
};
|
||||
}
|
||||
411
src/views/core-ne/ne/config/hooks/useConfigArray.ts
Normal file
411
src/views/core-ne/ne/config/hooks/useConfigArray.ts
Normal file
@@ -0,0 +1,411 @@
|
||||
import {
|
||||
addNeConfigData,
|
||||
delNeConfigData,
|
||||
editNeConfigData,
|
||||
} from '@/api/ne/neConfig';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { Modal, message } from 'ant-design-vue/es';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
/**
|
||||
* 参数配置array类型
|
||||
* @param param 父级传入 { t, treeState, neTypeSelect, fnActiveConfigNode, ruleVerification, modalState, fnModalCancel}
|
||||
* @returns
|
||||
*/
|
||||
export default function useConfigArray({
|
||||
t,
|
||||
treeState,
|
||||
neTypeSelect,
|
||||
fnActiveConfigNode,
|
||||
ruleVerification,
|
||||
modalState,
|
||||
fnModalCancel,
|
||||
}: any) {
|
||||
/**多列列表状态类型 */
|
||||
type ArrayStateType = {
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**多列嵌套记录字段 */
|
||||
columns: Record<string, any>[];
|
||||
/**表格字段列排序 */
|
||||
columnsDnd: Record<string, any>[];
|
||||
/**多列记录数据 */
|
||||
columnsData: Record<string, any>[];
|
||||
/**多列嵌套展开key */
|
||||
arrayChildExpandKeys: any[];
|
||||
|
||||
/**多列记录数据 */
|
||||
data: Record<string, any>[];
|
||||
/**多列记录规则 */
|
||||
dataRule: Record<string, any>;
|
||||
};
|
||||
|
||||
/**多列列表状态 */
|
||||
let arrayState: ArrayStateType = reactive({
|
||||
size: 'small',
|
||||
columns: [],
|
||||
columnsDnd: [],
|
||||
columnsData: [],
|
||||
arrayChildExpandKeys: [],
|
||||
data: [],
|
||||
dataRule: {},
|
||||
});
|
||||
|
||||
/**多列表编辑 */
|
||||
function arrayEdit(rowIndex: Record<string, any>) {
|
||||
const item = arrayState.data.find((s: any) => s.key === rowIndex.value);
|
||||
if (!item) return;
|
||||
const from = arrayInitEdit(item, arrayState.dataRule);
|
||||
// 处理信息
|
||||
const row: Record<string, any> = {};
|
||||
for (const v of from.record) {
|
||||
if (Array.isArray(v.array)) {
|
||||
continue;
|
||||
}
|
||||
row[v.name] = Object.assign({}, v);
|
||||
}
|
||||
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(row, 'upfId')) {
|
||||
const v = row.upfId.value;
|
||||
if (typeof v === 'string') {
|
||||
if (v === '') {
|
||||
row.upfId.value = [];
|
||||
} else if (v.includes(';')) {
|
||||
row.upfId.value = v.split(';');
|
||||
} else if (v.includes(',')) {
|
||||
row.upfId.value = v.split(',');
|
||||
} else {
|
||||
row.upfId.value = [v];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modalState.from = row;
|
||||
modalState.type = 'arrayEdit';
|
||||
modalState.title = `${treeState.selectNode.paramDisplay} ${from.title}`;
|
||||
modalState.key = from.key;
|
||||
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
|
||||
modalState.open = true;
|
||||
|
||||
// 关闭嵌套
|
||||
arrayState.arrayChildExpandKeys = [];
|
||||
}
|
||||
|
||||
/**多列表编辑关闭 */
|
||||
function arrayEditClose() {
|
||||
arrayState.arrayChildExpandKeys = [];
|
||||
fnModalCancel();
|
||||
}
|
||||
|
||||
/**多列表编辑确认 */
|
||||
function arrayEditOk(from: Record<string, any>) {
|
||||
const loc = `${from['index']['value']}`;
|
||||
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(from, 'upfId')) {
|
||||
const v = from.upfId.value;
|
||||
if (Array.isArray(v)) {
|
||||
from.upfId.value = v.join(';');
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历提取属性和值
|
||||
let data: Record<string, any> = {};
|
||||
for (const key in from) {
|
||||
// 子嵌套的不插入
|
||||
if (from[key]['array']) {
|
||||
continue;
|
||||
}
|
||||
// 检查规则
|
||||
const [ok, msg] = ruleVerification(from[key]);
|
||||
if (!ok) {
|
||||
message.warning({
|
||||
content: `${msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
data[key] = from[key]['value'];
|
||||
}
|
||||
|
||||
// 发送
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
editNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neUid: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: data,
|
||||
loc: loc,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.ne.neConfig.updateItem', {
|
||||
num: modalState.title,
|
||||
}),
|
||||
duration: 3,
|
||||
});
|
||||
fnActiveConfigNode('#');
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.updateItemErr'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
arrayEditClose();
|
||||
});
|
||||
}
|
||||
|
||||
/**多列表删除单行 */
|
||||
function arrayDelete(rowIndex: Record<string, any>) {
|
||||
const loc = `${rowIndex.value}`;
|
||||
const title = `${treeState.selectNode.paramDisplay} Index-${loc}`;
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neConfig.delItemTip', {
|
||||
num: title,
|
||||
}),
|
||||
onOk() {
|
||||
delNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neUid: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
loc: loc,
|
||||
}).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.ne.neConfig.delItemOk', {
|
||||
num: title,
|
||||
}),
|
||||
duration: 2,
|
||||
});
|
||||
arrayEditClose();
|
||||
fnActiveConfigNode('#');
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**多列表新增单行 */
|
||||
function arrayAdd() {
|
||||
const from = arrayInitAdd(arrayState.data, arrayState.dataRule);
|
||||
|
||||
// 处理信息
|
||||
const row: Record<string, any> = {};
|
||||
for (const v of from.record) {
|
||||
if (Array.isArray(v.array)) {
|
||||
continue;
|
||||
}
|
||||
row[v.name] = Object.assign({}, v);
|
||||
}
|
||||
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(row, 'upfId')) {
|
||||
const v = row.upfId.value;
|
||||
if (typeof v === 'string') {
|
||||
if (v === '') {
|
||||
row.upfId.value = [];
|
||||
} else if (v.includes(';')) {
|
||||
row.upfId.value = v.split(';');
|
||||
} else if (v.includes(',')) {
|
||||
row.upfId.value = v.split(',');
|
||||
} else {
|
||||
row.upfId.value = [v];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modalState.from = row;
|
||||
modalState.type = 'arrayAdd';
|
||||
modalState.title = `${treeState.selectNode.paramDisplay} ${from.title}`;
|
||||
modalState.key = from.key;
|
||||
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
|
||||
modalState.open = true;
|
||||
}
|
||||
|
||||
/**多列表新增单行确认 */
|
||||
function arrayAddOk(from: Record<string, any>) {
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && Reflect.has(from, 'upfId')) {
|
||||
const v = from.upfId.value;
|
||||
if (Array.isArray(v)) {
|
||||
from.upfId.value = v.join(';');
|
||||
}
|
||||
}
|
||||
|
||||
// 遍历提取属性和值
|
||||
let data: Record<string, any> = {};
|
||||
for (const key in from) {
|
||||
// 子嵌套的不插入
|
||||
if (from[key]['array']) {
|
||||
continue;
|
||||
}
|
||||
// 检查规则
|
||||
const [ok, msg] = ruleVerification(from[key]);
|
||||
if (!ok) {
|
||||
message.warning({
|
||||
content: `${msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
data[key] = from[key]['value'];
|
||||
}
|
||||
|
||||
// 发送
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
addNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neUid: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: data,
|
||||
loc: `${from['index']['value']}`,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.ne.neConfig.addItemOk', {
|
||||
num: modalState.title,
|
||||
}),
|
||||
duration: 3,
|
||||
});
|
||||
fnActiveConfigNode('#');
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.addItemErr'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
arrayEditClose();
|
||||
});
|
||||
}
|
||||
|
||||
/**多列表编辑行数据初始化 */
|
||||
function arrayInitEdit(data: Record<string, any>, dataRule: any) {
|
||||
const dataFrom = data.record;
|
||||
const ruleFrom = Object.assign({}, JSON.parse(JSON.stringify(dataRule)));
|
||||
for (const row of ruleFrom.record) {
|
||||
// 子嵌套的不初始
|
||||
if (row.array) {
|
||||
row.value = [];
|
||||
continue;
|
||||
}
|
||||
// 查找项的值
|
||||
const item = dataFrom.find((s: any) => s.name === row.name);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
// 可选的
|
||||
row.optional = 'true';
|
||||
// 根据规则类型转值
|
||||
if (['enum', 'int'].includes(row.type)) {
|
||||
row.value = Number(item.value);
|
||||
} else if ('bool' === row.type) {
|
||||
row.value = Boolean(item.value);
|
||||
} else {
|
||||
row.value = item.value;
|
||||
}
|
||||
}
|
||||
ruleFrom.key = data.key;
|
||||
ruleFrom.title = data.title;
|
||||
return ruleFrom;
|
||||
}
|
||||
|
||||
/**多列表新增行数据初始化 */
|
||||
function arrayInitAdd(data: any[], dataRule: any) {
|
||||
// 有数据时取得最后的index
|
||||
let dataLastIndex = 0;
|
||||
if (data.length !== 0) {
|
||||
const lastFrom = Object.assign(
|
||||
{},
|
||||
JSON.parse(JSON.stringify(data.at(-1)))
|
||||
);
|
||||
if (lastFrom.record.length > 0) {
|
||||
dataLastIndex = parseInt(lastFrom.key);
|
||||
dataLastIndex += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const ruleFrom = Object.assign({}, JSON.parse(JSON.stringify(dataRule)));
|
||||
for (const row of ruleFrom.record) {
|
||||
// 子嵌套的不初始
|
||||
if (row.array) {
|
||||
row.value = [];
|
||||
continue;
|
||||
}
|
||||
// 可选的
|
||||
row.optional = 'true';
|
||||
// index值
|
||||
if (row.name === 'index') {
|
||||
let newIndex =
|
||||
dataLastIndex !== 0 ? dataLastIndex : parseInt(row.value);
|
||||
if (isNaN(newIndex)) {
|
||||
newIndex = 0;
|
||||
}
|
||||
row.value = newIndex;
|
||||
ruleFrom.key = newIndex;
|
||||
ruleFrom.title = `Index-${newIndex}`;
|
||||
continue;
|
||||
}
|
||||
// 根据规则类型转值
|
||||
if (['enum', 'int'].includes(row.type)) {
|
||||
row.value = Number(row.value);
|
||||
}
|
||||
if ('bool' === row.type) {
|
||||
row.value = Boolean(row.value);
|
||||
}
|
||||
|
||||
// 特殊SMF-upfid选择
|
||||
if (neTypeSelect.value[0] === 'SMF' && row.name === 'upfId') {
|
||||
const v = row.value;
|
||||
if (typeof v === 'string') {
|
||||
if (v === '') {
|
||||
row.value = [];
|
||||
} else if (v.includes(';')) {
|
||||
row.value = v.split(';');
|
||||
} else if (v.includes(',')) {
|
||||
row.value = v.split(',');
|
||||
} else {
|
||||
row.value = [v];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ruleFrom;
|
||||
}
|
||||
|
||||
// 监听表格字段列排序变化关闭展开
|
||||
watch(
|
||||
() => arrayState.columnsDnd,
|
||||
() => {
|
||||
arrayEditClose();
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
arrayState,
|
||||
arrayEdit,
|
||||
arrayEditClose,
|
||||
arrayEditOk,
|
||||
arrayDelete,
|
||||
arrayAdd,
|
||||
arrayAddOk,
|
||||
arrayInitEdit,
|
||||
arrayInitAdd,
|
||||
};
|
||||
}
|
||||
352
src/views/core-ne/ne/config/hooks/useConfigArrayChild.ts
Normal file
352
src/views/core-ne/ne/config/hooks/useConfigArrayChild.ts
Normal file
@@ -0,0 +1,352 @@
|
||||
import {
|
||||
addNeConfigData,
|
||||
editNeConfigData,
|
||||
delNeConfigData,
|
||||
} from '@/api/ne/neConfig';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { Modal, message } from 'ant-design-vue/es';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { nextTick, reactive } from 'vue';
|
||||
|
||||
/**
|
||||
* 参数配置array类型的嵌套array
|
||||
* @param param 父级传入 { t, treeState, neTypeSelect, fnActiveConfigNode, ruleVerification, modalState, arrayState, arrayInitEdit, arrayInitAdd, arrayEditClose}
|
||||
* @returns
|
||||
*/
|
||||
export default function useConfigArrayChild({
|
||||
t,
|
||||
treeState,
|
||||
neTypeSelect,
|
||||
fnActiveConfigNode,
|
||||
ruleVerification,
|
||||
modalState,
|
||||
arrayState,
|
||||
arrayInitEdit,
|
||||
arrayInitAdd,
|
||||
arrayEditClose,
|
||||
}: any) {
|
||||
/**多列嵌套列表状态类型 */
|
||||
type ArrayChildStateType = {
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**层级index */
|
||||
loc: string;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**多列嵌套记录字段 */
|
||||
columns: Record<string, any>[];
|
||||
/**表格字段列排序 */
|
||||
columnsDnd: Record<string, any>[];
|
||||
/**多列记录数据 */
|
||||
columnsData: Record<string, any>[];
|
||||
|
||||
/**多列嵌套记录数据 */
|
||||
data: Record<string, any>[];
|
||||
/**多列嵌套记录规则 */
|
||||
dataRule: Record<string, any>;
|
||||
};
|
||||
|
||||
/**多列嵌套表格状态 */
|
||||
let arrayChildState: ArrayChildStateType = reactive({
|
||||
title: '',
|
||||
loc: '',
|
||||
size: 'small',
|
||||
columns: [],
|
||||
columnsDnd: [],
|
||||
columnsData: [],
|
||||
data: [],
|
||||
dataRule: {},
|
||||
});
|
||||
|
||||
/**多列表展开嵌套行 */
|
||||
function arrayChildExpand(
|
||||
indexRow: Record<string, any>,
|
||||
row: Record<string, any>
|
||||
) {
|
||||
const loc = indexRow.value;
|
||||
if (arrayChildState.loc === `${loc}/${row.name}`) {
|
||||
arrayChildState.loc = '';
|
||||
arrayState.arrayChildExpandKeys = [];
|
||||
return;
|
||||
}
|
||||
arrayChildState.loc = '';
|
||||
arrayState.arrayChildExpandKeys = [];
|
||||
const from = Object.assign({}, JSON.parse(JSON.stringify(row)));
|
||||
// 无数据时
|
||||
if (!Array.isArray(from.value)) {
|
||||
from.value = [];
|
||||
}
|
||||
const dataArr = Object.freeze(from.value);
|
||||
const ruleArr = Object.freeze(from.array);
|
||||
|
||||
// 列表项数据
|
||||
const dataArray: Record<string, any>[] = [];
|
||||
for (const item of dataArr) {
|
||||
const index = item['index'];
|
||||
let record: Record<string, any>[] = [];
|
||||
for (const key of Object.keys(item)) {
|
||||
// 规则为准
|
||||
for (const rule of ruleArr) {
|
||||
if (rule['name'] === key) {
|
||||
const ruleItem = Object.assign({ optional: 'true' }, rule, {
|
||||
value: item[key],
|
||||
});
|
||||
record.push(ruleItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// dataArray.push(record);
|
||||
dataArray.push({ title: `Index-${index}`, key: index, record });
|
||||
}
|
||||
arrayChildState.data = dataArray;
|
||||
|
||||
// 无数据时,用于新增
|
||||
arrayChildState.dataRule = {
|
||||
title: `Index-0`,
|
||||
key: 0,
|
||||
record: ruleArr,
|
||||
};
|
||||
|
||||
// 列表数据
|
||||
const columnsData: Record<string, any>[] = [];
|
||||
for (const v of arrayChildState.data) {
|
||||
const row: Record<string, any> = {};
|
||||
for (const item of v.record) {
|
||||
row[item.name] = item;
|
||||
}
|
||||
columnsData.push(row);
|
||||
}
|
||||
arrayChildState.columnsData = columnsData;
|
||||
|
||||
// 列表字段
|
||||
const columns: Record<string, any>[] = [];
|
||||
for (const rule of arrayChildState.dataRule.record) {
|
||||
columns.push({
|
||||
title: rule.display,
|
||||
dataIndex: rule.name,
|
||||
align: 'left',
|
||||
resizable: true,
|
||||
width: 50,
|
||||
minWidth: 50,
|
||||
maxWidth: 250,
|
||||
});
|
||||
}
|
||||
columns.push({
|
||||
title: t('common.operate'),
|
||||
dataIndex: 'index',
|
||||
key: 'index',
|
||||
align: 'center',
|
||||
fixed: 'right',
|
||||
width: 100,
|
||||
});
|
||||
arrayChildState.columns = columns;
|
||||
|
||||
nextTick(() => {
|
||||
// 设置展开key
|
||||
arrayState.arrayChildExpandKeys = [indexRow];
|
||||
// 层级标识
|
||||
arrayChildState.loc = `${loc}/${from['name']}`;
|
||||
// 设置展开列表标题
|
||||
arrayChildState.title = `${from['display']}`;
|
||||
});
|
||||
}
|
||||
|
||||
/**多列表嵌套行编辑 */
|
||||
function arrayChildEdit(rowIndex: Record<string, any>) {
|
||||
const item = arrayChildState.data.find(
|
||||
(s: any) => s.key === rowIndex.value
|
||||
);
|
||||
if (!item) return;
|
||||
const from = arrayInitEdit(item, arrayChildState.dataRule);
|
||||
// 处理信息
|
||||
const row: Record<string, any> = {};
|
||||
for (const v of from.record) {
|
||||
if (Array.isArray(v.array)) {
|
||||
continue;
|
||||
}
|
||||
row[v.name] = Object.assign({}, v);
|
||||
}
|
||||
|
||||
modalState.from = row;
|
||||
modalState.type = 'arrayChildEdit';
|
||||
modalState.title = `${arrayChildState.title} ${from.title}`;
|
||||
modalState.key = from.key;
|
||||
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
|
||||
modalState.open = true;
|
||||
}
|
||||
|
||||
/**多列表嵌套行编辑确认 */
|
||||
function arrayChildEditOk(from: Record<string, any>) {
|
||||
const loc = `${arrayChildState.loc}/${from['index']['value']}`;
|
||||
|
||||
let data: Record<string, any> = {};
|
||||
for (const key in from) {
|
||||
// 子嵌套的不插入
|
||||
if (from[key]['array']) {
|
||||
continue;
|
||||
}
|
||||
// 检查规则
|
||||
const [ok, msg] = ruleVerification(from[key]);
|
||||
if (!ok) {
|
||||
message.warning({
|
||||
content: `${msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
data[key] = from[key]['value'];
|
||||
}
|
||||
|
||||
// 发送
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
editNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neUid: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: data,
|
||||
loc,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.ne.neConfig.updateItem', {
|
||||
num: modalState.title,
|
||||
}),
|
||||
duration: 3,
|
||||
});
|
||||
fnActiveConfigNode('#');
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.updateItemErr'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
arrayEditClose();
|
||||
});
|
||||
}
|
||||
|
||||
/**多列表嵌套行删除单行 */
|
||||
function arrayChildDelete(rowIndex: Record<string, any>) {
|
||||
const index = rowIndex.value;
|
||||
const loc = `${arrayChildState.loc}/${index}`;
|
||||
const title = `${arrayChildState.title} Index-${index}`;
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neConfig.delItemTip', {
|
||||
num: title,
|
||||
}),
|
||||
onOk() {
|
||||
delNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neUid: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
loc,
|
||||
}).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.ne.neConfig.delItemOk', {
|
||||
num: title,
|
||||
}),
|
||||
duration: 2,
|
||||
});
|
||||
arrayEditClose();
|
||||
fnActiveConfigNode('#');
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**多列表嵌套行新增单行 */
|
||||
function arrayChildAdd() {
|
||||
const from = arrayInitAdd(arrayChildState.data, arrayChildState.dataRule);
|
||||
// 处理信息
|
||||
const row: Record<string, any> = {};
|
||||
for (const v of from.record) {
|
||||
if (Array.isArray(v.array)) {
|
||||
continue;
|
||||
}
|
||||
row[v.name] = Object.assign({}, v);
|
||||
}
|
||||
|
||||
modalState.from = row;
|
||||
modalState.type = 'arrayChildAdd';
|
||||
modalState.title = `${arrayChildState.title} ${from.title}`;
|
||||
modalState.key = from.key;
|
||||
modalState.data = from.record.filter((v: any) => !Array.isArray(v.array));
|
||||
modalState.open = true;
|
||||
}
|
||||
|
||||
/**多列表新增单行确认 */
|
||||
function arrayChildAddOk(from: Record<string, any>) {
|
||||
const loc = `${arrayChildState.loc}/${from['index']['value']}`;
|
||||
|
||||
let data: Record<string, any> = {};
|
||||
for (const key in from) {
|
||||
// 子嵌套的不插入
|
||||
if (from[key]['array']) {
|
||||
continue;
|
||||
}
|
||||
// 检查规则
|
||||
const [ok, msg] = ruleVerification(from[key]);
|
||||
if (!ok) {
|
||||
message.warning({
|
||||
content: `${msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
data[key] = from[key]['value'];
|
||||
}
|
||||
|
||||
// 发送
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
addNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neUid: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: data,
|
||||
loc,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.ne.neConfig.addItemOk', {
|
||||
num: modalState.title,
|
||||
}),
|
||||
duration: 3,
|
||||
});
|
||||
fnActiveConfigNode('#');
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.addItemErr'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
arrayEditClose();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
arrayChildState,
|
||||
arrayChildExpand,
|
||||
arrayChildEdit,
|
||||
arrayChildEditOk,
|
||||
arrayChildDelete,
|
||||
arrayChildAdd,
|
||||
arrayChildAddOk,
|
||||
};
|
||||
}
|
||||
152
src/views/core-ne/ne/config/hooks/useConfigList.ts
Normal file
152
src/views/core-ne/ne/config/hooks/useConfigList.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import { editNeConfigData } from '@/api/ne/neConfig';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { reactive, toRaw } from 'vue';
|
||||
|
||||
/**
|
||||
* list类型参数处理
|
||||
* @param param 父级传入 {t, treeState, neTypeSelect, ruleVerification}
|
||||
* @returns
|
||||
*/
|
||||
export default function useConfigList({
|
||||
t,
|
||||
treeState,
|
||||
neTypeSelect,
|
||||
ruleVerification,
|
||||
}: any) {
|
||||
/**单列表状态类型 */
|
||||
type ListStateType = {
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**单列记录字段 */
|
||||
columns: Record<string, any>[];
|
||||
/**单列记录数据 */
|
||||
data: Record<string, any>[];
|
||||
/**编辑行记录 */
|
||||
editRecord: Record<string, any>;
|
||||
/**确认提交等待 */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**单列表状态 */
|
||||
let listState: ListStateType = reactive({
|
||||
size: 'small',
|
||||
columns: [
|
||||
{
|
||||
title: 'Key',
|
||||
dataIndex: 'display',
|
||||
align: 'left',
|
||||
width: '30%',
|
||||
},
|
||||
{
|
||||
title: 'Value',
|
||||
dataIndex: 'value',
|
||||
align: 'left',
|
||||
width: '70%',
|
||||
},
|
||||
],
|
||||
data: [],
|
||||
confirmLoading: false,
|
||||
editRecord: {},
|
||||
});
|
||||
|
||||
/**单列表编辑 */
|
||||
function listEdit(row: Record<string, any>) {
|
||||
if (
|
||||
listState.confirmLoading ||
|
||||
['read-only', 'read', 'ro'].includes(row.access)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
listState.editRecord = Object.assign({}, row);
|
||||
}
|
||||
|
||||
/**单列表编辑关闭 */
|
||||
function listEditClose() {
|
||||
listState.confirmLoading = false;
|
||||
listState.editRecord = {};
|
||||
}
|
||||
|
||||
/**单列表编辑确认 */
|
||||
function listEditOk() {
|
||||
if (listState.confirmLoading) return;
|
||||
const from = toRaw(listState.editRecord);
|
||||
// 检查规则
|
||||
const [ok, msg] = ruleVerification(from);
|
||||
if (!ok) {
|
||||
message.warning({
|
||||
content: `${msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送
|
||||
listState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
editNeConfigData({
|
||||
neType: neTypeSelect.value[0],
|
||||
neUid: neTypeSelect.value[1],
|
||||
paramName: treeState.selectNode.paramName,
|
||||
paramData: {
|
||||
[from['name']]: from['value'],
|
||||
},
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.ne.neConfig.updateValue', {
|
||||
num: from['display'],
|
||||
}),
|
||||
duration: 3,
|
||||
});
|
||||
// 改变表格数据
|
||||
const item = listState.data.find(
|
||||
(item: Record<string, any>) => from['name'] === item['name']
|
||||
);
|
||||
if (item) {
|
||||
Object.assign(item, listState.editRecord);
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.ne.neConfig.updateValueErr'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
listState.confirmLoading = false;
|
||||
listState.editRecord = {};
|
||||
});
|
||||
}
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 10,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 10,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: true,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
},
|
||||
});
|
||||
|
||||
return { tablePagination, listState, listEdit, listEditClose, listEditOk };
|
||||
}
|
||||
190
src/views/core-ne/ne/config/hooks/useOptions.ts
Normal file
190
src/views/core-ne/ne/config/hooks/useOptions.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { getNeConfigData } from '@/api/ne/neConfig';
|
||||
import { regExpIPv4, regExpIPv6, validURL } from '@/utils/regular-utils';
|
||||
import { ref } from 'vue';
|
||||
|
||||
/**
|
||||
* 参数公共函数
|
||||
* @param param 父级传入 {t}
|
||||
* @returns
|
||||
*/
|
||||
export default function useOptions({ t }: any) {
|
||||
/**规则校验 */
|
||||
function ruleVerification(row: Record<string, any>): (string | boolean)[] {
|
||||
let result = [true, ''];
|
||||
const type = row.type;
|
||||
const value = row.value;
|
||||
const filter = row.filter;
|
||||
const display = row.display;
|
||||
|
||||
// 子嵌套的不检查
|
||||
if (row.array) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 可选的同时没有值不检查
|
||||
if (row.optional === 'true' && !value) {
|
||||
return result;
|
||||
}
|
||||
switch (type) {
|
||||
case 'int':
|
||||
// filter: "0~128"
|
||||
|
||||
if (filter) {
|
||||
let filterArr = ['0', '1'];
|
||||
if (filter.indexOf('-') !== -1) {
|
||||
filterArr = filter.split('-');
|
||||
} else if (filter.indexOf('~') !== -1) {
|
||||
filterArr = filter.split('~');
|
||||
}
|
||||
const minInt = parseInt(filterArr[0]);
|
||||
const maxInt = parseInt(filterArr[1]);
|
||||
const valueInt = parseInt(value);
|
||||
if (valueInt < minInt || valueInt > maxInt) {
|
||||
return [
|
||||
false,
|
||||
t('views.ne.neConfig.requireInt', {
|
||||
display,
|
||||
filter,
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ipv4':
|
||||
if (!regExpIPv4.test(value)) {
|
||||
return [false, t('views.ne.neConfig.requireIpv4', { display })];
|
||||
}
|
||||
break;
|
||||
case 'ipv6':
|
||||
if (!regExpIPv6.test(value)) {
|
||||
return [false, t('views.ne.neConfig.requireIpv6', { display })];
|
||||
}
|
||||
break;
|
||||
case 'enum':
|
||||
if (filter && filter.indexOf('{') === 1) {
|
||||
let filterJson: Record<string, any> = {};
|
||||
try {
|
||||
filterJson = JSON.parse(filter); //string---json
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (!Object.keys(filterJson).includes(`${value}`)) {
|
||||
return [false, t('views.ne.neConfig.requireEnum', { display })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'bool':
|
||||
// filter: '{"0":"false", "1":"true"}'
|
||||
|
||||
if (filter && filter.indexOf('{') === 1) {
|
||||
let filterJson: Record<string, any> = {};
|
||||
try {
|
||||
filterJson = JSON.parse(filter); //string---json
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (!Object.values(filterJson).includes(`${value}`)) {
|
||||
return [false, t('views.ne.neConfig.requireBool', { display })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
// filter: "0~128"
|
||||
|
||||
// 字符串长度判断
|
||||
if (filter) {
|
||||
try {
|
||||
let rule: RegExp = new RegExp('^.*$');
|
||||
if (filter.indexOf('-') !== -1) {
|
||||
const filterArr = filter.split('-');
|
||||
rule = new RegExp(
|
||||
'^.{' + filterArr[0] + ',' + filterArr[1] + '}$'
|
||||
);
|
||||
} else if (filter.indexOf('~') !== -1) {
|
||||
const filterArr = filter.split('~');
|
||||
rule = new RegExp(
|
||||
'^\\S{' + filterArr[0] + ',' + filterArr[1] + '}$'
|
||||
);
|
||||
}
|
||||
if (!rule.test(value)) {
|
||||
return [
|
||||
false,
|
||||
t('views.ne.neConfig.requireString', {
|
||||
display,
|
||||
}),
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
// 字符串http判断
|
||||
if (value.startsWith('http')) {
|
||||
try {
|
||||
if (!validURL(value)) {
|
||||
return [
|
||||
false,
|
||||
t('views.ne.neConfig.requireString', {
|
||||
display,
|
||||
}),
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'regex':
|
||||
// filter: "^[0-9]{3}$"
|
||||
|
||||
if (filter) {
|
||||
try {
|
||||
let regex = new RegExp(filter);
|
||||
if (!regex.test(value)) {
|
||||
return [
|
||||
false,
|
||||
t('views.ne.neConfig.requireString', {
|
||||
display,
|
||||
}),
|
||||
];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return [false, t('views.ne.neConfig.requireUn', { display })];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**upfId可选择 */
|
||||
const smfByUPFIdOptions = ref<{ value: string; label: string }[]>([]);
|
||||
/**加载smf配置的upfId */
|
||||
function smfByUPFIdLoadData(neUid: string) {
|
||||
getNeConfigData({
|
||||
neType: 'SMF',
|
||||
neUid: neUid,
|
||||
paramName: 'upfConfig',
|
||||
}).then(res => {
|
||||
smfByUPFIdOptions.value = [];
|
||||
for (const s of res.data) {
|
||||
smfByUPFIdOptions.value.push({
|
||||
value: s.id,
|
||||
label: s.id,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
ruleVerification,
|
||||
smfByUPFIdLoadData,
|
||||
smfByUPFIdOptions,
|
||||
};
|
||||
}
|
||||
1082
src/views/core-ne/ne/config/index.vue
Normal file
1082
src/views/core-ne/ne/config/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
848
src/views/core-ne/ne/info/index.vue
Normal file
848
src/views/core-ne/ne/info/index.vue
Normal file
@@ -0,0 +1,848 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw, defineAsyncComponent, ref } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
import { listNeInfo, delNeInfo } from '@/api/ne/neInfo';
|
||||
import { stateNeInfo } from '@/api/ne/neAction';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useNeOptions from '@/views/ne/info/hooks/useNeOptions';
|
||||
import useCoreStore from '@/store/modules/core';
|
||||
const { getDict } = useDictStore();
|
||||
const neStore = useNeStore();
|
||||
const coreStore = useCoreStore();
|
||||
const { t } = useI18n();
|
||||
const {
|
||||
fnNeStart,
|
||||
fnNeRestart,
|
||||
fnNeStop,
|
||||
fnNeReload,
|
||||
fnNeLogFile,
|
||||
parseResouresUsage,
|
||||
} = useNeOptions();
|
||||
// 异步加载组件
|
||||
const EditModal = defineAsyncComponent(
|
||||
() => import('@/views/ne/info/components/EditModal.vue')
|
||||
);
|
||||
const OAMModal = defineAsyncComponent(
|
||||
() => import('@/views/ne/info/components/OAMModal.vue')
|
||||
);
|
||||
// 软件授权上传
|
||||
const LicenseEditModal = defineAsyncComponent(
|
||||
() => import('@/views/ne/info/components/LicenseEditModal.vue')
|
||||
);
|
||||
// 配置备份文件导入
|
||||
const BackConfModal = defineAsyncComponent(
|
||||
() => import('@/views/ne/info/components/BackConfModal.vue')
|
||||
);
|
||||
const backConf = ref(); // 引用句柄,取导出函数
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**网元信息状态 */
|
||||
neInfoStatus: DictType[];
|
||||
} = reactive({
|
||||
neInfoStatus: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
coreUid: coreStore.currentCoreUid,
|
||||
/**网元类型 */
|
||||
neType: '',
|
||||
/**带状态信息 */
|
||||
bandStatus: true,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
neType: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: Record<string, any>[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
/**勾选记录 */
|
||||
selectedRows: Record<string, any>[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
selectedRows: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.ne.common.neUid'),
|
||||
dataIndex: 'neUid',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.neType'),
|
||||
dataIndex: 'neType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.neName'),
|
||||
dataIndex: 'neName',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.ipAddr'),
|
||||
dataIndex: 'ipAddr',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.port'),
|
||||
dataIndex: 'port',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.serialNum'),
|
||||
dataIndex: 'serialNum',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.expiryDate'),
|
||||
dataIndex: 'expiryDate',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.ueNumber'),
|
||||
dataIndex: 'ueNumber',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (['UDM', 'AMF', 'MME'].includes(opt.record.neType)) {
|
||||
return opt.value;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.nbNumber'),
|
||||
dataIndex: 'nbNumber',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (['AMF', 'MME'].includes(opt.record.neType)) {
|
||||
return opt.value;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neInfo.state'),
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[], rows: any[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
tableState.selectedRows = rows.map(item => {
|
||||
return {
|
||||
id: item.id,
|
||||
neUid: item.neUid,
|
||||
neType: item.neType,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**软件授权上传框是否显示 */
|
||||
openByLicense: boolean;
|
||||
/**配置备份框是否显示 */
|
||||
openByBackConf: boolean;
|
||||
/**OAM文件配置框是否显示 */
|
||||
openByOAM: boolean;
|
||||
/**新增框或修改框是否显示 */
|
||||
openByEdit: boolean;
|
||||
/**新增框或修改框ID */
|
||||
id: number;
|
||||
neUid: string;
|
||||
neType: string;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByLicense: false,
|
||||
openByBackConf: false,
|
||||
openByOAM: false,
|
||||
openByEdit: false,
|
||||
id: 0,
|
||||
neUid: '',
|
||||
neType: '',
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param noticeId 网元id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(row?: Record<string, any>) {
|
||||
if (!row) {
|
||||
modalState.id = 0;
|
||||
modalState.neUid = '';
|
||||
modalState.neType = '';
|
||||
} else {
|
||||
modalState.id = row.id;
|
||||
modalState.neUid = row.neUid;
|
||||
modalState.neType = row.neType;
|
||||
}
|
||||
modalState.openByEdit = !modalState.openByEdit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalEditOk(from: Record<string, any>) {
|
||||
// 新增时刷新列表
|
||||
if (!from.neUid) {
|
||||
fnGetList();
|
||||
return;
|
||||
}
|
||||
// 编辑时局部更新信息
|
||||
reloadRowInfo(from);
|
||||
}
|
||||
|
||||
/**局部更新信息 */
|
||||
function reloadRowInfo(row: Record<string, any>) {
|
||||
stateNeInfo(row.neUid)
|
||||
.then(res => {
|
||||
// 找到编辑更新的网元
|
||||
const item = tableState.data.find(s => s.id === row.id);
|
||||
if (item && res.code === RESULT_CODE_SUCCESS) {
|
||||
item.neType = row.neType;
|
||||
item.neUid = row.neUid;
|
||||
item.neName = row.neName;
|
||||
item.ipAddr = row.ipAddr;
|
||||
item.port = row.port;
|
||||
if (res.data.online) {
|
||||
item.status = '1';
|
||||
if (res.data.standby) {
|
||||
item.status = '3';
|
||||
}
|
||||
} else {
|
||||
item.status = '0';
|
||||
}
|
||||
Object.assign(item.serverState, res.data);
|
||||
const resouresUsage = parseResouresUsage(item.serverState);
|
||||
Reflect.set(item, 'resoures', resouresUsage);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
neStore.fnNelistRefresh();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalEditCancel() {
|
||||
modalState.neUid = '';
|
||||
modalState.neType = '';
|
||||
modalState.openByEdit = false;
|
||||
modalState.openByOAM = false;
|
||||
modalState.openByBackConf = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录删除
|
||||
* @param id 编号
|
||||
*/
|
||||
function fnRecordDelete(id: string) {
|
||||
if (modalState.confirmLoading) return;
|
||||
let msg = t('views.ne.neInfo.delTip');
|
||||
if (id === '0') {
|
||||
msg = `${msg} ...${tableState.selectedRowKeys.length}`;
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: msg,
|
||||
onOk() {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
let reqArr: any = [];
|
||||
if (id === '0') {
|
||||
const ids = tableState.selectedRowKeys.join(',');
|
||||
delNeInfo({ id: ids });
|
||||
} else {
|
||||
tableState.data.forEach(item => {
|
||||
if (item.id === id) {
|
||||
reqArr.push(
|
||||
delNeInfo({
|
||||
id: item.id,
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
Promise.all(reqArr)
|
||||
.then(resArr => {
|
||||
if (resArr.every((item: any) => item.code === RESULT_CODE_SUCCESS)) {
|
||||
message.success(t('common.operateOk'), 3);
|
||||
// 过滤掉删除的id
|
||||
tableState.data = tableState.data.filter(item => {
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
return !tableState.selectedRowKeys.includes(item.id);
|
||||
} else {
|
||||
return item.id !== id;
|
||||
}
|
||||
});
|
||||
// 刷新缓存
|
||||
neStore.fnNelistRefresh();
|
||||
} else {
|
||||
message.error({
|
||||
content: t('common.operateErr'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录多项选择
|
||||
*/
|
||||
function fnRecordMore(type: string | number, row: Record<string, any>) {
|
||||
switch (type) {
|
||||
case 'delete':
|
||||
fnRecordDelete(row.id);
|
||||
break;
|
||||
case 'start':
|
||||
fnNeStart(row, () => reloadRowInfo(row));
|
||||
break;
|
||||
case 'restart':
|
||||
fnNeRestart(row, () => reloadRowInfo(row));
|
||||
break;
|
||||
case 'stop':
|
||||
fnNeStop(row, () => reloadRowInfo(row));
|
||||
break;
|
||||
case 'reload':
|
||||
fnNeReload(row);
|
||||
break;
|
||||
case 'log':
|
||||
fnNeLogFile(row);
|
||||
break;
|
||||
case 'oam':
|
||||
modalState.neUid = row.neUid;
|
||||
modalState.neType = row.neType;
|
||||
modalState.openByOAM = !modalState.openByOAM;
|
||||
break;
|
||||
case 'license':
|
||||
modalState.id = row.id;
|
||||
modalState.neUid = row.neUid;
|
||||
modalState.neType = row.neType;
|
||||
modalState.openByLicense = !modalState.openByLicense;
|
||||
break;
|
||||
case 'backConfExport':
|
||||
backConf.value.exportConf(row.neUid, row.neType);
|
||||
break;
|
||||
case 'backConfImport':
|
||||
modalState.neUid = row.neUid;
|
||||
modalState.neType = row.neType;
|
||||
modalState.openByBackConf = !modalState.openByBackConf;
|
||||
break;
|
||||
default:
|
||||
console.warn(type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listNeInfo(toRaw(queryParams))
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data.rows)) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
// 遍历处理资源情况数值
|
||||
tableState.data = rows.map((item: any) => {
|
||||
let resouresUsage = {
|
||||
sysDiskUsage: 0,
|
||||
sysMemUsage: 0,
|
||||
sysCpuUsage: 0,
|
||||
nfCpuUsage: 0,
|
||||
};
|
||||
const neState = item.serverState;
|
||||
if (neState) {
|
||||
resouresUsage = parseResouresUsage(neState);
|
||||
} else {
|
||||
item.serverState = { online: false };
|
||||
}
|
||||
Reflect.set(item, 'resoures', resouresUsage);
|
||||
return item;
|
||||
});
|
||||
}
|
||||
tableState.loading = false;
|
||||
})
|
||||
.finally(() => {
|
||||
// 刷新缓存的网元信息
|
||||
neStore.fnNelistRefresh();
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('ne_info_status')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.neInfoStatus = resArr[0].value;
|
||||
}
|
||||
});
|
||||
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.common.neType')" name="neType ">
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.neType"
|
||||
:options="neStore.getNeSelectOtions"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
|
||||
<template #icon><PlusOutlined /></template>
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.confirmLoading"
|
||||
@click.prevent="fnRecordDelete('0')"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('common.deleteText') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.switch.show')"
|
||||
:un-checked-children="t('common.switch.hide')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">
|
||||
{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: tableColumns.length * 120 }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dict.neInfoStatus" :value="record.status" />
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.editText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record)"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>
|
||||
{{ t('views.ne.common.restart') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordMore('restart', record)"
|
||||
>
|
||||
<template #icon><UndoOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="left">
|
||||
<template #title>{{ t('common.moreText') }}</template>
|
||||
<a-dropdown placement="bottomRight" trigger="click">
|
||||
<a-button type="link">
|
||||
<template #icon><EllipsisOutlined /> </template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key }:any) => fnRecordMore(key, record)">
|
||||
<a-menu-item key="log">
|
||||
<FileTextOutlined />
|
||||
{{ t('views.ne.common.log') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="start">
|
||||
<ThunderboltOutlined />
|
||||
{{ t('views.ne.common.start') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="stop">
|
||||
<CloseSquareOutlined />
|
||||
{{ t('views.ne.common.stop') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="reload" v-if="false">
|
||||
<SyncOutlined />
|
||||
{{ t('views.ne.common.reload') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delete">
|
||||
<DeleteOutlined />
|
||||
{{ t('common.deleteText') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
key="oam"
|
||||
v-if="!['OMC'].includes(record.neType)"
|
||||
>
|
||||
<FileTextOutlined />
|
||||
{{ t('views.ne.common.oam') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item
|
||||
key="license"
|
||||
v-if="!['OMC'].includes(record.neType)"
|
||||
>
|
||||
<FileTextOutlined />
|
||||
{{ t('views.ne.common.license') }}
|
||||
</a-menu-item>
|
||||
<!-- 配置备份 -->
|
||||
<a-menu-item key="backConfExport">
|
||||
<ExportOutlined />
|
||||
{{ t('views.ne.neInfo.backConf.export') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="backConfImport">
|
||||
<ImportOutlined />
|
||||
{{ t('views.ne.neInfo.backConf.import') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
<template #expandedRowRender="{ record }">
|
||||
<a-row>
|
||||
<a-col :offset="2" :lg="8" :md="8" :xs="8">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.ne.neInfo.info') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.serviceState') }}:</span>
|
||||
<DictTag :options="dict.neInfoStatus" :value="record.status" />
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neVersion.version') }}:</span>
|
||||
<span>{{ record.serverState.version }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.common.serialNum') }}:</span>
|
||||
<span>{{ record.serverState.sn }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.common.expiryDate') }}:</span>
|
||||
<span>{{ record.serverState.expire }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.common.ueNumber') }}:</span>
|
||||
<span
|
||||
v-if="
|
||||
['UDM', 'AMF', 'MME'].includes(record.serverState.neType)
|
||||
"
|
||||
>
|
||||
{{ record.serverState.ueNumber }}
|
||||
</span>
|
||||
<span v-else> - </span>
|
||||
</div>
|
||||
<div v-if="['AMF', 'MME'].includes(record.serverState.neType)">
|
||||
<span>{{ t('views.ne.common.nbNumber') }}:</span>
|
||||
<span> {{ record.serverState.nbNumber }} </span>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :offset="2" :lg="8" :md="8" :xs="8">
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.ne.neInfo.resourceInfo') }}
|
||||
</a-divider>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.neCpu') }}:</span>
|
||||
<a-progress
|
||||
status="normal"
|
||||
:stroke-color="
|
||||
record.resoures.nfCpuUsage < 30
|
||||
? '#52c41a'
|
||||
: record.resoures.nfCpuUsage > 70
|
||||
? '#ff4d4f'
|
||||
: '#1890ff'
|
||||
"
|
||||
:percent="record.resoures.nfCpuUsage"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.sysCpu') }}:</span>
|
||||
<a-progress
|
||||
status="normal"
|
||||
:stroke-color="
|
||||
record.resoures.sysCpuUsage < 30
|
||||
? '#52c41a'
|
||||
: record.resoures.sysCpuUsage > 70
|
||||
? '#ff4d4f'
|
||||
: '#1890ff'
|
||||
"
|
||||
:percent="record.resoures.sysCpuUsage"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.sysMem') }}:</span>
|
||||
<a-progress
|
||||
status="normal"
|
||||
:stroke-color="
|
||||
record.resoures.sysMemUsage < 30
|
||||
? '#52c41a'
|
||||
: record.resoures.sysMemUsage > 70
|
||||
? '#ff4d4f'
|
||||
: '#1890ff'
|
||||
"
|
||||
:percent="record.resoures.sysMemUsage"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<span>{{ t('views.ne.neInfo.sysDisk') }}:</span>
|
||||
<a-progress
|
||||
status="normal"
|
||||
:stroke-color="
|
||||
record.resoures.sysDiskUsage < 30
|
||||
? '#52c41a'
|
||||
: record.resoures.sysDiskUsage > 70
|
||||
? '#ff4d4f'
|
||||
: '#1890ff'
|
||||
"
|
||||
:percent="record.resoures.sysDiskUsage"
|
||||
/>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<EditModal
|
||||
v-model:open="modalState.openByEdit"
|
||||
:id="modalState.id"
|
||||
@ok="fnModalEditOk"
|
||||
@cancel="fnModalEditCancel"
|
||||
></EditModal>
|
||||
|
||||
<!-- OAM编辑框 -->
|
||||
<OAMModal
|
||||
v-model:open="modalState.openByOAM"
|
||||
:ne-uid="modalState.neUid"
|
||||
:ne-type="modalState.neType"
|
||||
@cancel="fnModalEditCancel"
|
||||
></OAMModal>
|
||||
|
||||
<!-- 配置文件备份框 -->
|
||||
<BackConfModal
|
||||
ref="backConf"
|
||||
v-model:open="modalState.openByBackConf"
|
||||
:ne-uid="modalState.neUid"
|
||||
:ne-type="modalState.neType"
|
||||
@cancel="fnModalEditCancel"
|
||||
></BackConfModal>
|
||||
|
||||
<!-- 文件上传框 -->
|
||||
<LicenseEditModal
|
||||
v-model:open="modalState.openByLicense"
|
||||
:id="modalState.id"
|
||||
:ne-uid="modalState.neUid"
|
||||
:ne-type="modalState.neType"
|
||||
@ok="fnModalEditOk"
|
||||
@cancel="fnModalEditCancel"
|
||||
></LicenseEditModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
724
src/views/core-ne/ne/version/index.vue
Normal file
724
src/views/core-ne/ne/version/index.vue
Normal file
@@ -0,0 +1,724 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw, defineAsyncComponent } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import {
|
||||
Modal,
|
||||
TableColumnsType,
|
||||
message,
|
||||
notification,
|
||||
} from 'ant-design-vue/es';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listNeVersion, operateNeVersion } from '@/api/ne/neVersion';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useMaskStore from '@/store/modules/mask';
|
||||
const maskStore = useMaskStore();
|
||||
const neStore = useNeStore();
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
// 异步加载组件
|
||||
const EditModal = defineAsyncComponent(
|
||||
() => import('@/views/ne/neSoftware/components/EditModal.vue')
|
||||
);
|
||||
const UploadMoreFile = defineAsyncComponent(
|
||||
() => import('@/views/ne/neSoftware/components/UploadMoreFile.vue')
|
||||
);
|
||||
|
||||
/**字典数据-状态 */
|
||||
let dictStatus = ref<DictType[]>([]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neType: undefined,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
neType: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: any[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
/**勾选单行记录 */
|
||||
selectedRowOne: any;
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
/**勾选单行记录 */
|
||||
selectedRowOne: { neType: '' },
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns = ref<TableColumnsType>([
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.neType'),
|
||||
dataIndex: 'neType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neVersion.version'),
|
||||
dataIndex: 'version',
|
||||
key: 'version',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
resizable: true,
|
||||
minWidth: 150,
|
||||
maxWidth: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neVersion.preVersion'),
|
||||
dataIndex: 'preVersion',
|
||||
key: 'preVersion',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
resizable: true,
|
||||
minWidth: 150,
|
||||
maxWidth: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neVersion.newVersion'),
|
||||
dataIndex: 'newVersion',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
resizable: true,
|
||||
minWidth: 150,
|
||||
maxWidth: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neVersion.status'),
|
||||
key: 'status',
|
||||
dataIndex: 'status',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: t('common.updateTime'),
|
||||
dataIndex: 'updateTime',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(
|
||||
keys: (string | number)[],
|
||||
selectedRows: any[]
|
||||
) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
// 勾选单个上传
|
||||
if (selectedRows.length === 1) {
|
||||
tableState.selectedRowOne = selectedRows[0];
|
||||
} else {
|
||||
tableState.selectedRowOne = { neType: '' };
|
||||
}
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listNeVersion(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
} else {
|
||||
tablePagination.total = 0;
|
||||
tableState.data = [];
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**单文件上传 */
|
||||
openByEdit: boolean;
|
||||
/**多文件上传 */
|
||||
openByMoreFile: boolean;
|
||||
/**勾选升级情况 */
|
||||
openByUpgrade: boolean;
|
||||
/**操作数据进行版本升级 */
|
||||
operateDataUpgrade: any[];
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByEdit: false,
|
||||
openByMoreFile: false,
|
||||
openByUpgrade: false,
|
||||
operateDataUpgrade: [],
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalEditOk() {
|
||||
fnGetList(1);
|
||||
if (modalState.openByUpgrade) {
|
||||
fnModalEditCancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalEditCancel() {
|
||||
modalState.openByEdit = false;
|
||||
modalState.openByMoreFile = false;
|
||||
modalState.openByUpgrade = false;
|
||||
modalState.operateDataUpgrade = [];
|
||||
}
|
||||
|
||||
/**版本控制升级回退 */
|
||||
function fnRecordVersion(
|
||||
action: 'upgrade' | 'rollback',
|
||||
row: Record<string, any>
|
||||
) {
|
||||
let contentTip = `${action} version packages?`;
|
||||
if (action === 'upgrade') {
|
||||
contentTip = t('views.ne.neVersion.upgradeTip');
|
||||
if (row.newVersion === '' || row.newVersion === '-') {
|
||||
message.warning(t('views.ne.neVersion.upgradeTipEmpty'), 3);
|
||||
return;
|
||||
}
|
||||
if (row.newVersion === row.version) {
|
||||
contentTip = t('views.ne.neVersion.upgradeTipEqual');
|
||||
}
|
||||
}
|
||||
if (action === 'rollback') {
|
||||
contentTip = t('views.ne.neVersion.rollbackTip');
|
||||
if (row.preVersion === '' || row.preVersion === '-') {
|
||||
message.warning(t('views.ne.neVersion.rollbackTipEmpty'), 3);
|
||||
return;
|
||||
}
|
||||
if (row.prePath === '' || row.prePath === '-') {
|
||||
message.warning(t('views.ne.neVersion.noPath'), 3);
|
||||
return;
|
||||
}
|
||||
if (row.preVersion === row.version) {
|
||||
contentTip = t('views.ne.neVersion.rollbackTipEqual');
|
||||
}
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: contentTip,
|
||||
onOk() {
|
||||
if (modalState.confirmLoading) return;
|
||||
modalState.confirmLoading = true;
|
||||
const notificationKey = 'NE_VERSION_' + action;
|
||||
notification.info({
|
||||
key: notificationKey,
|
||||
message: t('common.tipTitle'),
|
||||
description: `${row.neType} ${t('common.loading')}`,
|
||||
duration: 0,
|
||||
});
|
||||
let preinput = {};
|
||||
if (row.neType.toUpperCase() === 'IMS') {
|
||||
preinput = { pisCSCF: 'y', updateMFetc: 'No', updateMFshare: 'No' };
|
||||
}
|
||||
operateNeVersion({
|
||||
neType: row.neType,
|
||||
neUid: row.neUid,
|
||||
action: action,
|
||||
preinput: preinput,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// OMC自升级
|
||||
if (row.neType.toUpperCase() === 'OMC') {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
maskStore.handleMaskType('reload');
|
||||
} else {
|
||||
message.error(t('views.ne.neVersion.upgradeFail'), 3);
|
||||
}
|
||||
return;
|
||||
}
|
||||
fnGetList(1);
|
||||
} else {
|
||||
message.error(t('views.ne.neVersion.upgradeFail'), 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
notification.close(notificationKey);
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**版本升级弹出确认是否升级 */
|
||||
function fnRecordUpgradeConfirm() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neVersion.upgradeBatchTip'),
|
||||
onOk() {
|
||||
fnRecordUpgrade();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**版本升级进行 */
|
||||
async function fnRecordUpgrade() {
|
||||
if (modalState.confirmLoading) return;
|
||||
modalState.confirmLoading = true;
|
||||
modalState.openByUpgrade = true;
|
||||
// 操作升级的网元数据
|
||||
const selectRows = tableState.data.filter(item =>
|
||||
tableState.selectedRowKeys.includes(item.id)
|
||||
);
|
||||
for (const row of selectRows) {
|
||||
if (row.newVersion === '-' || row.newVersion === '') {
|
||||
modalState.operateDataUpgrade.push({
|
||||
neType: row.neType,
|
||||
neId: row.neId,
|
||||
status: 'fail',
|
||||
log: t('views.ne.neVersion.upgradeNotNewVer'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
// OMC跳过操作
|
||||
if (row.neType.toUpperCase() === 'OMC') {
|
||||
modalState.operateDataUpgrade.push({
|
||||
neType: row.neType,
|
||||
neId: row.neId,
|
||||
status: 'fail',
|
||||
log: t('views.ne.neVersion.upgradeOMCVer'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
// 开始升级
|
||||
let preinput = {};
|
||||
if (row.neType.toUpperCase() === 'IMS') {
|
||||
preinput = { pisCSCF: 'y', updateMFetc: 'No', updateMFshare: 'No' };
|
||||
}
|
||||
const installData = {
|
||||
neType: row.neType,
|
||||
neId: row.neId,
|
||||
action: 'upgrade',
|
||||
preinput: preinput,
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await operateNeVersion(installData);
|
||||
const operateData = {
|
||||
neType: row.neType,
|
||||
neId: row.neId,
|
||||
status: 'fail',
|
||||
log: t('common.operateErr'),
|
||||
};
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
operateData.status = 'done';
|
||||
operateData.log = t('views.ne.neVersion.upgradeDone');
|
||||
} else {
|
||||
operateData.status = 'fail';
|
||||
operateData.log = t('views.ne.neVersion.upgradeFail');
|
||||
}
|
||||
modalState.operateDataUpgrade.unshift(operateData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 结束
|
||||
modalState.confirmLoading = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
getDict('ne_version_status')
|
||||
.then(res => {
|
||||
dictStatus.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.common.neType')" name="neType ">
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.neType"
|
||||
:options="neStore.getNeSelectOtions"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.prevent="
|
||||
() => (modalState.openByEdit = !modalState.openByEdit)
|
||||
"
|
||||
>
|
||||
<template #icon><UploadOutlined /></template>
|
||||
{{ t('views.ne.neSoftware.upload') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="tableState.selectedRowKeys.length > 1"
|
||||
@click.prevent="
|
||||
() => (modalState.openByMoreFile = !modalState.openByMoreFile)
|
||||
"
|
||||
>
|
||||
<template #icon><UploadOutlined /></template>
|
||||
<template v-if="tableState.selectedRowOne.neType">
|
||||
{{ t('views.ne.neSoftware.upload') }}
|
||||
{{ tableState.selectedRowOne.neType }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t('views.ne.neSoftware.uploadBatch') }}
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:ghost="true"
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.confirmLoading"
|
||||
@click.prevent="fnRecordUpgradeConfirm()"
|
||||
>
|
||||
<template #icon><ThunderboltOutlined /></template>
|
||||
{{ t('views.ne.neVersion.upgradeBatch') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.switch.show')"
|
||||
:un-checked-children="t('common.switch.hide')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown placement="bottomRight" trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">
|
||||
{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: tableColumns.length * 150 }"
|
||||
@resizeColumn="(w:number, col:any) => (col.width = w)"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dictStatus" :value="record.status" />
|
||||
</template>
|
||||
<template v-if="column.key === 'version'">
|
||||
{{ record.version }}
|
||||
<a-tooltip
|
||||
placement="topRight"
|
||||
v-if="
|
||||
record.version && (record.path === '' || record.path === '-')
|
||||
"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('views.ne.neVersion.noPath') }}
|
||||
</template>
|
||||
<InfoCircleOutlined />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'preVersion'">
|
||||
{{ record.preVersion }}
|
||||
<a-tooltip
|
||||
placement="topRight"
|
||||
v-if="
|
||||
record.preVersion &&
|
||||
(record.prePath === '' || record.prePath === '-')
|
||||
"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('views.ne.neVersion.noPath') }}
|
||||
</template>
|
||||
<InfoCircleOutlined />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{ t('views.ne.neVersion.upgrade') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordVersion('upgrade', record)"
|
||||
>
|
||||
<template #icon><ThunderboltOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{ t('views.ne.neVersion.rollback') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordVersion('rollback', record)"
|
||||
>
|
||||
<template #icon><RollbackOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增单文件上传 -->
|
||||
<EditModal
|
||||
v-model:open="modalState.openByEdit"
|
||||
@ok="fnModalEditOk"
|
||||
@cancel="fnModalEditCancel"
|
||||
></EditModal>
|
||||
|
||||
<!-- 新增多文件上传框 -->
|
||||
<UploadMoreFile
|
||||
v-model:open="modalState.openByMoreFile"
|
||||
:ne-type="tableState.selectedRowOne.neType"
|
||||
@ok="fnModalEditOk"
|
||||
@cancel="fnModalEditCancel"
|
||||
></UploadMoreFile>
|
||||
|
||||
<!-- 勾选网元版本进行升级框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
:destroyOnClose="true"
|
||||
:body-style="{ height: '520px', overflowY: 'scroll' }"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:open="modalState.openByUpgrade"
|
||||
:title="t('views.ne.neVersion.upgradeModal')"
|
||||
:closable="false"
|
||||
@ok="fnModalEditOk"
|
||||
@cancel="fnModalEditCancel"
|
||||
>
|
||||
<template #footer>
|
||||
<a-button
|
||||
key="submit"
|
||||
type="primary"
|
||||
:disabled="modalState.confirmLoading"
|
||||
@click="fnModalEditOk"
|
||||
>
|
||||
{{ t('common.close') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<p>
|
||||
<a-alert
|
||||
v-if="modalState.confirmLoading"
|
||||
:message="t('common.loading')"
|
||||
type="info"
|
||||
show-icon
|
||||
>
|
||||
<template #icon>
|
||||
<LoadingOutlined />
|
||||
</template>
|
||||
</a-alert>
|
||||
</p>
|
||||
|
||||
<p v-for="o in modalState.operateDataUpgrade" :key="o.neUid">
|
||||
<a-alert
|
||||
:message="`${o.neType}-${o.neUid}`"
|
||||
:description="o.log"
|
||||
:type="o.status === 'done' ? 'success' : 'error'"
|
||||
show-icon
|
||||
>
|
||||
<template #icon>
|
||||
<CheckCircleOutlined v-if="o.status === 'done'" />
|
||||
<InfoCircleOutlined v-else />
|
||||
</template>
|
||||
</a-alert>
|
||||
</p>
|
||||
</ProModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
306
src/views/core-ne/tool/mml/logs/index.vue
Normal file
306
src/views/core-ne/tool/mml/logs/index.vue
Normal file
@@ -0,0 +1,306 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { mmlLogList } from '@/api/tool/mml';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
|
||||
/**记录开始结束时间 */
|
||||
let queryRangePicker = ref<[string, string]>(['', '']);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**登录账号 */
|
||||
user: '',
|
||||
/**记录时间 */
|
||||
beginTime: '',
|
||||
endTime: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
accountName: '',
|
||||
beginTime: '',
|
||||
endTime: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
queryRangePicker.value = ['', ''];
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = reactive([
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.neType'),
|
||||
dataIndex: 'neType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.neName'),
|
||||
dataIndex: 'neName',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.mml.logTime'),
|
||||
dataIndex: 'createTime',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.mml.MML'),
|
||||
dataIndex: 'command',
|
||||
key: 'command',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
},
|
||||
]);
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**查询备份信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
if (!queryRangePicker.value) {
|
||||
queryRangePicker.value = ['', ''];
|
||||
}
|
||||
queryParams.beginTime = queryRangePicker.value[0];
|
||||
queryParams.endTime = queryRangePicker.value[1];
|
||||
mmlLogList(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.logManage.mml.account')" name="user">
|
||||
<a-input
|
||||
v-model:value="queryParams.user"
|
||||
:allow-clear="true"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.logManage.mml.logTime')"
|
||||
name="queryRangePicker"
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
allow-clear
|
||||
bordered
|
||||
show-time
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
style="width: 100%"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title> </template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.switch.show')"
|
||||
:un-checked-children="t('common.switch.hide')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">
|
||||
{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<!-- <template v-if="column.key === 'mml'">
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>{{ record.result }}</template>
|
||||
<div class="mmlText">{{ record.mml }}</div>
|
||||
</a-tooltip>
|
||||
</template> -->
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
.mmlText {
|
||||
// max-width: 800px; sdf
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
825
src/views/core-ne/tool/mml/oper-ne/index.vue
Normal file
825
src/views/core-ne/tool/mml/oper-ne/index.vue
Normal file
@@ -0,0 +1,825 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { Modal, message } from 'ant-design-vue/es';
|
||||
import CodemirrorEdite from '@/components/CodemirrorEdite/index.vue';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
import { regExpIPv4, regExpIPv6 } from '@/utils/regular-utils';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { getMMLByNE, sendMML } from '@/api/mmlManage/neOperate';
|
||||
import { uploadFileToNE } from '@/api/tool/file';
|
||||
import { UploadRequestOption } from 'ant-design-vue/es/vc-upload/interface';
|
||||
const neStore = useNeStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**网元参数 */
|
||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**对象信息状态类型 */
|
||||
type StateType = {
|
||||
/**网元类型 */
|
||||
neType: string[];
|
||||
/**命令网元类型 */
|
||||
mmlNeType: string;
|
||||
/**命令数据 tree */
|
||||
mmlTreeData: any[];
|
||||
/**命令选中 */
|
||||
mmlSelect: Record<string, any>;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**自动完成 input */
|
||||
autoCompleteValue: string;
|
||||
/**自动完成 options */
|
||||
autoCompleteData: any[];
|
||||
/**自动完成 options */
|
||||
autoCompleteSearch: any[];
|
||||
/**命令发送日志 */
|
||||
mmlCmdLog: string;
|
||||
};
|
||||
|
||||
/**对象信息状态 */
|
||||
let state: StateType = reactive({
|
||||
neType: [],
|
||||
mmlNeType: '',
|
||||
mmlTreeData: [],
|
||||
mmlSelect: {
|
||||
title: '',
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
objectType: 'General',
|
||||
param: [],
|
||||
},
|
||||
from: {
|
||||
uploadLoading: false,
|
||||
sendLoading: false,
|
||||
},
|
||||
autoCompleteValue: '',
|
||||
autoCompleteData: [],
|
||||
autoCompleteSearch: [],
|
||||
mmlCmdLog: '',
|
||||
});
|
||||
|
||||
/**查询可选命令列表 */
|
||||
function fnTreeSelect(_: any, info: any) {
|
||||
state.mmlSelect = info.node.dataRef;
|
||||
state.from = {};
|
||||
// 遍历判断是否有初始value
|
||||
if (Array.isArray(state.mmlSelect.param)) {
|
||||
for (const param of state.mmlSelect.param) {
|
||||
if (typeof param.value !== 'undefined' && param.value != '') {
|
||||
const valueType = param.type;
|
||||
if (['enum', 'int'].includes(valueType)) {
|
||||
state.from[param.name] = Number(param.value);
|
||||
} else if (valueType === 'bool') {
|
||||
state.from[param.name] = Boolean(param.value);
|
||||
} else {
|
||||
state.from[param.name] = param.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.autoCompleteValue =
|
||||
`${state.mmlSelect.operation} ${state.mmlSelect.object}`.trim();
|
||||
// state.mmlCmdLog = '';
|
||||
// 回到顶部
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth', // 平滑滚动到顶部,如果不需要平滑效果可以将此行代码删除
|
||||
});
|
||||
}
|
||||
|
||||
/**清空控制台日志 */
|
||||
function fnCleanCmdLog() {
|
||||
state.mmlCmdLog = '';
|
||||
}
|
||||
|
||||
/**清空表单 */
|
||||
function fnCleanFrom() {
|
||||
state.mmlSelect = {
|
||||
title: '',
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
objectType: 'General',
|
||||
param: [],
|
||||
};
|
||||
state.from = {};
|
||||
}
|
||||
|
||||
/**命令发送 */
|
||||
function fnSendMML() {
|
||||
if (state.from.sendLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cmdArr: string[] = [];
|
||||
const { operation, object, objectType, param } = state.mmlSelect;
|
||||
// 根据参数取值
|
||||
let argsArr: string[] = [];
|
||||
if (operation && Array.isArray(param)) {
|
||||
const from = toRaw(state.from);
|
||||
for (const item of toRaw(param)) {
|
||||
const value = from[item.name];
|
||||
|
||||
// 是否必填项且有效值
|
||||
const notV = value === null || value === undefined || value === '';
|
||||
if (item.optional === 'false' && notV) {
|
||||
message.warning(t('views.mmlManage.require', { num: item.display }), 2);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否存在值
|
||||
if (!Reflect.has(from, item.name) || notV) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查规则
|
||||
const [ok, msg] = ruleVerification(item, from[item.name]);
|
||||
if (!ok) {
|
||||
message.warning({
|
||||
content: `${msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
argsArr.push(`${item.name}=${from[item.name]}`);
|
||||
}
|
||||
|
||||
// 拼装命令
|
||||
const argsStr = argsArr.join(',');
|
||||
let cmdStr = '';
|
||||
if (object && argsStr) {
|
||||
cmdStr = `${operation} ${object} ${argsStr}`;
|
||||
} else if (object) {
|
||||
cmdStr = `${operation} ${object}`;
|
||||
} else {
|
||||
cmdStr = `${operation} ${argsStr}`;
|
||||
}
|
||||
cmdArr = [cmdStr.trim()];
|
||||
}
|
||||
|
||||
if (cmdArr.length > 0) {
|
||||
state.autoCompleteValue = cmdArr[0];
|
||||
} else {
|
||||
let value = state.autoCompleteValue;
|
||||
if (value.indexOf('\n') !== -1) {
|
||||
value = value.replace(/(\r\n|\n)/g, ';');
|
||||
}
|
||||
cmdArr = value.split(';');
|
||||
}
|
||||
if (cmdArr.length === 1 && cmdArr[0] === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送
|
||||
state.from.sendLoading = true;
|
||||
const [neType, neUid] = state.neType;
|
||||
sendMML({
|
||||
neUid: neUid,
|
||||
neType: neType,
|
||||
type: objectType,
|
||||
command: cmdArr,
|
||||
})
|
||||
.then(res => {
|
||||
state.from.sendLoading = false;
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
let resultArr = res.data;
|
||||
for (let i = 0; i < resultArr.length; i++) {
|
||||
const str = resultArr[i] || '';
|
||||
const logStr = str.replace(/(\r\n|\n)/g, '\n');
|
||||
const cmdStr = cmdArr[i] || '';
|
||||
state.mmlCmdLog += `${cmdStr}\n${logStr}\n`;
|
||||
}
|
||||
} else {
|
||||
state.mmlCmdLog += `${res.msg}\n`;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
// 控制台滚动底部
|
||||
const container = document.getElementsByClassName('cm-scroller')[0];
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**上传变更 */
|
||||
function fnUpload(up: UploadRequestOption, name: string) {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.mmlManage.uploadFileTip'),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
state.from.uploadLoading = true;
|
||||
const [neType, neUid] = state.neType;
|
||||
uploadFileToNE(neType, neUid, up.file as File, 5)
|
||||
.then(res => {
|
||||
// 文件转存
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('views.mmlManage.uploadFileOk'), 3);
|
||||
state.from[name] = res.data;
|
||||
} else {
|
||||
message.error(t('views.mmlManage.uploadFileErr'), 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.from.uploadLoading = false;
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**规则校验 */
|
||||
function ruleVerification(
|
||||
row: Record<string, any>,
|
||||
value: any
|
||||
): (string | boolean)[] {
|
||||
let result = [true, ''];
|
||||
const type = row.type;
|
||||
const filter = row.filter;
|
||||
const display = row.display;
|
||||
|
||||
switch (type) {
|
||||
case 'int':
|
||||
if (filter && filter.indexOf('~') !== -1) {
|
||||
const filterArr = filter.split('~');
|
||||
const minInt = parseInt(filterArr[0]);
|
||||
const maxInt = parseInt(filterArr[1]);
|
||||
const valueInt = parseInt(value);
|
||||
if (valueInt < minInt || valueInt > maxInt) {
|
||||
return [false, t('views.mmlManage.requireInt', { display, filter })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ipv4':
|
||||
if (!regExpIPv4.test(value)) {
|
||||
return [false, t('views.mmlManage.requireIpv4', { display })];
|
||||
}
|
||||
break;
|
||||
case 'ipv6':
|
||||
if (!regExpIPv6.test(value)) {
|
||||
return [false, t('views.mmlManage.requireIpv6', { display })];
|
||||
}
|
||||
break;
|
||||
case 'enum':
|
||||
if (filter && filter.indexOf('{') === 1) {
|
||||
let filterJson: Record<string, any> = {};
|
||||
try {
|
||||
filterJson = JSON.parse(filter); //string---json
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (!Object.keys(filterJson).includes(`${value}`)) {
|
||||
return [false, t('views.mmlManage.requireEnum', { display })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'bool':
|
||||
if (filter && filter.indexOf('{') === 1) {
|
||||
let filterJson: Record<string, any> = {};
|
||||
try {
|
||||
filterJson = JSON.parse(filter); //string---json
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (!Object.values(filterJson).includes(`${value}`)) {
|
||||
return [false, t('views.mmlManage.requireBool', { display })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
if (filter && filter.indexOf('~') !== -1) {
|
||||
try {
|
||||
const filterArr = filter.split('~');
|
||||
let rule = new RegExp(
|
||||
'^\\S{' + filterArr[0] + ',' + filterArr[1] + '}$'
|
||||
);
|
||||
if (!rule.test(value)) {
|
||||
return [false, t('views.mmlManage.requireString', { display })];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'regex':
|
||||
if (filter) {
|
||||
try {
|
||||
let regex = new RegExp(filter);
|
||||
if (!regex.test(value)) {
|
||||
return [false, t('views.mmlManage.requireString', { display })];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'file':
|
||||
if (filter) {
|
||||
const arr: string[] = filter.split(',');
|
||||
const itemArr = arr.filter(item => value.endsWith(item));
|
||||
if (itemArr.length === 0) {
|
||||
return [false, t('views.mmlManage.requireFile', { display })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(t('views.mmlManage.requireUn', { display }), type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**网元类型选择对应修改 */
|
||||
function fnNeChange(keys: any, _: any) {
|
||||
// 不是同类型时需要重新加载
|
||||
if (state.mmlNeType !== keys[0]) {
|
||||
state.autoCompleteSearch = [];
|
||||
state.autoCompleteData = [];
|
||||
state.mmlTreeData = [];
|
||||
state.mmlSelect = {
|
||||
title: '',
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
objectType: 'General',
|
||||
param: {},
|
||||
};
|
||||
fnGetList();
|
||||
}
|
||||
}
|
||||
|
||||
/**查询可选命令列表 */
|
||||
function fnGetList() {
|
||||
const neType = state.neType[0];
|
||||
state.mmlNeType = neType;
|
||||
getMMLByNE(neType).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
// 构建自动完成筛选结构
|
||||
const autoCompleteArr: Record<string, any>[] = [];
|
||||
// 构建树结构
|
||||
const treeArr: Record<string, any>[] = [];
|
||||
for (const item of res.data.rows) {
|
||||
const id = item['id'];
|
||||
const object = item['object'];
|
||||
const objectType = item['objectType'];
|
||||
const operation = item['operation'];
|
||||
const mmlDisplay = item['mmlDisplay'];
|
||||
// 可选属性参数
|
||||
let param = [];
|
||||
try {
|
||||
param = JSON.parse(item['paramJson']);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
// 遍历检查大类
|
||||
const treeItemIndex = treeArr.findIndex(i => i.key == item['category']);
|
||||
if (treeItemIndex < 0) {
|
||||
treeArr.push({
|
||||
title: item['catDisplay'],
|
||||
key: item['category'],
|
||||
selectable: false,
|
||||
children: [
|
||||
{
|
||||
key: id,
|
||||
title: mmlDisplay,
|
||||
object,
|
||||
objectType,
|
||||
operation,
|
||||
param,
|
||||
},
|
||||
],
|
||||
});
|
||||
autoCompleteArr.push({
|
||||
value: item['catDisplay'],
|
||||
key: item['category'],
|
||||
selectable: false,
|
||||
options: [
|
||||
{
|
||||
key: id,
|
||||
value: mmlDisplay,
|
||||
object,
|
||||
objectType,
|
||||
operation,
|
||||
param,
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
treeArr[treeItemIndex].children.push({
|
||||
key: id,
|
||||
title: mmlDisplay,
|
||||
object,
|
||||
objectType,
|
||||
operation,
|
||||
param,
|
||||
});
|
||||
autoCompleteArr[treeItemIndex].options.push({
|
||||
key: id,
|
||||
value: mmlDisplay,
|
||||
object,
|
||||
objectType,
|
||||
operation,
|
||||
param,
|
||||
});
|
||||
}
|
||||
}
|
||||
state.mmlTreeData = treeArr;
|
||||
state.autoCompleteData = autoCompleteArr;
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.mmlManage.cmdNoTip', { num: neType }),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**自动完成搜索匹配前缀 */
|
||||
function fnAutoCompleteSearch(value: string) {
|
||||
state.autoCompleteSearch = [];
|
||||
for (const item of state.autoCompleteData) {
|
||||
const filterOptions = item.options.filter((s: any) => {
|
||||
return `${s.operation} ${s.object}`.indexOf(value.toLowerCase()) >= 0;
|
||||
});
|
||||
if (filterOptions.length > 0) {
|
||||
state.autoCompleteSearch.push({
|
||||
value: item.value,
|
||||
key: item.value,
|
||||
selectable: false,
|
||||
options: filterOptions,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**自动完成搜索选择 */
|
||||
function fnAutoCompleteSelect(_: any, option: any) {
|
||||
if (Object.keys(option).length === 0) {
|
||||
return;
|
||||
}
|
||||
state.mmlSelect = {
|
||||
title: option.value,
|
||||
key: option.key,
|
||||
operation: option.operation,
|
||||
object: option.object,
|
||||
objectType: option.objectType,
|
||||
param: option.param,
|
||||
};
|
||||
state.from = {};
|
||||
state.autoCompleteValue = `${option.operation} ${option.object}`.trim();
|
||||
}
|
||||
|
||||
/**自动完成搜索选择 */
|
||||
function fnAutoCompleteChange(value: any, _: any) {
|
||||
if (value.indexOf(';') !== -1 || value.indexOf('\n') !== -1) {
|
||||
fnCleanFrom();
|
||||
return;
|
||||
}
|
||||
for (const item of state.autoCompleteData) {
|
||||
const findItem = item.options.find((s: any) => {
|
||||
const prefix = `${s.operation} ${s.object}`;
|
||||
return value.startsWith(prefix);
|
||||
});
|
||||
if (findItem) {
|
||||
state.mmlSelect = {
|
||||
title: findItem.value,
|
||||
key: findItem.key,
|
||||
operation: findItem.operation,
|
||||
object: findItem.object,
|
||||
param: findItem.param,
|
||||
};
|
||||
state.from = {};
|
||||
// 截取拆分赋值
|
||||
const prefix = `${findItem.operation} ${findItem.object} `;
|
||||
const argsStr = value.replace(prefix, '');
|
||||
if (argsStr.length > 3) {
|
||||
const argsArr = argsStr.split(',');
|
||||
for (const arg of argsArr) {
|
||||
const kvArr = arg.split('=');
|
||||
if (kvArr.length >= 2) {
|
||||
state.from[kvArr[0]] = kvArr[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
state.mmlSelect = {
|
||||
title: '',
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
objectType: 'General',
|
||||
param: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**自动完成按键触发 */
|
||||
function fnAutoCompleteKeydown(evt: KeyboardEvent) {
|
||||
if (evt.key === 'Enter') {
|
||||
// 阻止默认的换行行为
|
||||
evt.preventDefault();
|
||||
// 按下 Shift + Enter 键时换行
|
||||
if (evt.shiftKey && evt.target) {
|
||||
// 插入换行符
|
||||
const textarea = evt.target as HTMLInputElement;
|
||||
const start = textarea.selectionStart || 0;
|
||||
const end = textarea.selectionEnd || 0;
|
||||
const text = textarea.value;
|
||||
textarea.value = text.substring(0, start) + '\n' + text.substring(end);
|
||||
state.autoCompleteValue = textarea.value;
|
||||
// 更新光标位置
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 1;
|
||||
} else {
|
||||
fnSendMML();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
neCascaderOptions.value = neStore.getNeCascaderOptions.filter((item: any) => {
|
||||
return !['OMC', 'CBC', 'SGWC'].includes(item.value); // 过滤不可用的网元
|
||||
});
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 默认选择AMF
|
||||
const item = neCascaderOptions.value.find(s => s.value === 'AMF');
|
||||
if (item && item.children) {
|
||||
const info = item.children[0];
|
||||
state.neType = [info.neType, info.neUid];
|
||||
} else {
|
||||
const info = neCascaderOptions.value[0].children[0];
|
||||
state.neType = [info.neType, info.neUid];
|
||||
}
|
||||
|
||||
// 列表
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="6" :xs="24" style="margin-bottom: 24px">
|
||||
<!-- 命令导航 -->
|
||||
<a-card
|
||||
size="small"
|
||||
:bordered="false"
|
||||
:title="t('views.mmlManage.cmdTitle')"
|
||||
>
|
||||
<a-form layout="vertical" autocomplete="off">
|
||||
<a-form-item name="neType">
|
||||
<a-cascader
|
||||
v-model:value="state.neType"
|
||||
:options="neCascaderOptions"
|
||||
@change="fnNeChange"
|
||||
:allow-clear="false"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item name="mmlTree" v-if="state.mmlTreeData.length > 0">
|
||||
<a-directory-tree
|
||||
:tree-data="state.mmlTreeData"
|
||||
default-expand-all
|
||||
@select="fnTreeSelect"
|
||||
:selectedKeys="[state.mmlSelect.key]"
|
||||
></a-directory-tree>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :lg="18" :md="18" :xs="24">
|
||||
<!-- 命令参数输入 -->
|
||||
<a-card size="small" :bordered="false">
|
||||
<template #title>
|
||||
<a-typography-text strong v-if="state.mmlSelect.title">
|
||||
{{ state.mmlSelect.title }}
|
||||
</a-typography-text>
|
||||
<a-typography-text type="danger" v-else>
|
||||
{{ t('views.mmlManage.cmdOpTip') }}
|
||||
</a-typography-text>
|
||||
</template>
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8">
|
||||
<a-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click.prevent="fnCleanFrom"
|
||||
v-if="!!state.mmlSelect.param"
|
||||
>
|
||||
<template #icon>
|
||||
<ClearOutlined />
|
||||
</template>
|
||||
{{ t('views.mmlManage.clearForm') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="state.from.sendLoading"
|
||||
@click.prevent="fnSendMML"
|
||||
>
|
||||
<template #icon>
|
||||
<SendOutlined />
|
||||
</template>
|
||||
{{ t('views.mmlManage.exec') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<a-form
|
||||
layout="horizontal"
|
||||
autocomplete="off"
|
||||
:validate-on-rule-change="false"
|
||||
:validateTrigger="[]"
|
||||
>
|
||||
<a-form-item
|
||||
:label="t('views.mmlManage.cmdQuickEntry')"
|
||||
:help="t('views.mmlManage.cmdQuickEntryHelp')"
|
||||
>
|
||||
<!-- 非UPF通用4100 -->
|
||||
<a-auto-complete
|
||||
v-if="state.neType[0] !== 'UPF'"
|
||||
v-model:value="state.autoCompleteValue"
|
||||
:dropdown-match-select-width="500"
|
||||
style="width: 100%"
|
||||
:options="state.autoCompleteSearch"
|
||||
@search="fnAutoCompleteSearch"
|
||||
@select="fnAutoCompleteSelect"
|
||||
@change="fnAutoCompleteChange"
|
||||
@keydown="fnAutoCompleteKeydown"
|
||||
>
|
||||
<a-textarea :placeholder="t('common.inputPlease')" auto-size />
|
||||
</a-auto-complete>
|
||||
<!-- 可选接口类型mml -->
|
||||
<a-input-group compact v-else>
|
||||
<a-auto-complete
|
||||
v-model:value="state.autoCompleteValue"
|
||||
:dropdown-match-select-width="500"
|
||||
style="width: 80%"
|
||||
:options="state.autoCompleteSearch"
|
||||
@search="fnAutoCompleteSearch"
|
||||
@select="fnAutoCompleteSelect"
|
||||
@change="fnAutoCompleteChange"
|
||||
@keydown="fnAutoCompleteKeydown"
|
||||
>
|
||||
<a-textarea
|
||||
:placeholder="t('common.inputPlease')"
|
||||
auto-size
|
||||
/>
|
||||
</a-auto-complete>
|
||||
<a-select
|
||||
v-model:value="state.mmlSelect.objectType"
|
||||
style="width: 20%"
|
||||
>
|
||||
<a-select-option value="General">
|
||||
{{ t('views.mmlManage.neOperate.mml') }}
|
||||
</a-select-option>
|
||||
<a-select-option value="Standard">
|
||||
{{ t('views.mmlManage.neOperate.mml2') }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<template v-if="state.mmlSelect.operation && state.mmlSelect.param">
|
||||
<a-form
|
||||
layout="vertical"
|
||||
autocomplete="off"
|
||||
:validate-on-rule-change="false"
|
||||
:validateTrigger="[]"
|
||||
>
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.mmlManage.cmdParamPanel') }}
|
||||
</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
:lg="6"
|
||||
:md="12"
|
||||
:xs="24"
|
||||
v-for="item in state.mmlSelect.param"
|
||||
>
|
||||
<a-form-item
|
||||
:label="item.display"
|
||||
:name="item.name"
|
||||
:required="item.optional === 'false'"
|
||||
>
|
||||
<a-tooltip>
|
||||
<template #title v-if="item.comment">
|
||||
{{ item.comment }}
|
||||
</template>
|
||||
|
||||
<a-input-number
|
||||
v-if="item.type === 'int'"
|
||||
v-model:value="state.from[item.name]"
|
||||
style="width: 100%"
|
||||
></a-input-number>
|
||||
<a-switch
|
||||
v-else-if="item.type === 'bool'"
|
||||
v-model:checked="state.from[item.name]"
|
||||
:checked-children="t('common.switch.open')"
|
||||
:un-checked-children="t('common.switch.shut')"
|
||||
></a-switch>
|
||||
<a-select
|
||||
v-else-if="item.type === 'enum'"
|
||||
v-model:value="state.from[item.name]"
|
||||
:allow-clear="item.optional === 'true'"
|
||||
>
|
||||
<a-select-option
|
||||
:value="v"
|
||||
:key="v"
|
||||
v-for="(k, v) in JSON.parse(item.filter)"
|
||||
>
|
||||
{{ k }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-input-group compact v-else-if="item.type === 'file'">
|
||||
<a-input
|
||||
v-model:value="state.from[item.name]"
|
||||
style="width: calc(100% - 32px)"
|
||||
/>
|
||||
<a-upload
|
||||
name="file"
|
||||
list-type="text"
|
||||
:accept="item.filter"
|
||||
:max-count="1"
|
||||
:show-upload-list="false"
|
||||
:custom-request="(v:any) => fnUpload(v, item.name)"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="state.from.uploadLoading"
|
||||
>
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-input-group>
|
||||
<a-input
|
||||
v-else
|
||||
v-model:value="state.from[item.name]"
|
||||
></a-input>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
</a-card>
|
||||
|
||||
<!-- 命令展示 -->
|
||||
<a-card
|
||||
:title="t('views.mmlManage.cmdConsole')"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
:body-style="{ padding: 0 }"
|
||||
style="margin-top: 16px"
|
||||
v-show="state.mmlCmdLog"
|
||||
>
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click.prevent="fnCleanCmdLog"
|
||||
>
|
||||
<template #icon>
|
||||
<ClearOutlined />
|
||||
</template>
|
||||
{{ t('views.mmlManage.clearLog') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<CodemirrorEdite
|
||||
:value="state.mmlCmdLog"
|
||||
:disabled="true"
|
||||
height="500px"
|
||||
></CodemirrorEdite>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
751
src/views/core-ne/tool/mml/oper-udm/index.vue
Normal file
751
src/views/core-ne/tool/mml/oper-udm/index.vue
Normal file
@@ -0,0 +1,751 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { Modal, message } from 'ant-design-vue/es';
|
||||
import CodemirrorEdite from '@/components/CodemirrorEdite/index.vue';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
import { regExpIPv4, regExpIPv6 } from '@/utils/regular-utils';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { getMMLByUDM, sendMMlByGeneral } from '@/api/mmlManage/neOperate';
|
||||
import { UploadRequestOption } from 'ant-design-vue/es/vc-upload/interface';
|
||||
import { uploadFileToNE } from '@/api/tool/file';
|
||||
import useCoreStore from '@/store/modules/core';
|
||||
const { t } = useI18n();
|
||||
const neStore = useNeStore();
|
||||
const coreStore = useCoreStore();
|
||||
/**网元参数 */
|
||||
let neOptions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**对象信息状态类型 */
|
||||
type StateType = {
|
||||
/**网元ID */
|
||||
neUid: string | undefined;
|
||||
/**命令数据 tree */
|
||||
mmlTreeData: any[];
|
||||
/**命令选中 */
|
||||
mmlSelect: Record<string, any>;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**自动完成 input */
|
||||
autoCompleteValue: string;
|
||||
/**自动完成 options */
|
||||
autoCompleteData: any[];
|
||||
/**自动完成 options */
|
||||
autoCompleteSearch: any[];
|
||||
/**命令发送日志 */
|
||||
mmlCmdLog: string;
|
||||
};
|
||||
|
||||
/**对象信息状态 */
|
||||
let state: StateType = reactive({
|
||||
neUid: undefined,
|
||||
mmlTreeData: [],
|
||||
mmlSelect: {
|
||||
title: '',
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
param: [],
|
||||
},
|
||||
from: {
|
||||
uploadLoading: false,
|
||||
sendLoading: false,
|
||||
},
|
||||
autoCompleteValue: '',
|
||||
autoCompleteData: [],
|
||||
autoCompleteSearch: [],
|
||||
mmlCmdLog: '',
|
||||
});
|
||||
|
||||
/**查询可选命令列表 */
|
||||
function fnTreeSelect(_: any, info: any) {
|
||||
state.mmlSelect = info.node.dataRef;
|
||||
state.from = {};
|
||||
// 遍历判断是否有初始value
|
||||
if (Array.isArray(state.mmlSelect.param)) {
|
||||
for (const param of state.mmlSelect.param) {
|
||||
if (typeof param.value !== 'undefined' && param.value != '') {
|
||||
const valueType = param.type;
|
||||
if (['enum', 'int'].includes(valueType)) {
|
||||
state.from[param.name] = Number(param.value);
|
||||
} else if (valueType === 'bool') {
|
||||
state.from[param.name] = Boolean(param.value);
|
||||
} else {
|
||||
state.from[param.name] = param.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.autoCompleteValue =
|
||||
`${state.mmlSelect.operation} ${state.mmlSelect.object}`.trim();
|
||||
// state.mmlCmdLog = '';
|
||||
// 回到顶部
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth', // 平滑滚动到顶部,如果不需要平滑效果可以将此行代码删除
|
||||
});
|
||||
}
|
||||
|
||||
/**清空控制台日志 */
|
||||
function fnCleanCmdLog() {
|
||||
state.mmlCmdLog = '';
|
||||
}
|
||||
|
||||
/**清空表单 */
|
||||
function fnCleanFrom() {
|
||||
state.mmlSelect = {
|
||||
title: '',
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
param: [],
|
||||
};
|
||||
state.from = {};
|
||||
}
|
||||
|
||||
/**命令发送 */
|
||||
function fnSendMML() {
|
||||
if (state.from.sendLoading) {
|
||||
return;
|
||||
}
|
||||
if (!state.neUid) {
|
||||
message.warning({
|
||||
content: t('views.mmlManage.udmOpesrate.noUDM'),
|
||||
duration: 5,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let cmdArr: string[] = [];
|
||||
const operation = state.mmlSelect.operation;
|
||||
const object = state.mmlSelect.object;
|
||||
// 根据参数取值
|
||||
let argsArr: string[] = [];
|
||||
const param = toRaw(state.mmlSelect.param) || [];
|
||||
if (operation && Array.isArray(param)) {
|
||||
const from = toRaw(state.from);
|
||||
for (const item of param) {
|
||||
const value = from[item.name];
|
||||
|
||||
// 是否必填项且有效值
|
||||
const notV = value === null || value === undefined || value === '';
|
||||
if (item.optional === 'false' && notV) {
|
||||
message.warning(t('views.mmlManage.require', { num: item.display }), 2);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否存在值
|
||||
if (!Reflect.has(from, item.name) || notV) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查规则
|
||||
const [ok, msg] = ruleVerification(item, from[item.name]);
|
||||
if (!ok) {
|
||||
message.warning({
|
||||
content: `${msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
argsArr.push(`${item.name}=${from[item.name]}`);
|
||||
}
|
||||
|
||||
// 拼装命令
|
||||
const argsStr = argsArr.join(',');
|
||||
let cmdStr = '';
|
||||
if (object && argsStr) {
|
||||
cmdStr = `${operation} ${object}:${argsStr}`;
|
||||
} else if (object) {
|
||||
cmdStr = `${operation} ${object}`;
|
||||
} else {
|
||||
cmdStr = `${operation} ${argsStr}`;
|
||||
}
|
||||
cmdArr = [cmdStr.trim()];
|
||||
}
|
||||
|
||||
if (cmdArr.length > 0) {
|
||||
state.autoCompleteValue = cmdArr[0];
|
||||
} else {
|
||||
let value = state.autoCompleteValue;
|
||||
if (value.indexOf('\n') !== -1) {
|
||||
value = value.replace(/(\r\n|\n)/g, ';');
|
||||
}
|
||||
cmdArr = value.split(';');
|
||||
}
|
||||
|
||||
// 发送
|
||||
state.from.sendLoading = true;
|
||||
sendMMlByGeneral(state.neUid, cmdArr)
|
||||
.then(res => {
|
||||
state.from.sendLoading = false;
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
let resultArr = res.data;
|
||||
for (let i = 0; i < resultArr.length; i++) {
|
||||
const str = resultArr[i] || '';
|
||||
const logStr = str.replace(/(\r\n|\n)/g, '\n');
|
||||
const cmdStr = cmdArr[i] || '';
|
||||
state.mmlCmdLog += `${cmdStr}\n${logStr}\n`;
|
||||
}
|
||||
} else {
|
||||
state.mmlCmdLog += `${res.msg}\n`;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
// 控制台滚动底部
|
||||
const container = document.getElementsByClassName('cm-scroller')[0];
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**上传变更 */
|
||||
function fnUpload(up: UploadRequestOption, name: string) {
|
||||
const neUid = state.neUid;
|
||||
if (!neUid) {
|
||||
message.warning({
|
||||
content: t('views.mmlManage.udmOpesrate.noUDM'),
|
||||
duration: 5,
|
||||
});
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.mmlManage.uploadFileTip'),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
state.from.uploadLoading = true;
|
||||
uploadFileToNE('UDM', neUid, up.file as File, 5)
|
||||
.then(res => {
|
||||
// 文件转存
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('views.mmlManage.uploadFileOk'), 3);
|
||||
state.from[name] = res.data;
|
||||
} else {
|
||||
message.error(t('views.mmlManage.uploadFileErr'), 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.from.uploadLoading = false;
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**规则校验 */
|
||||
function ruleVerification(
|
||||
row: Record<string, any>,
|
||||
value: any
|
||||
): (string | boolean)[] {
|
||||
let result = [true, ''];
|
||||
const type = row.type;
|
||||
const filter = row.filter;
|
||||
const display = row.display;
|
||||
|
||||
switch (type) {
|
||||
case 'int':
|
||||
if (filter && filter.indexOf('~') !== -1) {
|
||||
const filterArr = filter.split('~');
|
||||
const minInt = parseInt(filterArr[0]);
|
||||
const maxInt = parseInt(filterArr[1]);
|
||||
const valueInt = parseInt(value);
|
||||
if (valueInt < minInt || valueInt > maxInt) {
|
||||
return [false, t('views.mmlManage.requireInt', { display, filter })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'ipv4':
|
||||
if (!regExpIPv4.test(value)) {
|
||||
return [false, t('views.mmlManage.requireIpv4', { display })];
|
||||
}
|
||||
break;
|
||||
case 'ipv6':
|
||||
if (!regExpIPv6.test(value)) {
|
||||
return [false, t('views.mmlManage.requireIpv6', { display })];
|
||||
}
|
||||
break;
|
||||
case 'enum':
|
||||
if (filter && filter.indexOf('{') === 1) {
|
||||
let filterJson: Record<string, any> = {};
|
||||
try {
|
||||
filterJson = JSON.parse(filter); //string---json
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (!Object.keys(filterJson).includes(`${value}`)) {
|
||||
return [false, t('views.mmlManage.requireEnum', { display })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'bool':
|
||||
if (filter && filter.indexOf('{') === 1) {
|
||||
let filterJson: Record<string, any> = {};
|
||||
try {
|
||||
filterJson = JSON.parse(filter); //string---json
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (!Object.values(filterJson).includes(`${value}`)) {
|
||||
return [false, t('views.mmlManage.requireBool', { display })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
if (filter && filter.indexOf('~') !== -1) {
|
||||
try {
|
||||
const filterArr = filter.split('~');
|
||||
let rule = new RegExp(
|
||||
'^\\S{' + filterArr[0] + ',' + filterArr[1] + '}$'
|
||||
);
|
||||
if (!rule.test(value)) {
|
||||
return [false, t('views.mmlManage.requireString', { display })];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'regex':
|
||||
if (filter) {
|
||||
try {
|
||||
let regex = new RegExp(filter);
|
||||
if (!regex.test(value)) {
|
||||
return [false, t('views.mmlManage.requireString', { display })];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'file':
|
||||
if (filter) {
|
||||
const arr: string[] = filter.split(',');
|
||||
const itemArr = arr.filter(item => value.endsWith(item));
|
||||
if (itemArr.length === 0) {
|
||||
return [false, t('views.mmlManage.requireFile', { display })];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(t('views.mmlManage.requireUn', { display }), type);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**查询可选命令列表 */
|
||||
function fnGetList() {
|
||||
getMMLByUDM().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
// 构建自动完成筛选结构
|
||||
const autoCompleteArr: Record<string, any>[] = [];
|
||||
// 构建树结构
|
||||
const treeArr: Record<string, any>[] = [];
|
||||
for (const item of res.data.rows) {
|
||||
const id = item['id'];
|
||||
const object = item['object'];
|
||||
const operation = item['operation'];
|
||||
const mmlDisplay = item['mmlDisplay'];
|
||||
// 可选属性参数
|
||||
let param = [];
|
||||
try {
|
||||
param = JSON.parse(item['paramJson']);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
// 遍历检查大类
|
||||
const treeItemIndex = treeArr.findIndex(i => i.key == item['category']);
|
||||
if (treeItemIndex < 0) {
|
||||
treeArr.push({
|
||||
title: item['catDisplay'],
|
||||
key: item['category'],
|
||||
selectable: false,
|
||||
children: [
|
||||
{ key: id, title: mmlDisplay, object, operation, param },
|
||||
],
|
||||
});
|
||||
autoCompleteArr.push({
|
||||
value: item['catDisplay'],
|
||||
key: item['category'],
|
||||
selectable: false,
|
||||
options: [{ key: id, value: mmlDisplay, object, operation, param }],
|
||||
});
|
||||
} else {
|
||||
treeArr[treeItemIndex].children.push({
|
||||
key: id,
|
||||
title: mmlDisplay,
|
||||
object,
|
||||
operation,
|
||||
param,
|
||||
});
|
||||
autoCompleteArr[treeItemIndex].options.push({
|
||||
key: id,
|
||||
value: mmlDisplay,
|
||||
object,
|
||||
operation,
|
||||
param,
|
||||
});
|
||||
}
|
||||
}
|
||||
state.mmlTreeData = treeArr;
|
||||
state.autoCompleteData = autoCompleteArr;
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('views.mmlManage.cmdNoTip', { num: 'UDM' }),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**自动完成搜索匹配前缀 */
|
||||
function fnAutoCompleteSearch(value: string) {
|
||||
state.autoCompleteSearch = [];
|
||||
for (const item of state.autoCompleteData) {
|
||||
const filterOptions = item.options.filter((s: any) => {
|
||||
return `${s.operation} ${s.object}`.indexOf(value.toLowerCase()) >= 0;
|
||||
});
|
||||
if (filterOptions.length > 0) {
|
||||
state.autoCompleteSearch.push({
|
||||
value: item.value,
|
||||
key: item.value,
|
||||
selectable: false,
|
||||
options: filterOptions,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**自动完成搜索选择 */
|
||||
function fnAutoCompleteSelect(_: any, option: any) {
|
||||
if (Object.keys(option).length === 0) {
|
||||
return;
|
||||
}
|
||||
state.mmlSelect = {
|
||||
title: option.value,
|
||||
key: option.key,
|
||||
operation: option.operation,
|
||||
object: option.object,
|
||||
param: option.param,
|
||||
};
|
||||
state.from = {};
|
||||
state.autoCompleteValue = `${option.operation} ${option.object}`.trim();
|
||||
}
|
||||
|
||||
/**自动完成搜索选择 */
|
||||
function fnAutoCompleteChange(value: any, _: any) {
|
||||
if (value.indexOf(';') !== -1 || value.indexOf('\n') !== -1) {
|
||||
fnCleanFrom();
|
||||
return;
|
||||
}
|
||||
for (const item of state.autoCompleteData) {
|
||||
const findItem = item.options.find((s: any) => {
|
||||
const prefix = `${s.operation} ${s.object}`;
|
||||
return value.startsWith(prefix);
|
||||
});
|
||||
if (findItem) {
|
||||
state.mmlSelect = {
|
||||
title: findItem.value,
|
||||
key: findItem.key,
|
||||
operation: findItem.operation,
|
||||
object: findItem.object,
|
||||
param: findItem.param,
|
||||
};
|
||||
state.from = {};
|
||||
// 截取拆分赋值
|
||||
const prefix = `${findItem.operation} ${findItem.object}:`;
|
||||
const argsStr = value.replace(prefix, '');
|
||||
if (argsStr.length > 3) {
|
||||
const argsArr = argsStr.split(',');
|
||||
for (const arg of argsArr) {
|
||||
const kvArr = arg.split('=');
|
||||
if (kvArr.length >= 2) {
|
||||
state.from[kvArr[0]] = kvArr[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
state.mmlSelect = {
|
||||
title: '',
|
||||
key: '',
|
||||
operation: '',
|
||||
object: '',
|
||||
param: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**自动完成按键触发 */
|
||||
function fnAutoCompleteKeydown(evt: any) {
|
||||
if (evt.key === 'Enter') {
|
||||
// 阻止默认的换行行为
|
||||
evt.preventDefault();
|
||||
// 按下 Shift + Enter 键时换行
|
||||
if (evt.shiftKey) {
|
||||
// 插入换行符
|
||||
const textarea = evt.target;
|
||||
const start = textarea.selectionStart;
|
||||
const end = textarea.selectionEnd;
|
||||
const text = textarea.value;
|
||||
textarea.value = text.substring(0, start) + '\n' + text.substring(end);
|
||||
state.autoCompleteValue = textarea.value;
|
||||
// 更新光标位置
|
||||
textarea.selectionStart = textarea.selectionEnd = start + 1;
|
||||
} else {
|
||||
fnSendMML();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
const coreUid = coreStore.currentCoreUid;
|
||||
const coreNeList = neStore.coreDataNeCascaderOptions.get(coreUid) || [];
|
||||
coreNeList.forEach(item => {
|
||||
if (item.value === 'UDM') {
|
||||
neOptions.value = JSON.parse(JSON.stringify(item.children));
|
||||
}
|
||||
});
|
||||
if (neOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (neOptions.value.length > 0) {
|
||||
state.neUid = neOptions.value[0].value;
|
||||
}
|
||||
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="6" :xs="24" style="margin-bottom: 24px">
|
||||
<!-- 命令导航 -->
|
||||
<a-card
|
||||
size="small"
|
||||
:bordered="false"
|
||||
:title="t('views.mmlManage.cmdTitle')"
|
||||
>
|
||||
<a-form layout="vertical" autocomplete="off">
|
||||
<a-form-item name="neUid ">
|
||||
<a-select
|
||||
v-model:value="state.neUid"
|
||||
:options="neOptions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item name="mmlTree" v-if="state.mmlTreeData.length > 0">
|
||||
<a-directory-tree
|
||||
:tree-data="state.mmlTreeData"
|
||||
default-expand-all
|
||||
@select="fnTreeSelect"
|
||||
:selectedKeys="[state.mmlSelect.key]"
|
||||
></a-directory-tree>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col :lg="18" :md="18" :xs="24">
|
||||
<!-- 命令参数输入 -->
|
||||
<a-card size="small" :bordered="false">
|
||||
<template #title>
|
||||
<a-typography-text strong v-if="state.mmlSelect.title">
|
||||
{{ state.mmlSelect.title }}
|
||||
</a-typography-text>
|
||||
<a-typography-text type="danger" v-else>
|
||||
{{ t('views.mmlManage.cmdOpTip') }}
|
||||
</a-typography-text>
|
||||
</template>
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8">
|
||||
<a-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click.prevent="fnCleanFrom"
|
||||
v-if="!!state.mmlSelect.param"
|
||||
>
|
||||
<template #icon>
|
||||
<ClearOutlined />
|
||||
</template>
|
||||
{{ t('views.mmlManage.clearForm') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:loading="state.from.sendLoading"
|
||||
@click.prevent="fnSendMML"
|
||||
>
|
||||
<template #icon>
|
||||
<SendOutlined />
|
||||
</template>
|
||||
{{ t('views.mmlManage.exec') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<a-form
|
||||
layout="horizontal"
|
||||
autocomplete="off"
|
||||
:validate-on-rule-change="false"
|
||||
:validateTrigger="[]"
|
||||
>
|
||||
<a-form-item
|
||||
:label="t('views.mmlManage.cmdQuickEntry')"
|
||||
:help="t('views.mmlManage.cmdQuickEntryHelp')"
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="state.autoCompleteValue"
|
||||
:dropdown-match-select-width="500"
|
||||
style="width: 100%"
|
||||
:options="state.autoCompleteSearch"
|
||||
@search="fnAutoCompleteSearch"
|
||||
@select="fnAutoCompleteSelect"
|
||||
@change="fnAutoCompleteChange"
|
||||
@keydown="fnAutoCompleteKeydown"
|
||||
>
|
||||
<a-textarea :placeholder="t('common.inputPlease')" auto-size />
|
||||
</a-auto-complete>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<template v-if="state.mmlSelect.operation && state.mmlSelect.param">
|
||||
<a-form
|
||||
layout="vertical"
|
||||
autocomplete="off"
|
||||
:validate-on-rule-change="false"
|
||||
:validateTrigger="[]"
|
||||
>
|
||||
<a-divider orientation="left">
|
||||
{{ t('views.mmlManage.cmdParamPanel') }}
|
||||
</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
:lg="6"
|
||||
:md="12"
|
||||
:xs="24"
|
||||
v-for="item in state.mmlSelect.param"
|
||||
>
|
||||
<a-form-item
|
||||
:label="item.display"
|
||||
:name="item.name"
|
||||
:required="item.optional === 'false'"
|
||||
>
|
||||
<a-tooltip>
|
||||
<template #title v-if="item.comment">
|
||||
{{ item.comment }}
|
||||
</template>
|
||||
|
||||
<a-input-number
|
||||
v-if="item.type === 'int'"
|
||||
v-model:value="state.from[item.name]"
|
||||
style="width: 100%"
|
||||
></a-input-number>
|
||||
<a-switch
|
||||
v-else-if="item.type === 'bool'"
|
||||
v-model:checked="state.from[item.name]"
|
||||
:checked-children="t('common.switch.open')"
|
||||
:un-checked-children="t('common.switch.shut')"
|
||||
></a-switch>
|
||||
<a-select
|
||||
v-else-if="item.type === 'enum'"
|
||||
v-model:value="state.from[item.name]"
|
||||
:allow-clear="item.optional === 'true'"
|
||||
>
|
||||
<a-select-option
|
||||
:value="v"
|
||||
:key="v"
|
||||
v-for="(k, v) in JSON.parse(item.filter)"
|
||||
>
|
||||
{{ k }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-input-group compact v-else-if="item.type === 'file'">
|
||||
<a-input
|
||||
v-model:value="state.from[item.name]"
|
||||
style="width: calc(100% - 32px)"
|
||||
/>
|
||||
<a-upload
|
||||
name="file"
|
||||
list-type="text"
|
||||
:accept="item.filter"
|
||||
:max-count="1"
|
||||
:show-upload-list="false"
|
||||
:custom-request="(v:any) => fnUpload(v, item.name)"
|
||||
>
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="state.from.uploadLoading"
|
||||
>
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-input-group>
|
||||
<a-input
|
||||
v-else
|
||||
v-model:value="state.from[item.name]"
|
||||
></a-input>
|
||||
</a-tooltip>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
</a-card>
|
||||
|
||||
<!-- 命令展示 -->
|
||||
<a-card
|
||||
:title="t('views.mmlManage.cmdConsole')"
|
||||
:bordered="false"
|
||||
size="small"
|
||||
:body-style="{ padding: 0 }"
|
||||
style="margin-top: 16px"
|
||||
v-show="state.mmlCmdLog"
|
||||
>
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="default"
|
||||
size="small"
|
||||
@click.prevent="fnCleanCmdLog"
|
||||
>
|
||||
<template #icon>
|
||||
<ClearOutlined />
|
||||
</template>
|
||||
{{ t('views.mmlManage.clearLog') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<CodemirrorEdite
|
||||
:value="state.mmlCmdLog"
|
||||
:disabled="true"
|
||||
height="500px"
|
||||
></CodemirrorEdite>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/tool/signal/packet/index.vue
Normal file
16
src/views/core-ne/tool/signal/packet/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
479
src/views/core-ne/tool/signal/tcpdump/file.vue
Normal file
479
src/views/core-ne/tool/signal/tcpdump/file.vue
Normal file
@@ -0,0 +1,479 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
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';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
import useTabsStore from '@/store/modules/tabs';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import saveAs from 'file-saver';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
const tabsStore = useTabsStore();
|
||||
const neStore = useNeStore();
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 获取地址栏参数
|
||||
const routeParams = route.query as Record<string, any>;
|
||||
|
||||
/**网元参数 */
|
||||
let neTypeSelect = ref<string[]>([]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neType: '',
|
||||
neUid: '',
|
||||
/**读取路径 */
|
||||
path: '',
|
||||
/**前缀过滤 */
|
||||
search: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'small',
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = reactive([
|
||||
{
|
||||
title: t('views.logManage.neFile.fileMode'),
|
||||
dataIndex: 'fileMode',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.size'),
|
||||
dataIndex: 'size',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return parseSizeFromFile(opt.value);
|
||||
},
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.modifiedTime'),
|
||||
dataIndex: 'modifiedTime',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value * 1000);
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.logManage.neFile.fileName'),
|
||||
dataIndex: 'fileName',
|
||||
align: 'left',
|
||||
resizable: true,
|
||||
width: 200,
|
||||
minWidth: 100,
|
||||
maxWidth: 350,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'fileName',
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**下载触发等待 */
|
||||
let downLoading = ref<boolean>(false);
|
||||
|
||||
/**信息文件下载 */
|
||||
function fnDownloadFile(row: Record<string, any>) {
|
||||
if (downLoading.value) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.logManage.neFile.downTip', { fileName: row.fileName }),
|
||||
onOk() {
|
||||
downLoading.value = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
getNeFile({
|
||||
neType: queryParams.neType,
|
||||
neUid: queryParams.neUid,
|
||||
path: queryParams.path,
|
||||
fileName: row.fileName,
|
||||
delTemp: true,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('common.downloadText'),
|
||||
}),
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `${row.fileName}`);
|
||||
} else {
|
||||
message.error({
|
||||
content: t('views.logManage.neFile.downTipErr'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
downLoading.value = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**信息文件下载 */
|
||||
function fnDownloadFileZIP(row: Record<string, any>) {
|
||||
if (downLoading.value) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.logManage.neFile.downTipZip', { fileName: row.fileName }),
|
||||
onOk() {
|
||||
downLoading.value = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
getNeDirZip({
|
||||
neType: queryParams.neType,
|
||||
neUid: queryParams.neUid,
|
||||
path: `${queryParams.path}/${row.fileName}`,
|
||||
delTemp: true,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('common.downloadText'),
|
||||
}),
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `${row.fileName}.zip`);
|
||||
} else {
|
||||
message.error({
|
||||
content: t('views.logManage.neFile.downTipErr'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
downLoading.value = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**tmp目录下,UPF标准版内部输出目录 */
|
||||
let tmp = ref<boolean>(false);
|
||||
|
||||
/**UPF标准版内部抓包的输出目录 */
|
||||
function fnUPFTmp() {
|
||||
fnDirCD('/tmp', 0);
|
||||
}
|
||||
|
||||
/**关闭跳转 */
|
||||
function fnClose() {
|
||||
const to = tabsStore.tabClose(route.path);
|
||||
if (to) {
|
||||
router.push(to);
|
||||
} else {
|
||||
router.back();
|
||||
}
|
||||
}
|
||||
|
||||
/**访问路径 */
|
||||
let nePathArr = ref<string[]>([]);
|
||||
|
||||
/**进入目录 */
|
||||
function fnDirCD(dir: string, index?: number) {
|
||||
if (index === undefined) {
|
||||
nePathArr.value.push(dir);
|
||||
queryParams.search = '';
|
||||
fnGetList(1);
|
||||
return;
|
||||
}
|
||||
if (index === 0) {
|
||||
const neType = queryParams.neType;
|
||||
if (neType === 'UPF' && tmp.value) {
|
||||
nePathArr.value = ['/tmp'];
|
||||
queryParams.search = `${neType}_${queryParams.neUid}`;
|
||||
} else {
|
||||
nePathArr.value = [
|
||||
`/usr/local/omc/tcpdump/${neType.toLowerCase()}/${queryParams.neUid}`,
|
||||
];
|
||||
queryParams.search = '';
|
||||
}
|
||||
|
||||
fnGetList(1);
|
||||
} else {
|
||||
nePathArr.value = nePathArr.value.slice(0, index + 1);
|
||||
queryParams.search = '';
|
||||
fnGetList(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**网元类型选择对应修改 */
|
||||
function fnNeChange(keys: any, _: any) {
|
||||
if (!Array.isArray(keys)) return;
|
||||
const neType = keys[0];
|
||||
const neUid = keys[1];
|
||||
// 不是同类型时需要重新加载
|
||||
if (queryParams.neType !== neType || queryParams.neUid !== neUid) {
|
||||
queryParams.neType = neType;
|
||||
queryParams.neUid = neUid;
|
||||
if (neType === 'UPF' && tmp.value) {
|
||||
nePathArr.value = ['/tmp'];
|
||||
queryParams.search = `${neType}_${neUid}`;
|
||||
} else {
|
||||
nePathArr.value = [
|
||||
`/usr/local/omc/tcpdump/${neType.toLowerCase()}/${neUid}`,
|
||||
];
|
||||
queryParams.search = '';
|
||||
}
|
||||
fnGetList(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (queryParams.neUid === '') {
|
||||
message.warning({
|
||||
content: t('views.logManage.neFile.neTypePlease'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
queryParams.path = nePathArr.value.join('/');
|
||||
listNeFiles(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
tablePagination.total = 0;
|
||||
tableState.data = [];
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**抽屉状态 */
|
||||
const viewDrawerState = reactive({
|
||||
open: false,
|
||||
/**文件路径 /var/log/amf.log */
|
||||
filePath: '',
|
||||
/**网元类型 */
|
||||
neType: '',
|
||||
/**网元ID */
|
||||
neUid: '',
|
||||
});
|
||||
|
||||
/**打开抽屉查看 */
|
||||
function fnDrawerOpen(row: Record<string, any>) {
|
||||
viewDrawerState.filePath = [...nePathArr.value, row.fileName].join('/');
|
||||
viewDrawerState.neType = neTypeSelect.value[0];
|
||||
viewDrawerState.neUid = neTypeSelect.value[1];
|
||||
viewDrawerState.open = !viewDrawerState.open;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (routeParams.neType && routeParams.neUid) {
|
||||
neTypeSelect.value = [routeParams.neType, routeParams.neUid];
|
||||
fnNeChange(neTypeSelect.value, undefined);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-row :gutter="16" :wrap="true" align="middle">
|
||||
<a-col>
|
||||
<a-button type="default" @click.prevent="fnClose()">
|
||||
<template #icon><CloseOutlined /></template>
|
||||
{{ t('common.close') }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
<a-col>
|
||||
<span>{{ t('views.logManage.neFile.neType') }}:</span>
|
||||
<a-cascader
|
||||
v-model:value="neTypeSelect"
|
||||
:options="neStore.getNeCascaderOptions"
|
||||
@change="fnNeChange"
|
||||
:allow-clear="false"
|
||||
:placeholder="t('views.logManage.neFile.neTypePlease')"
|
||||
:disabled="downLoading || tableState.loading"
|
||||
/>
|
||||
</a-col>
|
||||
<template v-if="nePathArr.length > 0">
|
||||
<span>{{ t('views.logManage.neFile.nePath') }}:</span>
|
||||
<a-col>
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item
|
||||
v-for="(path, index) in nePathArr"
|
||||
:key="path"
|
||||
@click="fnDirCD(path, index)"
|
||||
>
|
||||
{{ path }}
|
||||
</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
</a-col>
|
||||
</template>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight" v-if="neTypeSelect[0] === 'UPF'">
|
||||
<template #title>
|
||||
{{ t('views.traceManage.pcap.fileUPFTip') }}
|
||||
</template>
|
||||
<a-checkbox v-model:checked="tmp" @change="fnUPFTmp()">
|
||||
{{ t('views.traceManage.pcap.fileUPF') }}
|
||||
</a-checkbox>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="fileName"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: 800 }"
|
||||
@resizeColumn="(w:number, col:any) => (col.width = w)"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'fileName'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="downLoading"
|
||||
@click.prevent="fnDownloadFile(record)"
|
||||
v-if="record.fileType === 'file'"
|
||||
>
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
{{ t('common.downloadText') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnDrawerOpen(record)"
|
||||
v-if="
|
||||
record.fileType === 'file' && record.fileName.endsWith('.log')
|
||||
"
|
||||
>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
{{ t('common.viewText') }}
|
||||
</a-button>
|
||||
|
||||
<template v-if="record.fileType === 'dir'">
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="downLoading"
|
||||
@click.prevent="fnDownloadFileZIP(record)"
|
||||
>
|
||||
<template #icon><DownloadOutlined /></template>
|
||||
{{ t('common.downloadText') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="link"
|
||||
:loading="downLoading"
|
||||
@click.prevent="fnDirCD(record.fileName)"
|
||||
>
|
||||
<template #icon><FolderOutlined /></template>
|
||||
{{ t('views.logManage.neFile.dirCd') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 文件内容查看抽屉 -->
|
||||
<ViewDrawer
|
||||
v-model:open="viewDrawerState.open"
|
||||
:file-path="viewDrawerState.filePath"
|
||||
:ne-type="viewDrawerState.neType"
|
||||
:ne-uid="viewDrawerState.neUid"
|
||||
></ViewDrawer>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
811
src/views/core-ne/tool/signal/tcpdump/index.vue
Normal file
811
src/views/core-ne/tool/signal/tcpdump/index.vue
Normal file
@@ -0,0 +1,811 @@
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import { dumpStart, dumpStop, traceUPF } from '@/api/trace/pcap';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { getNeDirZip, getNeFile, getNeViewFile } from '@/api/tool/neFile';
|
||||
import saveAs from 'file-saver';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { MENU_PATH_INLINE } from '@/constants/menu-constants';
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**表单数据 */
|
||||
from: Record<
|
||||
string,
|
||||
{
|
||||
loading: boolean;
|
||||
/**网元名 */
|
||||
title: string;
|
||||
/**命令 */
|
||||
cmdStart: string;
|
||||
/**upf标准版需要停止命令,一般空字符 */
|
||||
cmdStop: string;
|
||||
/**任务编号 */
|
||||
taskCode: string;
|
||||
/**任务日志,upf标准版为空字符串 */
|
||||
taskFiles: string[];
|
||||
/**提交表单参数 */
|
||||
data: {
|
||||
neType: string;
|
||||
neUid: string;
|
||||
cmd?: string;
|
||||
};
|
||||
}
|
||||
>;
|
||||
/**tcpdump命令组 */
|
||||
cmdOptions: {
|
||||
/**命令名称 */
|
||||
label: string;
|
||||
/**命令选中值 */
|
||||
value: string;
|
||||
/**开始命令 */
|
||||
start: string;
|
||||
/**停止命令 */
|
||||
stop: string;
|
||||
}[];
|
||||
/**UPF命令组 */
|
||||
cmdOptionsUPF: {
|
||||
/**命令名称 */
|
||||
label: string;
|
||||
/**命令选中值 */
|
||||
value: string;
|
||||
/**开始命令 */
|
||||
start: string;
|
||||
/**停止命令 */
|
||||
stop: string;
|
||||
}[];
|
||||
/**详情框是否显示 */
|
||||
openByView: boolean;
|
||||
/**详情框内容 */
|
||||
viewFrom: {
|
||||
neType: string;
|
||||
neUid: string;
|
||||
path: string;
|
||||
action: string;
|
||||
files: string[];
|
||||
content: string;
|
||||
};
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
from: {},
|
||||
cmdOptions: [
|
||||
{
|
||||
label: t('views.traceManage.pcap.execCmd'),
|
||||
value: 'any',
|
||||
start: '-n -v -s 0',
|
||||
stop: '',
|
||||
},
|
||||
{
|
||||
label: t('views.traceManage.pcap.execCmd2'),
|
||||
value: 'any2',
|
||||
start: 'sctp or tcp port 8088 or 33030',
|
||||
stop: '',
|
||||
},
|
||||
{
|
||||
label: t('views.traceManage.pcap.execCmd3'),
|
||||
value: 'any3',
|
||||
start: '-n -s 0 -v -G 10 -W 7',
|
||||
stop: '',
|
||||
},
|
||||
],
|
||||
cmdOptionsUPF: [
|
||||
{
|
||||
label: t('views.traceManage.pcap.execUPFCmdA'),
|
||||
value: 'pcap trace',
|
||||
start: 'pcap trace rx tx max 100000 intfc any',
|
||||
stop: 'pcap trace rx tx off',
|
||||
},
|
||||
{
|
||||
label: t('views.traceManage.pcap.execUPFCmdB'),
|
||||
value: 'pcap dispatch',
|
||||
start: 'pcap dispatch trace on max 100000',
|
||||
stop: 'pcap dispatch trace off',
|
||||
},
|
||||
],
|
||||
openByView: false,
|
||||
viewFrom: {
|
||||
neType: '',
|
||||
neUid: '',
|
||||
path: '',
|
||||
action: '',
|
||||
files: [],
|
||||
content: '',
|
||||
},
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.ne.common.neType'),
|
||||
dataIndex: 'neType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.neName'),
|
||||
dataIndex: 'neName',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.ipAddr'),
|
||||
dataIndex: 'ipAddr',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.traceManage.pcap.cmd'),
|
||||
key: 'cmd',
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
width: 350,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**查询全部网元数据列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
listAllNeInfo({
|
||||
bandStatus: false,
|
||||
}).then(res => {
|
||||
if (
|
||||
res.code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(res.data) &&
|
||||
res.data.length > 0
|
||||
) {
|
||||
tableState.data = res.data;
|
||||
// 初始网元参数表单
|
||||
if (tableState.data.length > 0) {
|
||||
const { start, stop } = modalState.cmdOptions[0];
|
||||
for (const item of res.data) {
|
||||
modalState.from[item.id] = {
|
||||
loading: false,
|
||||
title: item.neName,
|
||||
cmdStart: start,
|
||||
cmdStop: stop,
|
||||
taskCode: '',
|
||||
taskFiles: [],
|
||||
data: {
|
||||
neType: item.neType,
|
||||
neUid: item.neUid,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**抓包命令选择 */
|
||||
function fnSelectCmd(id: any, option: any) {
|
||||
modalState.from[id].cmdStart = option.start;
|
||||
modalState.from[id].cmdStop = option.stop;
|
||||
// 重置任务
|
||||
modalState.from[id].taskCode = '';
|
||||
modalState.from[id].taskFiles = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始
|
||||
* @param row 行记录
|
||||
*/
|
||||
function fnRecordStart(row?: Record<string, any>) {
|
||||
let neIDs: string[] = [];
|
||||
if (row) {
|
||||
neIDs = [`${row.id}`];
|
||||
} else {
|
||||
row = {
|
||||
neName: t('views.traceManage.pcap.textSelect'),
|
||||
};
|
||||
neIDs = tableState.selectedRowKeys.map(s => `${s}`);
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.traceManage.pcap.startTip', { title: row.neName }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const fromArr = neIDs.map(id => modalState.from[id]);
|
||||
const reqArr = fromArr.map(from => {
|
||||
from.loading = true;
|
||||
const data = Object.assign({ cmd: from.cmdStart }, from.data);
|
||||
if (from.data.neType === 'UPF' && from.cmdStart.startsWith('pcap')) {
|
||||
return traceUPF(data);
|
||||
}
|
||||
return dumpStart(data);
|
||||
});
|
||||
Promise.allSettled(reqArr)
|
||||
.then(resArr => {
|
||||
resArr.forEach((res, idx) => {
|
||||
const title = fromArr[idx].title;
|
||||
if (res.status === 'fulfilled') {
|
||||
const resV = res.value;
|
||||
if (resV.code === RESULT_CODE_SUCCESS) {
|
||||
if (!fromArr[idx].cmdStop) {
|
||||
fromArr[idx].taskCode = resV.data;
|
||||
}
|
||||
fromArr[idx].loading = true;
|
||||
message.success({
|
||||
content: t('views.traceManage.pcap.startOk', { title }),
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
fromArr[idx].loading = false;
|
||||
message.warning({
|
||||
content: `${resV.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
fromArr[idx].loading = false;
|
||||
message.error({
|
||||
content: t('views.traceManage.pcap.startErr', { title }),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止
|
||||
* @param row 行记录
|
||||
*/
|
||||
function fnRecordStop(row?: Record<string, any>) {
|
||||
let neIDs: string[] = [];
|
||||
if (row) {
|
||||
neIDs = [`${row.id}`];
|
||||
} else {
|
||||
row = {
|
||||
neName: t('views.traceManage.pcap.textSelect'),
|
||||
};
|
||||
neIDs = tableState.selectedRowKeys.map(s => `${s}`);
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.traceManage.pcap.stopTip', { title: row.neName }),
|
||||
onOk() {
|
||||
const fromArr = neIDs.map(id => modalState.from[id]);
|
||||
const reqArr: any = [];
|
||||
for (const from of fromArr) {
|
||||
if (from.data.neType === 'UPF' && from.cmdStart.startsWith('pcap')) {
|
||||
reqArr.push(
|
||||
traceUPF(Object.assign({ cmd: from.cmdStop }, from.data))
|
||||
);
|
||||
} else {
|
||||
const taskCode = from.taskCode;
|
||||
if (!taskCode) {
|
||||
message.warning({
|
||||
content: t('views.traceManage.pcap.stopNotRun', {
|
||||
title: from.title,
|
||||
}),
|
||||
duration: 3,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
reqArr.push(
|
||||
dumpStop(Object.assign({ taskCode: from.taskCode }, from.data))
|
||||
);
|
||||
}
|
||||
}
|
||||
if (reqArr.length === 0) return;
|
||||
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
Promise.allSettled(reqArr)
|
||||
.then(resArr => {
|
||||
resArr.forEach((res, idx) => {
|
||||
const title = fromArr[idx].title;
|
||||
|
||||
if (res.status === 'fulfilled') {
|
||||
const resV = res.value;
|
||||
fromArr[idx].loading = false;
|
||||
fromArr[idx].taskFiles = [];
|
||||
if (fromArr[idx].cmdStop) {
|
||||
fromArr[idx].taskCode = '';
|
||||
}
|
||||
|
||||
if (resV.code === RESULT_CODE_SUCCESS) {
|
||||
if (fromArr[idx].cmdStop) {
|
||||
fromArr[idx].taskCode = resV.data;
|
||||
} else {
|
||||
fromArr[idx].taskFiles = resV.data;
|
||||
}
|
||||
message.success({
|
||||
content: t('views.traceManage.pcap.stopOk', { title }),
|
||||
duration: 3,
|
||||
});
|
||||
} else if (resV.msg.indexOf('not run') > 0) {
|
||||
message.warning({
|
||||
content: t('views.traceManage.pcap.stopNotRun', { title }),
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
message.warning({
|
||||
content: `${resV.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
message.error({
|
||||
content: t('views.traceManage.pcap.stopErr', { title }),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**下载PCAP文件 */
|
||||
function fnDownPCAP(row?: Record<string, any>) {
|
||||
let neIDs: string[] = [];
|
||||
if (row) {
|
||||
neIDs = [`${row.id}`];
|
||||
} else {
|
||||
row = {
|
||||
neName: t('views.traceManage.pcap.textSelect'),
|
||||
};
|
||||
neIDs = tableState.selectedRowKeys.map(s => `${s}`);
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.traceManage.pcap.downTip', { title: row.neName }),
|
||||
onOk() {
|
||||
const fromArr = neIDs.map(id => modalState.from[id]);
|
||||
const reqArr: any = [];
|
||||
for (const from of fromArr) {
|
||||
const taskCode = from.taskCode;
|
||||
if (!taskCode) {
|
||||
message.warning({
|
||||
content: t('views.traceManage.pcap.stopNotRun', {
|
||||
title: from.title,
|
||||
}),
|
||||
duration: 3,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (from.data.neType === 'UPF' && taskCode.startsWith('/tmp')) {
|
||||
const fileName = taskCode.substring(taskCode.lastIndexOf('/') + 1);
|
||||
reqArr.push(
|
||||
getNeFile(
|
||||
Object.assign(
|
||||
{ path: '/tmp', fileName, delTemp: true },
|
||||
from.data
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const { neType, neUid } = from.data;
|
||||
const path = `/usr/local/omc/tcpdump/${neType.toLowerCase()}/${neUid}/${taskCode}`;
|
||||
reqArr.push(
|
||||
getNeDirZip({
|
||||
neType,
|
||||
neUid,
|
||||
path,
|
||||
delTemp: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
if (reqArr.length === 0) return;
|
||||
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
Promise.allSettled(reqArr)
|
||||
.then(resArr => {
|
||||
type successType = { data: Blob; filename: string }[];
|
||||
const successResults: successType = [];
|
||||
|
||||
resArr.forEach((res, idx) => {
|
||||
const title = fromArr[idx].title;
|
||||
const taskCode = fromArr[idx].taskCode;
|
||||
|
||||
if (res.status === 'fulfilled') {
|
||||
const resV = res.value;
|
||||
if (resV.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.traceManage.pcap.downOk', { title }),
|
||||
duration: 3,
|
||||
});
|
||||
// 文件名
|
||||
let filename = `${title}_${Date.now()}.zip`;
|
||||
if (taskCode.startsWith('/tmp')) {
|
||||
filename = `${title}_${Date.now()}.pcap`;
|
||||
}
|
||||
successResults.push({
|
||||
data: resV.data,
|
||||
filename: filename,
|
||||
});
|
||||
} else {
|
||||
message.warning({
|
||||
content: `${resV.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
message.error({
|
||||
content: t('views.traceManage.pcap.downErr', { title }),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 分批下载函数,每批3个,间隔300ms
|
||||
const batchDownload = async (results: successType) => {
|
||||
const batchSize = 3;
|
||||
const delay = 1_000; // 毫秒
|
||||
|
||||
for (let i = 0; i < results.length; i += batchSize) {
|
||||
const batch = results.slice(i, i + batchSize);
|
||||
|
||||
// 处理当前批次
|
||||
batch.forEach(item => {
|
||||
saveAs(item.data, item.filename);
|
||||
});
|
||||
|
||||
await new Promise(resolve =>
|
||||
setTimeout(() => resolve(true), delay)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 开始分批下载
|
||||
batchDownload(successResults);
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**批量操作 */
|
||||
function fnBatchOper(key: string) {
|
||||
switch (key) {
|
||||
case 'start':
|
||||
fnRecordStart();
|
||||
break;
|
||||
case 'stop':
|
||||
fnRecordStop();
|
||||
break;
|
||||
case 'down':
|
||||
fnDownPCAP();
|
||||
break;
|
||||
default:
|
||||
console.warn('undefined batch oper', key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 查看
|
||||
* @param dictId 编号id
|
||||
*/
|
||||
function fnModalVisibleByVive(id: string | number) {
|
||||
const from = modalState.from[id];
|
||||
if (!from) return;
|
||||
const { neType, neUid } = from.data;
|
||||
const path = `/usr/local/omc/tcpdump/${neType.toLowerCase()}/${neUid}/${
|
||||
from.taskCode
|
||||
}`;
|
||||
const files = from.taskFiles.filter(f => f.endsWith('log'));
|
||||
modalState.viewFrom.neType = neType;
|
||||
modalState.viewFrom.neUid = neUid;
|
||||
modalState.viewFrom.path = path;
|
||||
modalState.viewFrom.files = [...files];
|
||||
fnViveTab(files[0]);
|
||||
modalState.openByView = true;
|
||||
}
|
||||
|
||||
/**对话框tab查看 */
|
||||
function fnViveTab(action: any) {
|
||||
// console.log('fnViveTab', action);
|
||||
if (modalState.viewFrom.action === action) return;
|
||||
modalState.viewFrom.action = action;
|
||||
modalState.viewFrom.content = '';
|
||||
const { neType, neUid, path } = modalState.viewFrom;
|
||||
getNeViewFile({
|
||||
neUid,
|
||||
neType,
|
||||
path,
|
||||
fileName: action,
|
||||
}).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
modalState.viewFrom.content = res.data;
|
||||
} else {
|
||||
modalState.viewFrom.content = '';
|
||||
message.warning({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.openByView = false;
|
||||
modalState.viewFrom.action = '';
|
||||
modalState.viewFrom.files = [];
|
||||
}
|
||||
|
||||
/**跳转文件数据页面 */
|
||||
function fnFileView(row?: Record<string, any>) {
|
||||
let query = undefined;
|
||||
if (row) {
|
||||
const from = modalState.from[row.id];
|
||||
query = {
|
||||
neUid: from.data.neUid,
|
||||
neType: from.data.neType,
|
||||
};
|
||||
}
|
||||
router.push({
|
||||
path: `${route.path}${MENU_PATH_INLINE}/file`,
|
||||
query: query,
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元列表
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button @click="fnFileView()">
|
||||
<FileSearchOutlined />
|
||||
{{ t('views.traceManage.pcap.fileView') }}
|
||||
</a-button>
|
||||
|
||||
<a-dropdown trigger="click">
|
||||
<a-button :disabled="tableState.selectedRowKeys.length <= 0">
|
||||
{{ t('views.traceManage.pcap.batchOper') }}
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key }:any) => fnBatchOper(key)">
|
||||
<a-menu-item key="start">
|
||||
<PlayCircleOutlined />
|
||||
{{ t('views.traceManage.pcap.batchStartText') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="stop">
|
||||
<StopOutlined />
|
||||
{{ t('views.traceManage.pcap.batchStopText') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="down">
|
||||
<DownloadOutlined />
|
||||
{{ t('views.traceManage.pcap.batchDownText') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
size="small"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:pagination="false"
|
||||
:scroll="{ x: tableColumns.length * 170 }"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'cmd'">
|
||||
<a-auto-complete
|
||||
v-model:value="modalState.from[record.id].cmdStart"
|
||||
:options="
|
||||
record.neType === 'UPF'
|
||||
? modalState.cmdOptions.concat(modalState.cmdOptionsUPF)
|
||||
: modalState.cmdOptions
|
||||
"
|
||||
:placeholder="t('views.traceManage.pcap.capArgPlease')"
|
||||
:disabled="modalState.from[record.id].loading"
|
||||
allow-clear
|
||||
@select="(_: any, option: any) => fnSelectCmd(record.id, option)"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="start" direction="horizontal">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.pcap.textStart') }}</div>
|
||||
</template>
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
:disabled="modalState.from[record.id].loading"
|
||||
@click.prevent="fnRecordStart(record)"
|
||||
>
|
||||
<template #icon><PlayCircleOutlined /> </template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
placement="topRight"
|
||||
v-if="
|
||||
modalState.from[record.id].loading ||
|
||||
modalState.from[record.id].cmdStop
|
||||
"
|
||||
>
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.pcap.textStop') }}</div>
|
||||
</template>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
size="small"
|
||||
@click.prevent="fnRecordStop(record)"
|
||||
>
|
||||
<template #icon><StopOutlined /> </template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip
|
||||
placement="topRight"
|
||||
v-if="
|
||||
!modalState.from[record.id].loading &&
|
||||
modalState.from[record.id].taskFiles.length > 0
|
||||
"
|
||||
>
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.pcap.textLog') }}</div>
|
||||
</template>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
size="small"
|
||||
@click.prevent="fnModalVisibleByVive(record.id)"
|
||||
>
|
||||
<template #icon><FileTextOutlined /> </template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
placement="topRight"
|
||||
v-if="
|
||||
!modalState.from[record.id].loading &&
|
||||
!!modalState.from[record.id].taskCode
|
||||
"
|
||||
>
|
||||
<template #title>
|
||||
<div>{{ t('views.traceManage.pcap.textDown') }}</div>
|
||||
</template>
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
size="small"
|
||||
@click.prevent="fnDownPCAP(record)"
|
||||
>
|
||||
<template #icon><DownloadOutlined /> </template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 日志信息框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:fullscreen="true"
|
||||
:borderDraw="true"
|
||||
:width="800"
|
||||
:open="modalState.openByView"
|
||||
:footer="false"
|
||||
:maskClosable="false"
|
||||
:keyboard="false"
|
||||
:body-style="{ padding: '0 12px 12px' }"
|
||||
:title="t('views.traceManage.pcap.textLogMsg')"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-tabs
|
||||
v-model:activeKey="modalState.viewFrom.action"
|
||||
tab-position="top"
|
||||
size="small"
|
||||
@tabClick="fnViveTab"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="fileName in modalState.viewFrom.files"
|
||||
:key="fileName"
|
||||
:tab="fileName"
|
||||
:destroyInactiveTabPane="false"
|
||||
>
|
||||
<a-spin :spinning="!modalState.viewFrom.content">
|
||||
<a-textarea
|
||||
:value="modalState.viewFrom.content"
|
||||
:auto-size="{ minRows: 2 }"
|
||||
:disabled="true"
|
||||
style="color: rgba(0, 0, 0, 0.85)"
|
||||
/>
|
||||
</a-spin>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</ProModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
16
src/views/core-ne/tool/signal/wireshark/index.vue
Normal file
16
src/views/core-ne/tool/signal/wireshark/index.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<h1>Overview 核心网 概览</h1>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
381
src/views/core-ne/tool/terminal/components/hostList.vue
Normal file
381
src/views/core-ne/tool/terminal/components/hostList.vue
Normal file
@@ -0,0 +1,381 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw, defineAsyncComponent } from 'vue';
|
||||
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 { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { delNeHost, listNeHost } from '@/api/ne/neHost';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const neStore = useNeStore();
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
const EditModal = defineAsyncComponent(
|
||||
() => import('@/views/ne/neHost/components/EditModal.vue')
|
||||
);
|
||||
const emit = defineEmits(['modal', 'link']);
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**主机类型 */
|
||||
neHostType: DictType[];
|
||||
/**分组 */
|
||||
neHostGroupId: DictType[];
|
||||
/**认证模式 */
|
||||
neHostAuthMode: DictType[];
|
||||
} = reactive({
|
||||
neHostType: [],
|
||||
neHostGroupId: [],
|
||||
neHostAuthMode: [],
|
||||
});
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**主机类型 */
|
||||
hostType: undefined,
|
||||
/**分组 */
|
||||
groupId: undefined,
|
||||
/**名称 */
|
||||
title: 'OMC',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 10,
|
||||
sortField: 'createTime',
|
||||
sortOrder: 'desc',
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'small',
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'center',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.hostType'),
|
||||
dataIndex: 'hostType',
|
||||
key: 'hostType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.groupId'),
|
||||
dataIndex: 'groupId',
|
||||
key: 'groupId',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.title'),
|
||||
dataIndex: 'title',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.addr'),
|
||||
dataIndex: 'addr',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neHost.port'),
|
||||
dataIndex: 'port',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 10,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 10,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**查询信息列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listNeHost(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
openByEdit: boolean;
|
||||
/**记录ID */
|
||||
id: number | undefined;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByEdit: false,
|
||||
id: undefined,
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param roleId 角色编号ID, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(roleId?: undefined) {
|
||||
modalState.id = roleId;
|
||||
modalState.openByEdit = true;
|
||||
emit('modal');
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.openByEdit = false;
|
||||
modalState.id = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元主机删除
|
||||
* @param id 网元主机编号ID
|
||||
*/
|
||||
function fnRecordDelete(id: string) {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neHost.delTip', { num: id }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delNeHost(id).then(res => {
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 网元主机连接
|
||||
* @param id 网元主机编号ID
|
||||
*/
|
||||
function fnRecordLink(host: Record<string, any>) {
|
||||
emit('link', JSON.parse(JSON.stringify(host)));
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('ne_host_type'),
|
||||
getDict('ne_host_groupId'),
|
||||
getDict('ne_host_authMode'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.neHostType = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.neHostGroupId = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.neHostAuthMode = resArr[2].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }" size="small">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click.prevent="fnModalVisibleByEdit()"
|
||||
>
|
||||
<template #icon><PlusOutlined /></template>
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
<a-button type="text" size="small" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
{{ t('common.reloadText') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center" direction="horizontal">
|
||||
<a-form layout="inline">
|
||||
<a-form-item :label="t('views.ne.neHost.hostType')" name="hostType">
|
||||
<a-select
|
||||
v-model:value="queryParams.hostType"
|
||||
:options="dict.neHostType"
|
||||
allow-clear
|
||||
size="small"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnGetList(1)"
|
||||
style="width: 100px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('views.ne.neHost.title')" name="title">
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.title"
|
||||
:options="neStore.getNeSelectOtions"
|
||||
allow-clear
|
||||
size="small"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
@change="fnGetList(1)"
|
||||
style="width: 180px"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: tableColumns.length * 120 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'groupId'">
|
||||
<DictTag :options="dict.neHostGroupId" :value="record.groupId" />
|
||||
</template>
|
||||
<template v-if="column.key === 'hostType'">
|
||||
<DictTag :options="dict.neHostType" :value="record.hostType" />
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title> Link to host </template>
|
||||
<a-button type="link" @click.prevent="fnRecordLink(record)">
|
||||
<template #icon><CodeOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.editText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.id)"
|
||||
>
|
||||
<template #icon><FormOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-if="record.groupId !== '1'">
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button type="link" @click.prevent="fnRecordDelete(record.id)">
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<EditModal
|
||||
v-model:open="modalState.openByEdit"
|
||||
:edit-id="modalState.id"
|
||||
:ne-group="true"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
></EditModal>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
340
src/views/core-ne/tool/terminal/index.vue
Normal file
340
src/views/core-ne/tool/terminal/index.vue
Normal file
@@ -0,0 +1,340 @@
|
||||
<script lang="ts" setup>
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { Modal } from 'ant-design-vue/es';
|
||||
import { useFullscreen } from '@vueuse/core';
|
||||
import { defineAsyncComponent, reactive, ref } from 'vue';
|
||||
import { parseDuration } from '@/utils/date-utils';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
const TerminalSSH = defineAsyncComponent(
|
||||
() => import('@/components/TerminalSSH/index.vue')
|
||||
);
|
||||
const TerminalTelnet = defineAsyncComponent(
|
||||
() => import('@/components/TerminalTelnet/index.vue')
|
||||
);
|
||||
const TerminalRedis = defineAsyncComponent(
|
||||
() => import('@/components/TerminalRedis/index.vue')
|
||||
);
|
||||
const HostList = defineAsyncComponent(
|
||||
() => import('./components/hostList.vue')
|
||||
);
|
||||
|
||||
// 全屏
|
||||
const terminalCard = ref<HTMLElement | null>(null);
|
||||
const { isFullscreen, toggle } = useFullscreen(terminalCard);
|
||||
|
||||
/**主机对象信息状态类型 */
|
||||
type HostStateType = {
|
||||
/**显示主机列表 */
|
||||
show: boolean;
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**查询参数 */
|
||||
params: {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
};
|
||||
/**数据总数 */
|
||||
total: number;
|
||||
data: Record<string, any>[];
|
||||
};
|
||||
|
||||
/**主机对象信息状态 */
|
||||
const hostState: HostStateType = reactive({
|
||||
show: false,
|
||||
loading: false,
|
||||
params: {
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
},
|
||||
total: 0,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**连接主机 */
|
||||
function fnConnectHost(data: Record<string, any>) {
|
||||
const id = `${Date.now()}`;
|
||||
tabState.panes.push({
|
||||
id,
|
||||
status: false,
|
||||
host: data,
|
||||
});
|
||||
tabState.activeKey = id;
|
||||
}
|
||||
|
||||
/**标签对象信息状态类型 */
|
||||
type TabStateType = {
|
||||
/**激活选中 */
|
||||
activeKey: string;
|
||||
/**页签数据 */
|
||||
panes: {
|
||||
id: string;
|
||||
status: boolean;
|
||||
host: Record<string, any>;
|
||||
connectStamp?: string;
|
||||
}[];
|
||||
};
|
||||
|
||||
/**标签对象信息状态 */
|
||||
const tabState: TabStateType = reactive({
|
||||
activeKey: '0',
|
||||
panes: [
|
||||
{
|
||||
id: '0',
|
||||
host: {
|
||||
id: 0,
|
||||
title: t('views.tool.terminal.start'),
|
||||
type: '0',
|
||||
},
|
||||
status: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* 终端连接状态
|
||||
* @param data 主机连接结果
|
||||
*/
|
||||
function fnTerminalConnect(data: Record<string, any>) {
|
||||
const { id, timeStamp } = data;
|
||||
const seconds = timeStamp / 1000;
|
||||
// 获取当前项下标
|
||||
const tab = tabState.panes.find(item => item.id === id);
|
||||
if (tab) {
|
||||
tab.status = true;
|
||||
tab.connectStamp = parseDuration(seconds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 终端关闭状态
|
||||
* @param data 主机连接结果
|
||||
*/
|
||||
function fnTerminalClose(data: Record<string, any>) {
|
||||
const { id } = data;
|
||||
// 获取当前项下标
|
||||
const tab = tabState.panes.find(item => item.id === id);
|
||||
if (tab) {
|
||||
tab.status = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签更多菜单项
|
||||
* @param key 菜单key
|
||||
*/
|
||||
function fnTabMenu(key: string | number) {
|
||||
// 刷新当前
|
||||
if (key === 'reload') {
|
||||
const tabIndex = tabState.panes.findIndex(
|
||||
item => item.id === tabState.activeKey
|
||||
);
|
||||
if (tabIndex) {
|
||||
const tab = tabState.panes[tabIndex];
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.tool.terminal.reloadTip', {
|
||||
num: `${tab.host.hostType} - ${tab.host.title}`,
|
||||
}),
|
||||
onOk() {
|
||||
tabState.panes.splice(tabIndex, 1);
|
||||
tab.host && fnConnectHost(tab.host);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
// 关闭当前
|
||||
if (key === 'current') {
|
||||
fnTabClose(tabState.activeKey);
|
||||
}
|
||||
// 关闭其他
|
||||
if (key === 'other') {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.tool.terminal.otherTip'),
|
||||
onOk() {
|
||||
hostState.show = false;
|
||||
tabState.panes = tabState.panes.filter(
|
||||
tab => tab.id === '0' || tab.id === tabState.activeKey
|
||||
);
|
||||
tabState.activeKey = tabState.activeKey;
|
||||
},
|
||||
});
|
||||
}
|
||||
// 关闭全部
|
||||
if (key === 'all') {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.tool.terminal.allTip'),
|
||||
onOk() {
|
||||
hostState.show = false;
|
||||
tabState.panes.splice(1);
|
||||
tabState.activeKey = '0';
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导航标签关闭
|
||||
* @param id 标签的key
|
||||
*/
|
||||
function fnTabClose(id: string) {
|
||||
if (isFullscreen.value) toggle();
|
||||
|
||||
// 获取当前项下标
|
||||
const tabIndex = tabState.panes.findIndex(tab => tab.id === id);
|
||||
if (tabIndex === -1) return;
|
||||
const item = tabState.panes[tabIndex];
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.tool.terminal.closeTip', {
|
||||
num: `${item.host.hostType.toUpperCase()} - ${item.host.title}`,
|
||||
}),
|
||||
onOk() {
|
||||
tabState.panes.splice(tabIndex, 1);
|
||||
// 激活前一项标签
|
||||
tabState.activeKey = tabState.panes[tabIndex - 1].id;
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
size="small"
|
||||
:body-style="{ padding: '12px' }"
|
||||
ref="terminalCard"
|
||||
>
|
||||
<a-tabs
|
||||
class="terminal-tabs"
|
||||
hide-add
|
||||
size="small"
|
||||
tab-position="top"
|
||||
type="editable-card"
|
||||
:tab-bar-gutter="8"
|
||||
:tab-bar-style="{ margin: '0' }"
|
||||
v-model:activeKey="tabState.activeKey"
|
||||
@edit="(id:any) => fnTabClose(id)"
|
||||
>
|
||||
<a-tab-pane
|
||||
v-for="pane in tabState.panes"
|
||||
:key="pane.id"
|
||||
:closable="pane.id !== '0'"
|
||||
>
|
||||
<template #tab>
|
||||
<a-badge
|
||||
:status="pane.status ? 'success' : 'error'"
|
||||
:text="pane.host.title"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<div class="pane-box">
|
||||
<!-- 非开始页的ssh主机 -->
|
||||
<TerminalSSH
|
||||
v-if="pane.id !== '0' && pane.host.hostType === 'ssh'"
|
||||
:id="pane.id"
|
||||
:hostId="pane.host.id"
|
||||
@connect="fnTerminalConnect"
|
||||
@close="fnTerminalClose"
|
||||
>
|
||||
</TerminalSSH>
|
||||
|
||||
<!-- 非开始页的telnet主机 -->
|
||||
<TerminalTelnet
|
||||
v-if="pane.id !== '0' && pane.host.hostType === 'telnet'"
|
||||
:id="pane.id"
|
||||
:hostId="pane.host.id"
|
||||
init-cmd="help"
|
||||
:disable="true"
|
||||
@connect="fnTerminalConnect"
|
||||
@close="fnTerminalClose"
|
||||
>
|
||||
</TerminalTelnet>
|
||||
|
||||
<!-- 非开始页的redis主机 -->
|
||||
<TerminalRedis
|
||||
v-if="pane.id !== '0' && pane.host.hostType === 'redis'"
|
||||
:id="pane.id"
|
||||
:hostId="pane.host.id"
|
||||
init-cmd="PING"
|
||||
@connect="fnTerminalConnect"
|
||||
@close="fnTerminalClose"
|
||||
>
|
||||
</TerminalRedis>
|
||||
|
||||
<!-- 开始页 -->
|
||||
<div v-if="pane.id === '0'">
|
||||
<!-- 主机列表 -->
|
||||
<HostList
|
||||
v-show="tabState.activeKey === '0'"
|
||||
@modal="() => (isFullscreen ? toggle() : null)"
|
||||
@link="fnConnectHost"
|
||||
></HostList>
|
||||
</div>
|
||||
</div>
|
||||
</a-tab-pane>
|
||||
|
||||
<template #rightExtra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{ t('loayouts.rightContent.fullscreen') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="default"
|
||||
shape="circle"
|
||||
size="small"
|
||||
style="color: inherit"
|
||||
@click="toggle"
|
||||
>
|
||||
<template #icon>
|
||||
<FullscreenExitOutlined v-if="isFullscreen" />
|
||||
<FullscreenOutlined v-else />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<!-- 非开始页的更多操作 -->
|
||||
<div v-show="tabState.activeKey !== '0'">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{ t('views.tool.terminal.more') }}
|
||||
</template>
|
||||
<a-dropdown trigger="click" placement="bottomRight">
|
||||
<a-button type="ghost" shape="circle" size="small">
|
||||
<template #icon><EllipsisOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key }:any) => fnTabMenu(key)">
|
||||
<a-menu-item key="reload">
|
||||
{{ t('views.tool.terminal.reload') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="current">
|
||||
{{ t('views.tool.terminal.current') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="other">
|
||||
{{ t('views.tool.terminal.other') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="all">
|
||||
{{ t('views.tool.terminal.all') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-tabs>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.pane-box {
|
||||
height: calc(100vh - 200px);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
518
src/views/core-ne/tool/test/iperf/index.vue
Normal file
518
src/views/core-ne/tool/test/iperf/index.vue
Normal file
@@ -0,0 +1,518 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, onBeforeUnmount, ref } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import TerminalSSHView from '@/components/TerminalSSHView/index.vue';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
import { iperfV } from '@/api/tool/iperf';
|
||||
const neStore = useNeStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**网元参数 */
|
||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**状态对象 */
|
||||
let state = reactive({
|
||||
/**初始化 */
|
||||
initialized: false,
|
||||
/**运行中 */
|
||||
running: false,
|
||||
/**版本信息 */
|
||||
versionInfo: [],
|
||||
/**网元类型 */
|
||||
neType: [],
|
||||
/**数据类型 */
|
||||
dataType: 'options' as 'options' | 'command',
|
||||
/**ws参数 */
|
||||
params: {
|
||||
neType: '',
|
||||
neUid: '',
|
||||
cols: 120,
|
||||
rows: 40,
|
||||
},
|
||||
/**ws数据 */
|
||||
data: {
|
||||
command: '', // 命令字符串
|
||||
version: 'V3', // 服务版本,默认V3
|
||||
mode: 'client', // 服务端或客户端,默认服务端
|
||||
// Server or Client
|
||||
port: 5201, // 服务端口
|
||||
interval: 1, // 每次报告之间的时间间隔,单位为秒
|
||||
// Server
|
||||
oneOff: false, // 只进行一次连接
|
||||
// Client
|
||||
host: '', // 客户端连接到的服务端IP地址
|
||||
udp: false, // use UDP rather than TCP
|
||||
time: 10, // 以秒为单位的传输时间(默认为 10 秒)
|
||||
reverse: false, // 以反向模式运行(服务器发送,客户端接收)
|
||||
window: '300k', // 设置窗口大小/套接字缓冲区大小
|
||||
parallel: 1, // 运行的并行客户端流数量
|
||||
bitrate: 0, // 以比特/秒为单位(0 表示无限制)
|
||||
},
|
||||
});
|
||||
|
||||
/**连接发送 */
|
||||
async function fnIPerf() {
|
||||
const [neType, neUid] = state.neType;
|
||||
if (!neType || !neUid) {
|
||||
message.warning({
|
||||
content: 'No Found NE Type',
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
state.dataType === 'options' &&
|
||||
state.data.mode === 'client' &&
|
||||
state.data.host === ''
|
||||
) {
|
||||
message.warning({
|
||||
content: 'Please fill in the Host',
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (state.dataType === 'command' && state.data.command === '') {
|
||||
message.warning({
|
||||
content: 'Please fill in the Command',
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 网元切换时重置
|
||||
if (neType !== state.params.neType || neUid !== state.params.neUid) {
|
||||
state.initialized = false;
|
||||
state.params.neType = neType;
|
||||
state.params.neUid = neUid;
|
||||
}
|
||||
|
||||
// 软件版本检查
|
||||
state.params.neType = neType;
|
||||
state.params.neUid = neUid;
|
||||
const resVersion = await iperfV({
|
||||
neType,
|
||||
neUid,
|
||||
version: state.data.version,
|
||||
});
|
||||
if (resVersion.code !== RESULT_CODE_SUCCESS) {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: 'Not found iperf version, please install (https://iperf.fr)',
|
||||
onOk: () => {},
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
state.versionInfo = resVersion.data;
|
||||
}
|
||||
|
||||
// 初始化的直接重发
|
||||
if (state.initialized) {
|
||||
fnResend();
|
||||
return;
|
||||
}
|
||||
state.initialized = true;
|
||||
}
|
||||
|
||||
/**终端实例 */
|
||||
const toolTerminal = ref();
|
||||
|
||||
/**重置并停止 */
|
||||
function fnReset() {
|
||||
if (!toolTerminal.value) return;
|
||||
toolTerminal.value.ctrlC();
|
||||
// toolTerminal.value.clear();
|
||||
state.running = false;
|
||||
}
|
||||
|
||||
/**重载发送 */
|
||||
function fnResend() {
|
||||
if (!toolTerminal.value) return;
|
||||
state.running = true;
|
||||
toolTerminal.value.ctrlC();
|
||||
setTimeout(() => {
|
||||
toolTerminal.value.clear();
|
||||
const data = JSON.parse(JSON.stringify(state.data));
|
||||
if (state.dataType === 'options') data.command = '';
|
||||
toolTerminal.value.send('iperf', data);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**终端初始连接*/
|
||||
function fnConnect() {
|
||||
fnResend();
|
||||
}
|
||||
|
||||
/**终端消息处理*/
|
||||
function fnProcessMessage(data: string): string {
|
||||
// 查找的开始输出标记
|
||||
const parts: string[] = data.split('\u001b[?2004l\r');
|
||||
if (parts.length > 0) {
|
||||
if (parts[0].startsWith('^C') || parts[0].startsWith('\r')) {
|
||||
return '';
|
||||
}
|
||||
let text = parts[parts.length - 1];
|
||||
// 找到最后输出标记
|
||||
let lestIndex = text.lastIndexOf('\u001b[?2004h\u001b]0;');
|
||||
if (lestIndex !== -1) {
|
||||
text = text.substring(0, lestIndex);
|
||||
}
|
||||
if (text === '' || text === '\r\n' || text.startsWith('^C')) {
|
||||
return '';
|
||||
}
|
||||
// 是否还有最后输出标记
|
||||
lestIndex = text.lastIndexOf('\u001b[?2004h');
|
||||
if (lestIndex !== -1) {
|
||||
text = text.substring(0, lestIndex);
|
||||
}
|
||||
if (text.endsWith('# ')) {
|
||||
text = text.substring(0, text.lastIndexOf('\r\n') + 2);
|
||||
}
|
||||
|
||||
// console.log({ parts, text });
|
||||
if (parts.length > 1 && parts[0].startsWith('iperf')) {
|
||||
return parts[0] + '\r\n' + text;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**终端消息监听*/
|
||||
function fnMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
if (!requestId) return;
|
||||
let lestIndex = data.lastIndexOf('unable to');
|
||||
if (lestIndex !== -1) {
|
||||
state.running = false;
|
||||
return;
|
||||
}
|
||||
lestIndex = data.lastIndexOf('failed:');
|
||||
if (lestIndex !== -1) {
|
||||
state.running = false;
|
||||
return;
|
||||
}
|
||||
lestIndex = data.lastIndexOf('iperf Done.');
|
||||
if (lestIndex !== -1) {
|
||||
state.running = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**钩子函数,界面打开初始化*/
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
for (const item of neStore.getNeCascaderOptions) {
|
||||
neCascaderOptions.value.push(item); // 过滤不可用的网元
|
||||
}
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
/**钩子函数,界面关闭*/
|
||||
onBeforeUnmount(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8">
|
||||
<span>
|
||||
{{ t('views.ne.common.neType') }}:
|
||||
<a-cascader
|
||||
v-model:value="state.neType"
|
||||
:options="neCascaderOptions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:disabled="state.running"
|
||||
/>
|
||||
</span>
|
||||
<a-radio-group
|
||||
v-model:value="state.dataType"
|
||||
button-style="solid"
|
||||
:disabled="state.running"
|
||||
>
|
||||
<a-radio-button value="options">Options</a-radio-button>
|
||||
<a-radio-button value="command">Command</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8">
|
||||
<a-button
|
||||
@click.prevent="fnIPerf()"
|
||||
type="primary"
|
||||
:loading="state.running"
|
||||
>
|
||||
<template #icon><PlayCircleOutlined /></template>
|
||||
{{ state.running ? 'Running' : 'Launch' }}
|
||||
</a-button>
|
||||
<a-button v-if="state.running" @click.prevent="fnReset()" danger>
|
||||
<template #icon><CloseCircleOutlined /></template>
|
||||
Stop
|
||||
</a-button>
|
||||
<!-- 版本信息 -->
|
||||
<a-popover
|
||||
trigger="click"
|
||||
placement="bottomRight"
|
||||
v-if="state.versionInfo.length > 0"
|
||||
>
|
||||
<template #content>
|
||||
<div v-for="v in state.versionInfo">{{ v }}</div>
|
||||
</template>
|
||||
<InfoCircleOutlined />
|
||||
</a-popover>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- options -->
|
||||
<a-form
|
||||
v-if="state.dataType === 'options'"
|
||||
:model="state.data"
|
||||
name="queryParams"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:label-wrap="true"
|
||||
style="padding: 12px"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="4" :offset="2">
|
||||
<a-form-item label="Mode" name="mode">
|
||||
<a-radio-group
|
||||
v-model:value="state.data.mode"
|
||||
:disabled="state.running"
|
||||
button-style="solid"
|
||||
size="small"
|
||||
>
|
||||
<a-radio-button value="client">Client</a-radio-button>
|
||||
<a-radio-button value="server">Server</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="4">
|
||||
<a-form-item
|
||||
label="Version"
|
||||
name="version"
|
||||
:label-col="{ span: 8 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-radio-group
|
||||
v-model:value="state.data.version"
|
||||
:disabled="state.running"
|
||||
button-style="solid"
|
||||
size="small"
|
||||
>
|
||||
<a-radio-button value="V2">V2</a-radio-button>
|
||||
<a-radio-button value="V3">V3</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="6" :xs="12">
|
||||
<a-form-item label="Port" name="port">
|
||||
<a-input-number
|
||||
v-model:value="state.data.port"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
:min="1024"
|
||||
:max="65535"
|
||||
></a-input-number> </a-form-item
|
||||
></a-col>
|
||||
<a-col :lg="6" :md="6" :xs="12">
|
||||
<a-form-item label="Interval" name="interval">
|
||||
<a-input-number
|
||||
v-model:value="state.data.interval"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="30"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<template v-if="state.data.mode === 'client'">
|
||||
<a-form-item
|
||||
label="Host"
|
||||
name="host"
|
||||
help="run in client mode, connecting to <host>"
|
||||
:label-col="{ span: 3 }"
|
||||
:wrapper-col="{ span: 9 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.data.host"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="12">
|
||||
<a-form-item
|
||||
label="UDP"
|
||||
name="udp"
|
||||
help="use UDP rather than TCP"
|
||||
>
|
||||
<a-switch
|
||||
v-model:checked="state.data.udp"
|
||||
:disabled="state.running"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="12">
|
||||
<a-form-item
|
||||
label="Reverse"
|
||||
name="reverse"
|
||||
help="run in reverse mode (server sends, client receives)"
|
||||
>
|
||||
<a-switch
|
||||
v-model:checked="state.data.reverse"
|
||||
:disabled="state.running"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="12">
|
||||
<a-form-item
|
||||
label="Time"
|
||||
name="time"
|
||||
help="time in seconds to transmit for (default 10 secs)"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="state.data.time"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="Parallel"
|
||||
name="parallel"
|
||||
help="number of parallel client streams to run"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="state.data.parallel"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="12">
|
||||
<a-form-item
|
||||
label="Window"
|
||||
name="window"
|
||||
help="set window size / socket buffer size"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.data.window"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="Bitrate"
|
||||
name="bitrate"
|
||||
help="target bitrate in bits/sec (0 for unlimited)"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="state.data.bitrate"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
<a-row v-else>
|
||||
<a-col :lg="12" :md="12" :xs="12">
|
||||
<a-form-item
|
||||
label="OneOff"
|
||||
name="oneOff"
|
||||
help=" handle one client connection then exit"
|
||||
>
|
||||
<a-switch
|
||||
v-model:checked="state.data.oneOff"
|
||||
:disabled="state.running"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<!-- command -->
|
||||
<div v-else style="padding: 12px">
|
||||
<a-input-group compact>
|
||||
<a-select
|
||||
v-model:value="state.data.version"
|
||||
:disabled="state.running"
|
||||
>
|
||||
<a-select-option value="V2">iperf</a-select-option>
|
||||
<a-select-option value="V3">iperf3</a-select-option>
|
||||
</a-select>
|
||||
<a-auto-complete
|
||||
v-model:value="state.data.command"
|
||||
:disabled="state.running"
|
||||
:options="[
|
||||
{ value: '--help' },
|
||||
{ value: '-c 172.5.16.100 -p 5201' },
|
||||
{ value: '-s -p 5201' },
|
||||
]"
|
||||
style="width: 400px"
|
||||
placeholder="client or server command"
|
||||
/>
|
||||
</a-input-group>
|
||||
</div>
|
||||
|
||||
<!-- 运行过程 -->
|
||||
<TerminalSSHView
|
||||
v-if="state.initialized"
|
||||
ref="toolTerminal"
|
||||
:id="`V${Date.now()}`"
|
||||
prefix="iperf"
|
||||
url="/tool/iperf/run"
|
||||
:ne-type="state.params.neType"
|
||||
:ne-uid="state.params.neUid"
|
||||
:rows="state.params.rows"
|
||||
:cols="state.params.cols"
|
||||
:process-messages="fnProcessMessage"
|
||||
style="height: 400px"
|
||||
@connect="fnConnect"
|
||||
@message="fnMessage"
|
||||
></TerminalSSHView>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
338
src/views/core-ne/tool/test/net/index.vue
Normal file
338
src/views/core-ne/tool/test/net/index.vue
Normal file
@@ -0,0 +1,338 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
|
||||
|
||||
const { t } = useI18n();
|
||||
const ws = new WS();
|
||||
|
||||
/**表单查询参数 */
|
||||
let queryParams = reactive({
|
||||
pid: undefined,
|
||||
name: '',
|
||||
port: undefined,
|
||||
});
|
||||
|
||||
/**状态对象 */
|
||||
let state = reactive({
|
||||
/**调度器 */
|
||||
interval: null as any,
|
||||
/**刷新周期 */
|
||||
intervalTime: 5_000,
|
||||
/**查询参数 */
|
||||
query: {
|
||||
pid: undefined,
|
||||
name: '',
|
||||
port: undefined,
|
||||
},
|
||||
});
|
||||
|
||||
/**接收数据后回调(成功) */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 建联时发送请求
|
||||
if (!requestId && data.clientId) {
|
||||
fnGetList();
|
||||
return;
|
||||
}
|
||||
|
||||
// 收到消息数据
|
||||
if (requestId.startsWith('net_')) {
|
||||
// 将数据填入表格
|
||||
if (Array.isArray(data)) {
|
||||
if (tableState.loading) {
|
||||
tableState.loading = false;
|
||||
}
|
||||
tableState.data = data;
|
||||
} else {
|
||||
tableState.data = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**实时数据*/
|
||||
function fnRealTime(reLink: boolean) {
|
||||
if (reLink) {
|
||||
ws.close();
|
||||
}
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
onmessage: wsMessage,
|
||||
onerror: (ev: any) => {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
//建立连接
|
||||
ws.connect(options);
|
||||
}
|
||||
|
||||
/**调度器周期变更*/
|
||||
function fnIntervalChange(v: any) {
|
||||
clearInterval(state.interval);
|
||||
state.interval = null;
|
||||
const timer = parseInt(v);
|
||||
if (timer > 1_000) {
|
||||
state.intervalTime = v;
|
||||
fnGetList();
|
||||
}
|
||||
}
|
||||
|
||||
/**查询列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading || ws.state() === -1) return;
|
||||
tableState.loading = true;
|
||||
const msg = {
|
||||
requestId: `net_${state.interval}`,
|
||||
type: 'net',
|
||||
data: state.query,
|
||||
};
|
||||
// 首发
|
||||
ws.send(msg);
|
||||
// 定时刷新数据
|
||||
state.interval = setInterval(() => {
|
||||
msg.data = JSON.parse(JSON.stringify(state.query));
|
||||
ws.send(msg);
|
||||
}, state.intervalTime);
|
||||
}
|
||||
|
||||
/**查询参数传入 */
|
||||
function fnQuery() {
|
||||
state.query = JSON.parse(JSON.stringify(queryParams));
|
||||
nextTick(() => {
|
||||
ws.send({
|
||||
requestId: `net_${state.interval}`,
|
||||
type: 'net',
|
||||
data: state.query,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
Object.assign(queryParams, {
|
||||
pid: undefined,
|
||||
name: '',
|
||||
port: undefined,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
// 重置查询条件
|
||||
Object.assign(state.query, {
|
||||
pid: undefined,
|
||||
name: '',
|
||||
port: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TableStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TableStateType = reactive({
|
||||
loading: false,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
const tableColumns: ColumnsType<any> = [
|
||||
{
|
||||
title: t('views.tool.ps.pid'),
|
||||
dataIndex: 'pid',
|
||||
align: 'right',
|
||||
width: 100,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.pid - b.pid,
|
||||
multiple: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.proto'),
|
||||
dataIndex: 'type',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
customRender(opt) {
|
||||
return `${opt.value}`.toUpperCase();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.localAddr'),
|
||||
dataIndex: 'localAddr',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
const { ip, port } = opt.value;
|
||||
return port !== 0 ? `${ip}:${port}` : `${ip}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.remoteAddr'),
|
||||
dataIndex: 'remoteAddr',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
const { ip, port } = opt.value;
|
||||
return port !== 0 ? `${ip}:${port}` : `${ip}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.net.status'),
|
||||
dataIndex: 'status',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.name'),
|
||||
dataIndex: 'name',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
},
|
||||
});
|
||||
|
||||
/**钩子函数,界面打开初始化*/
|
||||
onMounted(() => {
|
||||
fnRealTime(false);
|
||||
});
|
||||
|
||||
/**钩子函数,界面关闭*/
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(state.interval);
|
||||
state.interval = null;
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="4" :md="6" :xs="12">
|
||||
<a-form-item :label="t('views.tool.ps.pid')" name="pid">
|
||||
<a-input-number
|
||||
v-model:value="queryParams.pid"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.tool.ps.name')" name="name">
|
||||
<a-input
|
||||
v-model:value="queryParams.name"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.tool.net.port')" name="port">
|
||||
<a-input-number
|
||||
v-model:value="queryParams.port"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="4" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnQuery()">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-form layout="inline">
|
||||
<a-form-item :label="t('views.tool.ps.realTime')" name="realTime">
|
||||
<a-select
|
||||
v-model:value="state.intervalTime"
|
||||
:options="[
|
||||
{ label: t('views.tool.ps.realTimeHigh'), value: 3_000 },
|
||||
{ label: t('views.tool.ps.realTimeRegular'), value: 5_000 },
|
||||
{ label: t('views.tool.ps.realTimeLow'), value: 10_000 },
|
||||
{ label: t('views.tool.ps.realTimeStop'), value: -1 },
|
||||
]"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnIntervalChange"
|
||||
style="width: 100px"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:pagination="tablePagination"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
size="small"
|
||||
:scroll="{ x: tableColumns.length * 120 }"
|
||||
></a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
408
src/views/core-ne/tool/test/ping/index.vue
Normal file
408
src/views/core-ne/tool/test/ping/index.vue
Normal file
@@ -0,0 +1,408 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, onBeforeUnmount, ref } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import TerminalSSHView from '@/components/TerminalSSHView/index.vue';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
import { pingV } from '@/api/tool/ping';
|
||||
const neStore = useNeStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**网元参数 */
|
||||
let neCascaderOptions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**状态对象 */
|
||||
let state = reactive({
|
||||
/**初始化 */
|
||||
initialized: false,
|
||||
/**运行中 */
|
||||
running: false,
|
||||
/**版本信息 */
|
||||
versionInfo: '',
|
||||
/**网元类型 */
|
||||
neType: [],
|
||||
/**数据类型 */
|
||||
dataType: 'options' as 'options' | 'command',
|
||||
/**ws参数 */
|
||||
params: {
|
||||
neUid: '',
|
||||
neType: '',
|
||||
cols: 120,
|
||||
rows: 40,
|
||||
},
|
||||
/**ws数据 */
|
||||
data: {
|
||||
command: '', // 命令字符串
|
||||
desAddr: '8.8.8.8', // dns name or ip address
|
||||
// Options
|
||||
interval: 1, // seconds between sending each packet
|
||||
ttl: 255, // define time to live
|
||||
count: 4, // <count> 次回复后停止
|
||||
size: 56, // 使用 <size> 作为要发送的数据字节数
|
||||
timeout: 10, // seconds time to wait for response
|
||||
},
|
||||
});
|
||||
|
||||
/**连接发送 */
|
||||
async function fnPing() {
|
||||
const [neType, neUid] = state.neType;
|
||||
if (!neType || !neUid) {
|
||||
message.warning({
|
||||
content: 'No Found NE Type',
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (state.dataType === 'options' && state.data.desAddr === '') {
|
||||
message.warning({
|
||||
content: 'Please fill in the Destination',
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (state.dataType === 'command' && state.data.command === '') {
|
||||
message.warning({
|
||||
content: 'Please fill in the Command',
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 网元切换时重置
|
||||
if (neType !== state.params.neType || neUid !== state.params.neUid) {
|
||||
state.initialized = false;
|
||||
state.params.neType = neType;
|
||||
state.params.neUid = neUid;
|
||||
}
|
||||
|
||||
// 软件版本检查
|
||||
state.params.neType = neType;
|
||||
state.params.neUid = neUid;
|
||||
const resVersion = await pingV({ neUid, neType });
|
||||
if (resVersion.code !== RESULT_CODE_SUCCESS) {
|
||||
message.warning({
|
||||
content: 'No Found ping iputils',
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
state.versionInfo = resVersion.data;
|
||||
}
|
||||
|
||||
// 初始化的直接重发
|
||||
if (state.initialized) {
|
||||
fnResend();
|
||||
return;
|
||||
}
|
||||
state.initialized = true;
|
||||
}
|
||||
|
||||
/**终端实例 */
|
||||
const toolTerminal = ref();
|
||||
|
||||
/**重置并停止 */
|
||||
function fnReset() {
|
||||
if (!toolTerminal.value) return;
|
||||
toolTerminal.value.ctrlC();
|
||||
// toolTerminal.value.clear();
|
||||
state.running = false;
|
||||
}
|
||||
|
||||
/**重载发送 */
|
||||
function fnResend() {
|
||||
if (!toolTerminal.value) return;
|
||||
state.running = true;
|
||||
toolTerminal.value.ctrlC();
|
||||
setTimeout(() => {
|
||||
toolTerminal.value.clear();
|
||||
const data = JSON.parse(JSON.stringify(state.data));
|
||||
if (state.dataType === 'options') data.command = '';
|
||||
toolTerminal.value.send('ping', data);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/**终端初始连接*/
|
||||
function fnConnect() {
|
||||
fnResend();
|
||||
}
|
||||
|
||||
/**终端消息处理*/
|
||||
function fnProcessMessage(data: string): string {
|
||||
// 查找的开始输出标记
|
||||
const parts: string[] = data.split('\u001b[?2004l\r');
|
||||
if (parts.length > 0) {
|
||||
if (parts[0].startsWith('^C') || parts[0].startsWith('\r')) {
|
||||
return '';
|
||||
}
|
||||
let text = parts[parts.length - 1];
|
||||
// 找到最后输出标记
|
||||
let lestIndex = text.lastIndexOf('\u001b[?2004h\u001b]0;');
|
||||
if (lestIndex !== -1) {
|
||||
text = text.substring(0, lestIndex);
|
||||
}
|
||||
if (text === '' || text === '\r\n' || text.startsWith('^C')) {
|
||||
return '';
|
||||
}
|
||||
// 是否还有最后输出标记
|
||||
lestIndex = text.lastIndexOf('\u001b[?2004h');
|
||||
if (lestIndex !== -1) {
|
||||
text = text.substring(0, lestIndex);
|
||||
}
|
||||
if (text.endsWith('# ')) {
|
||||
text = text.substring(0, text.lastIndexOf('\r\n') + 2);
|
||||
}
|
||||
|
||||
// console.log({ parts, text });
|
||||
if (parts.length > 1 && parts[0].startsWith('ping')) {
|
||||
return parts[0] + '\r\n' + text;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**终端消息监听*/
|
||||
function fnMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
if (!requestId) return;
|
||||
let lestIndex = data.lastIndexOf('ping statistics ---');
|
||||
if (lestIndex !== -1) {
|
||||
state.running = false;
|
||||
return;
|
||||
}
|
||||
lestIndex = data.lastIndexOf('failure');
|
||||
if (lestIndex !== -1) {
|
||||
state.running = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**钩子函数,界面打开初始化*/
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
for (const item of neStore.getNeCascaderOptions) {
|
||||
neCascaderOptions.value.push(item); // 过滤不可用的网元
|
||||
}
|
||||
if (neCascaderOptions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
/**钩子函数,界面关闭*/
|
||||
onBeforeUnmount(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8">
|
||||
<span>
|
||||
{{ t('views.ne.common.neType') }}:
|
||||
<a-cascader
|
||||
v-model:value="state.neType"
|
||||
:options="neCascaderOptions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:disabled="state.running"
|
||||
/>
|
||||
</span>
|
||||
<a-radio-group
|
||||
v-model:value="state.dataType"
|
||||
button-style="solid"
|
||||
:disabled="state.running"
|
||||
>
|
||||
<a-radio-button value="options">Options</a-radio-button>
|
||||
<a-radio-button value="command">Command</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8">
|
||||
<a-button
|
||||
@click.prevent="fnPing()"
|
||||
type="primary"
|
||||
:loading="state.running"
|
||||
>
|
||||
<template #icon><PlayCircleOutlined /></template>
|
||||
{{ state.running ? 'Running' : 'Launch' }}
|
||||
</a-button>
|
||||
<a-button v-if="state.running" @click.prevent="fnReset()" danger>
|
||||
<template #icon><CloseCircleOutlined /></template>
|
||||
Stop
|
||||
</a-button>
|
||||
<!-- 版本信息 -->
|
||||
<a-popover
|
||||
trigger="click"
|
||||
placement="bottomRight"
|
||||
v-if="state.versionInfo"
|
||||
>
|
||||
<template #content>
|
||||
{{ state.versionInfo }}
|
||||
</template>
|
||||
<InfoCircleOutlined />
|
||||
</a-popover>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- options -->
|
||||
<a-form
|
||||
v-if="state.dataType === 'options'"
|
||||
:model="state.data"
|
||||
name="queryParams"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:label-wrap="true"
|
||||
style="padding: 12px"
|
||||
>
|
||||
<a-form-item
|
||||
label="Destination"
|
||||
name="desAddr"
|
||||
help="dns name or ip address"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.data.desAddr"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="12">
|
||||
<a-form-item
|
||||
label="Interval"
|
||||
name="Interval"
|
||||
help="seconds between sending each packet"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="state.data.interval"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="120"
|
||||
>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="12">
|
||||
<a-form-item label="TTL" name="ttl" help="define time to live">
|
||||
<a-input-number
|
||||
v-model:value="state.data.ttl"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="255"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="12">
|
||||
<a-form-item
|
||||
label="Count"
|
||||
name="count"
|
||||
help="stop after <count> replies"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="state.data.count"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="120"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="12">
|
||||
<a-form-item
|
||||
label="Size"
|
||||
name="size"
|
||||
help="use <size> as number of data bytes to be sent"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="state.data.size"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="128"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="12">
|
||||
<a-form-item
|
||||
label="Deadline"
|
||||
name="timeout"
|
||||
help="reply wait <deadline> in seconds"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="state.data.timeout"
|
||||
:disabled="state.running"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="60"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
|
||||
<!-- command -->
|
||||
<div v-else style="padding: 12px">
|
||||
<a-auto-complete
|
||||
v-model:value="state.data.command"
|
||||
:disabled="state.running"
|
||||
:options="[{ value: '-help' }, { value: '-i 1 -c 4 8.8.8.8' }]"
|
||||
:dropdown-match-select-width="500"
|
||||
style="width: 100%"
|
||||
>
|
||||
<a-input addon-before="ping" placeholder="command" />
|
||||
</a-auto-complete>
|
||||
</div>
|
||||
|
||||
<!-- 运行过程 -->
|
||||
<TerminalSSHView
|
||||
v-if="state.initialized"
|
||||
ref="toolTerminal"
|
||||
:id="`V${Date.now()}`"
|
||||
prefix="ping"
|
||||
url="/tool/ping/run"
|
||||
:ne-type="state.params.neType"
|
||||
:ne-uid="state.params.neUid"
|
||||
:rows="state.params.rows"
|
||||
:cols="state.params.cols"
|
||||
:process-messages="fnProcessMessage"
|
||||
style="height: 400px"
|
||||
@connect="fnConnect"
|
||||
@message="fnMessage"
|
||||
></TerminalSSHView>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
370
src/views/core-ne/tool/test/ps/index.vue
Normal file
370
src/views/core-ne/tool/test/ps/index.vue
Normal file
@@ -0,0 +1,370 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { OptionsType, WS } from '@/plugins/ws-websocket';
|
||||
import { RESULT_CODE_ERROR } from '@/constants/result-constants';
|
||||
import { diffValue, parseDuration } from '@/utils/date-utils';
|
||||
import { parseSizeFromFile } from '@/utils/parse-utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
const ws = new WS();
|
||||
|
||||
/**表单查询参数 */
|
||||
let queryParams = reactive({
|
||||
pid: undefined,
|
||||
name: '',
|
||||
username: '',
|
||||
});
|
||||
|
||||
/**状态对象 */
|
||||
let state = reactive({
|
||||
/**调度器 */
|
||||
interval: null as any,
|
||||
/**刷新周期 */
|
||||
intervalTime: 5_000,
|
||||
/**查询参数 */
|
||||
query: {
|
||||
pid: undefined,
|
||||
name: '',
|
||||
username: '',
|
||||
},
|
||||
});
|
||||
|
||||
/**接收数据后回调(成功) */
|
||||
function wsMessage(res: Record<string, any>) {
|
||||
const { code, requestId, data } = res;
|
||||
if (code === RESULT_CODE_ERROR) {
|
||||
console.warn(res.msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 建联时发送请求
|
||||
if (!requestId && data.clientId) {
|
||||
fnGetList();
|
||||
return;
|
||||
}
|
||||
|
||||
// 收到消息数据
|
||||
if (requestId.startsWith('ps_')) {
|
||||
// 将数据填入表格
|
||||
if (Array.isArray(data)) {
|
||||
if (tableState.loading) {
|
||||
tableState.loading = false;
|
||||
}
|
||||
tableState.data = data;
|
||||
} else {
|
||||
tableState.data = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**实时数据*/
|
||||
function fnRealTime(reLink: boolean) {
|
||||
if (reLink) {
|
||||
ws.close();
|
||||
}
|
||||
const options: OptionsType = {
|
||||
url: '/ws',
|
||||
onmessage: wsMessage,
|
||||
onerror: (ev: any) => {
|
||||
// 接收数据后回调
|
||||
console.error(ev);
|
||||
},
|
||||
};
|
||||
//建立连接
|
||||
ws.connect(options);
|
||||
}
|
||||
|
||||
/**调度器周期变更*/
|
||||
function fnIntervalChange(v: any) {
|
||||
clearInterval(state.interval);
|
||||
state.interval = null;
|
||||
const timer = parseInt(v);
|
||||
if (timer > 1_000) {
|
||||
state.intervalTime = v;
|
||||
fnGetList();
|
||||
}
|
||||
}
|
||||
|
||||
/**查询列表 */
|
||||
function fnGetList() {
|
||||
if (tableState.loading || ws.state() === -1) return;
|
||||
tableState.loading = true;
|
||||
const msg = {
|
||||
requestId: `ps_${state.interval}`,
|
||||
type: 'ps',
|
||||
data: state.query,
|
||||
};
|
||||
// 首发
|
||||
ws.send(msg);
|
||||
// 定时刷新数据
|
||||
state.interval = setInterval(() => {
|
||||
msg.data = JSON.parse(JSON.stringify(state.query));
|
||||
ws.send(msg);
|
||||
}, state.intervalTime);
|
||||
}
|
||||
|
||||
/**查询参数传入 */
|
||||
function fnQuery() {
|
||||
state.query = JSON.parse(JSON.stringify(queryParams));
|
||||
nextTick(() => {
|
||||
ws.send({
|
||||
requestId: `ps_${state.interval}`,
|
||||
type: 'ps',
|
||||
data: state.query,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
Object.assign(queryParams, {
|
||||
pid: undefined,
|
||||
name: '',
|
||||
username: '',
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
// 重置查询条件
|
||||
Object.assign(state.query, {
|
||||
pid: undefined,
|
||||
name: '',
|
||||
username: '',
|
||||
});
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TableStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TableStateType = reactive({
|
||||
loading: false,
|
||||
data: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
const tableColumns: ColumnsType<any> = [
|
||||
{
|
||||
title: t('views.tool.ps.pid'),
|
||||
dataIndex: 'pid',
|
||||
align: 'right',
|
||||
width: 100,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.pid - b.pid,
|
||||
multiple: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.cpuPercent'),
|
||||
dataIndex: 'cpuPercent',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.cpuPercent - b.cpuPercent,
|
||||
multiple: 3,
|
||||
},
|
||||
customRender(opt) {
|
||||
return `${opt.value} %`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.diskRead'),
|
||||
dataIndex: 'diskRead',
|
||||
align: 'right',
|
||||
width: 100,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.diskRead - b.diskRead,
|
||||
multiple: 3,
|
||||
},
|
||||
customRender(opt) {
|
||||
return parseSizeFromFile(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.diskWrite'),
|
||||
dataIndex: 'diskWrite',
|
||||
align: 'right',
|
||||
width: 100,
|
||||
sorter: {
|
||||
compare: (a: any, b: any) => a.diskWrite - b.diskWrite,
|
||||
multiple: 3,
|
||||
},
|
||||
customRender(opt) {
|
||||
return parseSizeFromFile(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.numThreads'),
|
||||
dataIndex: 'numThreads',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
sorter: {
|
||||
//线程数比较大小
|
||||
compare: (a: any, b: any) => a.numThreads - b.numThreads,
|
||||
multiple: 4, //优先级4
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.runTime'),
|
||||
dataIndex: 'startTime',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
customRender(opt) {
|
||||
const second = diffValue(Date.now(), +opt.value, 'second');
|
||||
return parseDuration(second);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.username'),
|
||||
dataIndex: 'username',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.tool.ps.name'),
|
||||
dataIndex: 'name',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
},
|
||||
});
|
||||
|
||||
/**钩子函数,界面打开初始化*/
|
||||
onMounted(() => {
|
||||
fnRealTime(false);
|
||||
});
|
||||
|
||||
/**钩子函数,界面关闭*/
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(state.interval);
|
||||
state.interval = null;
|
||||
ws.close();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="4" :md="6" :xs="12">
|
||||
<a-form-item :label="t('views.tool.ps.pid')" name="pid">
|
||||
<a-input-number
|
||||
v-model:value="queryParams.pid"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
style="width: 100%"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.tool.ps.name')" name="name">
|
||||
<a-input
|
||||
v-model:value="queryParams.name"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.tool.ps.username')" name="username">
|
||||
<a-input
|
||||
v-model:value="queryParams.username"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="4" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnQuery()">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-form layout="inline">
|
||||
<a-form-item :label="t('views.tool.ps.realTime')" name="realTime">
|
||||
<a-select
|
||||
v-model:value="state.intervalTime"
|
||||
:options="[
|
||||
{ label: t('views.tool.ps.realTimeHigh'), value: 3_000 },
|
||||
{ label: t('views.tool.ps.realTimeRegular'), value: 5_000 },
|
||||
{ label: t('views.tool.ps.realTimeLow'), value: 10_000 },
|
||||
{ label: t('views.tool.ps.realTimeStop'), value: -1 },
|
||||
]"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
@change="fnIntervalChange"
|
||||
style="width: 100px"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="pid"
|
||||
:columns="tableColumns"
|
||||
:pagination="tablePagination"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
size="small"
|
||||
:scroll="{ x: tableColumns.length * 120 }"
|
||||
></a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
1219
src/views/core-ne/ue/pcf-rule/index.vue
Normal file
1219
src/views/core-ne/ue/pcf-rule/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
1311
src/views/core-ne/ue/udm-auth/index.vue
Normal file
1311
src/views/core-ne/ue/udm-auth/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
2165
src/views/core-ne/ue/udm-sub/index.vue
Normal file
2165
src/views/core-ne/ue/udm-sub/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
979
src/views/core-ne/ue/udm-voip/index.vue
Normal file
979
src/views/core-ne/ue/udm-voip/index.vue
Normal file
@@ -0,0 +1,979 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import {
|
||||
message,
|
||||
Modal,
|
||||
Form,
|
||||
TableColumnsType,
|
||||
notification,
|
||||
} from 'ant-design-vue';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import UploadModal from '@/components/UploadModal/index.vue';
|
||||
import {
|
||||
addUDMVoIP,
|
||||
delUDMVoIP,
|
||||
exportUDMVoIP,
|
||||
importUDMVoIP,
|
||||
listUDMVoIP,
|
||||
resetUDMVoIP,
|
||||
} from '@/api/neDataNf/udm_voip';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { uploadFile } from '@/api/tool/file';
|
||||
import { getNeViewFile } from '@/api/tool/neFile';
|
||||
const { t } = useI18n();
|
||||
const neStore = useNeStore();
|
||||
/**网元参数 */
|
||||
let neOtions = ref<Record<string, any>[]>([]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元ID */
|
||||
neUid: undefined,
|
||||
/**用户名 */
|
||||
username: '',
|
||||
/**排序字段 */
|
||||
sortField: 'username',
|
||||
/**排序方式 */
|
||||
sortOrder: 'asc',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
username: '',
|
||||
sortField: 'username',
|
||||
sortOrder: 'asc',
|
||||
});
|
||||
fnGetList(1);
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'small',
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns = ref<TableColumnsType>([
|
||||
{
|
||||
title: t('views.neData.udmVoIP.username'),
|
||||
dataIndex: 'username',
|
||||
sorter: true,
|
||||
align: 'left',
|
||||
resizable: true,
|
||||
width: 250,
|
||||
minWidth: 100,
|
||||
maxWidth: 300,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
||||
/**表格字段列排序 */
|
||||
let tableColumnsDnd = ref<TableColumnsType>([]);
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格分页、排序、筛选变化时触发操作, 排序方式,取值为 ascend descend */
|
||||
function fnTableChange(pagination: any, filters: any, sorter: any, extra: any) {
|
||||
const { field, order } = sorter;
|
||||
if (order) {
|
||||
queryParams.sortField = field;
|
||||
queryParams.sortOrder = order.replace('end', '');
|
||||
} else {
|
||||
queryParams.sortOrder = 'asc';
|
||||
}
|
||||
fnGetList(1);
|
||||
}
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
openByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**是否批量操作 */
|
||||
isBatch: boolean;
|
||||
/**操作类型 */
|
||||
type: 'delete' | 'add';
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**更新加载数据按钮 loading */
|
||||
loadDataLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByEdit: false,
|
||||
title: 'UDMVoIP',
|
||||
from: {
|
||||
num: 1,
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
},
|
||||
isBatch: false,
|
||||
type: 'add',
|
||||
confirmLoading: false,
|
||||
loadDataLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
num: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.neData.common.batchNum'),
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{ required: true, message: t('views.neData.udmVoIP.usernamePlease') },
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t('views.neData.udmVoIP.passwordPlease') },
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param noticeId 网元id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(row?: Record<string, any>) {
|
||||
modalState.isBatch = false;
|
||||
if (!row) {
|
||||
modalStateFrom.resetFields(); //重置表单
|
||||
modalState.title = t('views.neData.udmVoIP.addTitle');
|
||||
modalState.openByEdit = true;
|
||||
modalState.type = 'add';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
const from = JSON.parse(JSON.stringify(modalState.from));
|
||||
from.neUid = queryParams.neUid || '-';
|
||||
from.neType = 'UDM';
|
||||
from.username = `${from.username}`;
|
||||
|
||||
// 校验规则
|
||||
let validateArr = ['username', 'password'];
|
||||
if (modalState.isBatch) {
|
||||
validateArr.push('num');
|
||||
if (modalState.type === 'delete') {
|
||||
validateArr = ['num', 'username'];
|
||||
}
|
||||
}
|
||||
modalStateFrom
|
||||
.validate(validateArr)
|
||||
.then(e => {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
|
||||
// 根据类型选择函数
|
||||
let result: any = null;
|
||||
if (modalState.isBatch) {
|
||||
if (modalState.type === 'add') {
|
||||
result = addUDMVoIP(from);
|
||||
}
|
||||
if (modalState.type === 'delete') {
|
||||
result = delUDMVoIP(from);
|
||||
}
|
||||
} else {
|
||||
if (modalState.type === 'add') {
|
||||
result = addUDMVoIP(from);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
.then((res: any) => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 3,
|
||||
});
|
||||
|
||||
fnModalCancel();
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.type = 'add';
|
||||
modalState.isBatch = false;
|
||||
modalState.openByEdit = false;
|
||||
modalStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 批量操作
|
||||
* @param type 类型
|
||||
*/
|
||||
function fnModalVisibleByBatch(type: 'delete' | 'add') {
|
||||
modalStateFrom.resetFields(); //重置表单
|
||||
modalState.isBatch = true;
|
||||
modalState.type = type;
|
||||
if (type === 'add') {
|
||||
modalState.title = t('views.neData.common.batchAddText');
|
||||
modalState.openByEdit = true;
|
||||
}
|
||||
if (type === 'delete') {
|
||||
modalState.title = t('views.neData.common.batchDelText');
|
||||
modalState.openByEdit = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录删除
|
||||
* @param username 网元编号ID
|
||||
*/
|
||||
function fnRecordDelete(username: string) {
|
||||
const neUid = queryParams.neUid;
|
||||
if (!neUid) return;
|
||||
let msg = username;
|
||||
if (username === '0') {
|
||||
msg = `${tableState.selectedRowKeys[0]}... ${tableState.selectedRowKeys.length}`;
|
||||
username = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.neData.udmVoIP.delTip', { num: msg }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delUDMVoIP({ neUid, username, num: 0 })
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.operateOk'), 3);
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList(type: string) {
|
||||
const neUid = queryParams.neUid;
|
||||
if (!neUid) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
queryParams.pageNum = 1;
|
||||
queryParams.pageSize = tablePagination.total;
|
||||
exportUDMVoIP(Object.assign({ type: type }, queryParams))
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `UDM_VoIP_${Date.now()}.${type}`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
tablePagination.current = pageNum;
|
||||
}
|
||||
listUDMVoIP(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
} else {
|
||||
tableState.data = [];
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**重新加载数据 */
|
||||
function fnLoadData() {
|
||||
const neUid = queryParams.neUid;
|
||||
if (tableState.loading || !neUid) return;
|
||||
modalState.loadDataLoading = true;
|
||||
tablePagination.total = 0;
|
||||
tableState.data = [];
|
||||
tableState.loading = true; // 表格loading
|
||||
resetUDMVoIP(neUid).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const num = res.data;
|
||||
const timerS = Math.ceil(+num / 800) + 3;
|
||||
notification.success({
|
||||
message: t('views.neData.common.loadData'),
|
||||
description: t('views.neData.common.loadDataTip', {
|
||||
num,
|
||||
timer: timerS,
|
||||
}),
|
||||
duration: timerS,
|
||||
});
|
||||
// 延迟10s后关闭loading刷新列表
|
||||
setTimeout(() => {
|
||||
modalState.loadDataLoading = false;
|
||||
tableState.loading = false; // 表格loading
|
||||
fnQueryReset();
|
||||
}, timerS * 1000);
|
||||
} else {
|
||||
modalState.loadDataLoading = false;
|
||||
tableState.loading = false; // 表格loading
|
||||
fnQueryReset();
|
||||
message.error({
|
||||
content: t('common.getInfoFail'),
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框表格信息导入对象信息状态类型 */
|
||||
type ModalUploadImportStateType = {
|
||||
/**是否显示 */
|
||||
open: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**是否上传中 */
|
||||
loading: boolean;
|
||||
/**上传结果信息 */
|
||||
msg: string;
|
||||
/**含失败信息 */
|
||||
hasFail: boolean;
|
||||
};
|
||||
|
||||
/**对话框表格信息导入对象信息状态 */
|
||||
let uploadImportState: ModalUploadImportStateType = reactive({
|
||||
open: false,
|
||||
title: t('components.UploadModal.uploadTitle'),
|
||||
loading: false,
|
||||
msg: '',
|
||||
hasFail: false,
|
||||
});
|
||||
|
||||
/**对话框表格信息导入弹出窗口 */
|
||||
function fnModalUploadImportOpen() {
|
||||
uploadImportState.msg = '';
|
||||
uploadImportState.hasFail = false;
|
||||
uploadImportState.loading = false;
|
||||
uploadImportState.open = true;
|
||||
}
|
||||
|
||||
/**对话框表格信息导入关闭窗口 */
|
||||
function fnModalUploadImportClose() {
|
||||
uploadImportState.open = false;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**对话框表格信息导入上传 */
|
||||
function fnModalUploadImportUpload(file: File) {
|
||||
const neUid = queryParams.neUid;
|
||||
if (!neUid) {
|
||||
return Promise.reject('Unknown network element');
|
||||
}
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
uploadImportState.loading = true;
|
||||
// 上传文件
|
||||
let formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('subPath', 'import');
|
||||
uploadFile(formData)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
return res.data.filePath;
|
||||
} else {
|
||||
uploadImportState.msg = res.msg;
|
||||
uploadImportState.loading = false;
|
||||
return '';
|
||||
}
|
||||
})
|
||||
.then((filePath: string) => {
|
||||
if (!filePath) return;
|
||||
// 文件导入
|
||||
return importUDMVoIP({
|
||||
neUid: neUid,
|
||||
uploadPath: filePath,
|
||||
});
|
||||
})
|
||||
.then(res => {
|
||||
if (!res) return;
|
||||
uploadImportState.msg = res.msg;
|
||||
const regex = /fail num: (\d+)/;
|
||||
const match = res.msg.match(regex);
|
||||
if (match) {
|
||||
const failNum = Number(match[1]);
|
||||
uploadImportState.hasFail = failNum > 0;
|
||||
} else {
|
||||
uploadImportState.hasFail = false;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
uploadImportState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框表格信息导入失败原因 */
|
||||
function fnModalUploadImportFailReason() {
|
||||
const neUid = queryParams.neUid;
|
||||
if (!neUid) return;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
getNeViewFile({
|
||||
neType: 'UDM',
|
||||
neUid: neUid,
|
||||
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);
|
||||
|
||||
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}/udm_voip_template.txt`,
|
||||
`import_udmvoip_template_${Date.now()}.txt`
|
||||
);
|
||||
|
||||
hide();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
neStore.getCoreDataNeCascaderOptions.forEach(item => {
|
||||
if (item.value === 'UDM') {
|
||||
neOtions.value = JSON.parse(JSON.stringify(item.children));
|
||||
}
|
||||
});
|
||||
if (neOtions.value.length === 0) {
|
||||
message.warning({
|
||||
content: t('common.noData'),
|
||||
duration: 2,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (neOtions.value.length > 0) {
|
||||
queryParams.neUid = neOtions.value[0].value;
|
||||
}
|
||||
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item label="UDM" name="neUid ">
|
||||
<a-select
|
||||
v-model:value="queryParams.neUid"
|
||||
:options="neOtions"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
:disabled="modalState.loadDataLoading"
|
||||
@change="fnGetList(1)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.neData.udmVoIP.username')"
|
||||
name="username"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.username"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList()">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon>
|
||||
<ClearOutlined />
|
||||
</template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
{{ t('common.addText') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.confirmLoading"
|
||||
@click.prevent="fnRecordDelete('0')"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('views.neData.common.checkDel') }}
|
||||
</a-button>
|
||||
|
||||
<a-dropdown trigger="click">
|
||||
<a-button>
|
||||
{{ t('views.neData.common.batchOper') }}
|
||||
<DownOutlined />
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key }:any) => fnModalVisibleByBatch(key)">
|
||||
<a-menu-item key="add">
|
||||
<PlusOutlined />
|
||||
{{ t('views.neData.common.batchAddText') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delete">
|
||||
<DeleteOutlined />
|
||||
{{ t('views.neData.common.batchDelText') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
|
||||
<a-popconfirm
|
||||
placement="topRight"
|
||||
:title="t('views.neData.common.loadDataConfirm')"
|
||||
:ok-text="t('common.ok')"
|
||||
:cancel-text="t('common.cancel')"
|
||||
:disabled="modalState.loadDataLoading"
|
||||
@confirm="fnLoadData"
|
||||
>
|
||||
<a-button
|
||||
type="dashed"
|
||||
danger
|
||||
:disabled="modalState.loadDataLoading"
|
||||
:loading="modalState.loadDataLoading"
|
||||
>
|
||||
<template #icon><SyncOutlined /></template>
|
||||
{{ t('views.neData.common.loadData') }}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
|
||||
<a-button type="dashed" @click.prevent="fnModalUploadImportOpen">
|
||||
<template #icon><ImportOutlined /></template>
|
||||
{{ t('common.import') }}
|
||||
</a-button>
|
||||
<a-popconfirm
|
||||
placement="topRight"
|
||||
:title="t('views.neData.udmVoIP.exportTip')"
|
||||
ok-text="TXT"
|
||||
ok-type="default"
|
||||
@confirm="fnExportList('txt')"
|
||||
>
|
||||
<a-button type="dashed">
|
||||
<template #icon><ExportOutlined /></template>
|
||||
{{ t('common.export') }}
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{
|
||||
tableState.seached
|
||||
? t('common.switch.show')
|
||||
: t('common.switch.hide')
|
||||
}}
|
||||
</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.searchBarText')"
|
||||
:un-checked-children="t('common.searchBarText')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<TableColumnsDnd
|
||||
cache-id="udmVoIPData"
|
||||
:columns="tableColumns"
|
||||
v-model:columns-dnd="tableColumnsDnd"
|
||||
></TableColumnsDnd>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown placement="bottomRight" trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">
|
||||
{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="username"
|
||||
:columns="tableColumnsDnd"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ y: 'calc(100vh - 480px)' }"
|
||||
@change="fnTableChange"
|
||||
@resizeColumn="(w:number, col:any) => (col.width = w)"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column?.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.deleteText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordDelete(record.username)"
|
||||
>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="520"
|
||||
:destroyOnClose="true"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:open="modalState.openByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<!--批量删除-->
|
||||
<template v-if="modalState.isBatch && modalState.type === 'delete'">
|
||||
<a-form-item
|
||||
:label="t('views.neData.common.batchNum')"
|
||||
name="num"
|
||||
v-bind="modalStateFrom.validateInfos.num"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.num"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="500"
|
||||
placeholder="<=500"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="
|
||||
modalState.isBatch
|
||||
? t('views.neData.udmVoIP.startUsername')
|
||||
: t('views.neData.udmVoIP.username')
|
||||
"
|
||||
name="username"
|
||||
v-bind="modalStateFrom.validateInfos.username"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.username"
|
||||
style="width: 100%"
|
||||
:min="4"
|
||||
:maxlength="16"
|
||||
:placeholder="t('views.neData.udmVoIP.username')"
|
||||
>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<!--批量数-->
|
||||
<a-form-item
|
||||
v-if="modalState.isBatch"
|
||||
:label="t('views.neData.common.batchNum')"
|
||||
name="num"
|
||||
v-bind="modalStateFrom.validateInfos.num"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.num"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="500"
|
||||
placeholder="<=500"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="
|
||||
modalState.isBatch
|
||||
? t('views.neData.udmVoIP.startUsername')
|
||||
: t('views.neData.udmVoIP.username')
|
||||
"
|
||||
name="username"
|
||||
v-bind="modalStateFrom.validateInfos.username"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.username"
|
||||
style="width: 100%"
|
||||
:maxlength="16"
|
||||
:placeholder="t('views.neData.udmVoIP.username')"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.neData.udmVoIP.password')"
|
||||
name="password"
|
||||
v-bind="modalStateFrom.validateInfos.password"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="modalState.from.password"
|
||||
style="width: 100%"
|
||||
:min="4"
|
||||
:max="64"
|
||||
:placeholder="t('views.neData.udmVoIP.password')"
|
||||
>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
|
||||
<!-- 上传导入表格数据文件框 -->
|
||||
<UploadModal
|
||||
:title="uploadImportState.title"
|
||||
:loading="uploadImportState.loading"
|
||||
@upload="fnModalUploadImportUpload"
|
||||
@close="fnModalUploadImportClose"
|
||||
v-model:open="uploadImportState.open"
|
||||
:ext="['.txt']"
|
||||
>
|
||||
<template #default>
|
||||
<a-row justify="space-between" align="middle">
|
||||
<a-col :span="12"> </a-col>
|
||||
<a-col>
|
||||
<a-button
|
||||
type="link"
|
||||
:title="t('views.neData.common.importTemplate')"
|
||||
@click.prevent="fnModalDownloadImportTemplate"
|
||||
>
|
||||
{{ t('views.neData.common.importTemplate') }}
|
||||
</a-button>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
1223
src/views/core-ne/ue/udm-volte/index.vue
Normal file
1223
src/views/core-ne/ue/udm-volte/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
86
src/views/index/index.vue
Normal file
86
src/views/index/index.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
defineAsyncComponent,
|
||||
onMounted,
|
||||
ref,
|
||||
shallowRef,
|
||||
type Component,
|
||||
} from 'vue';
|
||||
import { getConfigKey, changeValue } from '@/api/system/config';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import { APP_SERVER_TYPE_M } from '@/constants/app-constants';
|
||||
const appStore = useAppStore();
|
||||
const currentComponent = shallowRef<Component | null>(null);
|
||||
|
||||
const spinning = ref<boolean>(true);
|
||||
|
||||
/**匹配views里面所有的.vue或.tsx文件 */
|
||||
const views = import.meta.glob('../views/**/*.{vue,tsx}') as Record<
|
||||
string,
|
||||
() => Promise<Component>
|
||||
>;
|
||||
|
||||
/**
|
||||
* 查找页面模块
|
||||
* @param dirName 组件路径
|
||||
* @returns 路由懒加载函数
|
||||
*/
|
||||
function findView(dirName: string): () => Promise<Component> {
|
||||
for (const dir in views) {
|
||||
let viewDirName = '';
|
||||
const component = dir.match(/\/(.+)\.(vue|tsx)/);
|
||||
if (component && component.length === 3) {
|
||||
viewDirName = component[1];
|
||||
}
|
||||
if (viewDirName === dirName) {
|
||||
return views[dir];
|
||||
}
|
||||
}
|
||||
return () => import('@/views/ne/neOverview/index.vue');
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 应用-服务类型 多核心网
|
||||
if (appStore.serverType === APP_SERVER_TYPE_M) {
|
||||
currentComponent.value = defineAsyncComponent(
|
||||
() => import('./m/index.vue')
|
||||
);
|
||||
spinning.value = false;
|
||||
return;
|
||||
}
|
||||
//获取当前系统设置的首页路径
|
||||
getConfigKey('sys.homePage').then(res => {
|
||||
spinning.value = false;
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
if (res.data) {
|
||||
const asyncComponent = findView(`${res.data}`);
|
||||
currentComponent.value = defineAsyncComponent(asyncComponent);
|
||||
}
|
||||
} else {
|
||||
currentComponent.value = defineAsyncComponent(
|
||||
() => import('@/views/ne/neOverview/index.vue')
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-spin
|
||||
size="large"
|
||||
:spinning="spinning"
|
||||
style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
min-height: 362px;
|
||||
"
|
||||
>
|
||||
<component :is="currentComponent" />
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
399
src/views/index/m/index.vue
Normal file
399
src/views/index/m/index.vue
Normal file
@@ -0,0 +1,399 @@
|
||||
<script setup lang="ts">
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import {
|
||||
reactive,
|
||||
ref,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
markRaw,
|
||||
useTemplateRef,
|
||||
} from 'vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { TooltipComponent } from 'echarts/components';
|
||||
import { GaugeChart } from 'echarts/charts';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import * as echarts from 'echarts/core';
|
||||
import { TitleComponent, LegendComponent } from 'echarts/components';
|
||||
import { PieChart } from 'echarts/charts';
|
||||
import { LabelLayout } from 'echarts/features';
|
||||
import { useRoute } from 'vue-router';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useCoreStore from '@/store/modules/core';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { listAllNeInfo } from '@/api/ne/neInfo';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
const { getDict } = useDictStore();
|
||||
const appStore = useAppStore();
|
||||
const coreStore = useCoreStore();
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
echarts.use([
|
||||
TooltipComponent,
|
||||
GaugeChart,
|
||||
TitleComponent,
|
||||
LegendComponent,
|
||||
PieChart,
|
||||
CanvasRenderer,
|
||||
LabelLayout,
|
||||
]);
|
||||
|
||||
/**图DOM节点实例对象 */
|
||||
const statusBar = useTemplateRef<HTMLDivElement>('statusBar');
|
||||
|
||||
/**图实例对象 */
|
||||
const statusBarChart = ref<any>(null);
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.index.object'),
|
||||
dataIndex: 'neName',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
title: t('views.index.realNeStatus'),
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
key: 'status',
|
||||
},
|
||||
{
|
||||
title: t('views.index.reloadTime'),
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (opt.value?.refreshTime) {
|
||||
return parseDateToStr(opt.value?.refreshTime, 'HH:mm:ss');
|
||||
}
|
||||
return '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.version'),
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return opt.value?.version || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.serialNum'),
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return opt.value?.sn || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.expiryDate'),
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return opt.value?.expire || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.index.ipAddress'),
|
||||
dataIndex: 'serverState',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
return opt.value?.neIP || '-';
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**记录数据 */
|
||||
data: Record<string, any>[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**状态 */
|
||||
let serverState: any = ref({});
|
||||
|
||||
/**查询网元状态列表 */
|
||||
async function fnGetList(reload: boolean = false) {
|
||||
tableState.loading = !reload;
|
||||
try {
|
||||
const res = await listAllNeInfo({
|
||||
coreUid: coreStore.currentCoreUid,
|
||||
bandStatus: true,
|
||||
});
|
||||
tableState.data = res.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
tableState.data = [];
|
||||
}
|
||||
tableState.loading = false;
|
||||
if (tableState.data.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var rightNum = 0;
|
||||
var errorNum = 0;
|
||||
for (const v of tableState.data) {
|
||||
if (v?.serverState?.online) {
|
||||
rightNum++;
|
||||
} else {
|
||||
errorNum++;
|
||||
}
|
||||
}
|
||||
|
||||
// 初始
|
||||
if (!reload) {
|
||||
// 选择第一个
|
||||
if (tableState.data.length > 0) {
|
||||
const item = tableState.data.find((item: any) => item.status === 1);
|
||||
if (item) {
|
||||
const id = item.id;
|
||||
fnTableSelectedRowKeys([id]);
|
||||
}
|
||||
} else {
|
||||
fnTableSelectedRowKeys(tableState.selectedRowKeys);
|
||||
}
|
||||
|
||||
if (statusBar.value) {
|
||||
fnDesign(statusBar.value, rightNum, errorNum);
|
||||
}
|
||||
} else {
|
||||
statusBarChart.value.setOption({
|
||||
series: [
|
||||
{
|
||||
data: [
|
||||
{ value: rightNum, name: t('views.index.normal') },
|
||||
{ value: errorNum, name: t('views.index.abnormal') },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function fnDesign(container: HTMLElement, rightNum: number, errorNum: number) {
|
||||
/// 图表数据
|
||||
const optionData: any = {
|
||||
title: {
|
||||
text: '',
|
||||
subtext: '',
|
||||
left: 'center',
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
},
|
||||
color: dict.indexStatus.map(item => item.tagClass),
|
||||
series: [
|
||||
{
|
||||
name: t('views.index.runStatus'),
|
||||
type: 'pie',
|
||||
radius: '70%',
|
||||
center: ['50%', '50%'],
|
||||
data: [
|
||||
{ value: rightNum, name: t('views.index.normal') },
|
||||
{ value: errorNum, name: t('views.index.abnormal') },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
statusBarChart.value = markRaw(echarts.init(container, 'light'));
|
||||
statusBarChart.value.setOption(optionData);
|
||||
|
||||
// 创建 ResizeObserver 实例
|
||||
var observer = new ResizeObserver(entries => {
|
||||
if (statusBarChart.value) {
|
||||
statusBarChart.value.resize();
|
||||
}
|
||||
});
|
||||
// 监听元素大小变化
|
||||
observer.observe(container);
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
if (keys.length <= 0) return;
|
||||
const id = keys[0];
|
||||
const row: any = tableState.data.find((item: any) => item.id === id);
|
||||
if (!row) {
|
||||
message.error(t('views.index.neStatus'), 2);
|
||||
return;
|
||||
}
|
||||
const neState = row.serverState;
|
||||
if (!neState?.online) {
|
||||
message.error(t('views.index.neStatus'), 2);
|
||||
return;
|
||||
}
|
||||
tableState.selectedRowKeys = keys;
|
||||
// Mem 将KB转换为MB
|
||||
// const totalMemInKB = neState.mem?.totalMem;
|
||||
// const nfUsedMemInKB = neState.mem?.nfUsedMem;
|
||||
// const sysMemUsageInKB = neState.mem?.sysMemUsage;
|
||||
// const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100;
|
||||
// const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
|
||||
// const sysMemUsageInMB = Math.round((sysMemUsageInKB / 1024) * 100) / 100;
|
||||
|
||||
// CPU
|
||||
// const nfCpu = neState.cpu?.nfCpuUsage;
|
||||
// const sysCpu = neState.cpu?.sysCpuUsage;
|
||||
// const nfCpuP = Math.round(nfCpu) / 100;
|
||||
// const sysCpuP = Math.round(sysCpu) / 100;
|
||||
|
||||
serverState.value = Object.assign(
|
||||
{
|
||||
// cpuUse: `NE:${nfCpuP}%; SYS:${sysCpuP}%`,
|
||||
// memoryUse: `Total: ${totalMemInMB}MB; NE: ${nfUsedMemInMB}MB; SYS: ${sysMemUsageInMB}MB`,
|
||||
},
|
||||
neState
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化翻译转换
|
||||
*/
|
||||
function fnLocale() {
|
||||
let title = route.meta.title as string;
|
||||
if (title.indexOf('router.') !== -1) {
|
||||
title = t(title);
|
||||
}
|
||||
appStore.setTitle(title);
|
||||
}
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**网元信息状态 */
|
||||
neInfoStatus: DictType[];
|
||||
/**主页状态 */
|
||||
indexStatus: DictType[];
|
||||
} = reactive({
|
||||
neInfoStatus: [],
|
||||
indexStatus: [],
|
||||
});
|
||||
|
||||
let timer: any;
|
||||
let timerFlag: boolean = false;
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('ne_info_status'), getDict('index_status')])
|
||||
.then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.neInfoStatus = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.indexStatus = resArr[1].value;
|
||||
}
|
||||
})
|
||||
.finally(async () => {
|
||||
fnLocale();
|
||||
await fnGetList(false);
|
||||
timer = setInterval(() => {
|
||||
if (timerFlag) return;
|
||||
fnGetList(true);
|
||||
}, 10_000); // 每隔10秒执行一次
|
||||
});
|
||||
});
|
||||
|
||||
// 在组件卸载之前清除定时器
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
timerFlag = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer :breadcrumb="{}">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="14" :md="16" :xs="24">
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
size="small"
|
||||
:columns="tableColumns"
|
||||
:data-source="tableState.data"
|
||||
:loading="tableState.loading"
|
||||
:pagination="false"
|
||||
:scroll="{ x: true }"
|
||||
:row-selection="{
|
||||
type: 'radio',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dict.neInfoStatus" :value="record.status" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-col>
|
||||
<a-col :lg="10" :md="8" :xs="24">
|
||||
<a-card
|
||||
:title="t('views.index.runStatus')"
|
||||
style="margin-bottom: 16px"
|
||||
size="small"
|
||||
>
|
||||
<div style="width: 100%; min-height: 200px" ref="statusBar"></div>
|
||||
</a-card>
|
||||
<a-card
|
||||
:loading="tableState.loading"
|
||||
:title="`${t('views.index.mark')} - ${serverState.neName || 'OMC'}`"
|
||||
style="margin-top: 16px"
|
||||
size="small"
|
||||
>
|
||||
<a-descriptions
|
||||
bordered
|
||||
:column="1"
|
||||
:label-style="{ width: '160px' }"
|
||||
>
|
||||
<a-descriptions-item :label="t('views.index.hostName')">
|
||||
{{ serverState.hostname }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.osInfo')">
|
||||
{{ serverState.os }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.ipAddress')">
|
||||
{{ serverState.neIP }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.version')">
|
||||
{{ serverState.version }}
|
||||
</a-descriptions-item>
|
||||
<!-- <a-descriptions-item :label="t('views.index.capability')">
|
||||
{{ serverState.capability }}
|
||||
</a-descriptions-item> -->
|
||||
<!-- <a-descriptions-item :label="t('views.index.cpuUse')">
|
||||
{{ serverState.cpuUse }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.memoryUse')">
|
||||
{{ serverState.memoryUse }}
|
||||
</a-descriptions-item> -->
|
||||
<a-descriptions-item :label="t('views.index.serialNum')">
|
||||
{{ serverState.sn }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.index.expiryDate')">
|
||||
{{ serverState.expire }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
569
src/views/log/login/index.vue
Normal file
569
src/views/log/login/index.vue
Normal file
@@ -0,0 +1,569 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import {
|
||||
exportSysLogLogin,
|
||||
listSysLogLogin,
|
||||
delSysLogLogin,
|
||||
cleanSysLogLogin,
|
||||
unlock,
|
||||
} from '@/api/system/log/login';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**登录状态 */
|
||||
sysCommonStatus: DictType[];
|
||||
} = reactive({
|
||||
sysCommonStatus: [],
|
||||
});
|
||||
|
||||
/**开始结束时间 */
|
||||
let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>(undefined);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**登录地址 */
|
||||
loginIp: '',
|
||||
/**登录账号 */
|
||||
userName: '',
|
||||
/**登录状态 */
|
||||
statusFlag: undefined,
|
||||
/**记录开始时间 */
|
||||
beginTime: undefined as number | undefined,
|
||||
/**记录结束时间 */
|
||||
endTime: undefined as number | undefined,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
loginIp: '',
|
||||
userName: '',
|
||||
statusFlag: undefined,
|
||||
beginTime: undefined,
|
||||
endTime: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
queryRangePicker.value = undefined;
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
/**勾选单个的登录账号 */
|
||||
selectedUserName: string;
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
selectedUserName: '',
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.system.log.login.operId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.login.account'),
|
||||
dataIndex: 'userName',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.login.loginIp'),
|
||||
dataIndex: 'loginIp',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
// {
|
||||
// title: t('views.system.log.login.loginLoc'),
|
||||
// dataIndex: 'loginLocation',
|
||||
// align: 'left',
|
||||
// width: 150,
|
||||
// },
|
||||
{
|
||||
title: t('views.system.log.login.os'),
|
||||
dataIndex: 'os',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.login.browser'),
|
||||
dataIndex: 'browser',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.login.status'),
|
||||
dataIndex: 'statusFlag',
|
||||
key: 'statusFlag',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.login.loginTime'),
|
||||
dataIndex: 'loginTime',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.login.info'),
|
||||
dataIndex: 'msg',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRows(
|
||||
_: (string | number)[],
|
||||
rows: Record<string, string>[]
|
||||
) {
|
||||
tableState.selectedRowKeys = rows.map(item => item.id);
|
||||
// 针对单个登录账号解锁
|
||||
if (rows.length === 1) {
|
||||
tableState.selectedUserName = rows[0].userName;
|
||||
} else {
|
||||
tableState.selectedUserName = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**记录删除 */
|
||||
function fnRecordDelete() {
|
||||
const ids = tableState.selectedRowKeys.join(',');
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.system.log.login.delSure', { ids }),
|
||||
onOk() {
|
||||
const key = 'delSysLogLogin';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
delSysLogLogin(ids).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.deleteText') }),
|
||||
key,
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
fnGetList();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表清空 */
|
||||
function fnCleanList() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.system.log.login.delAllSure'),
|
||||
onOk() {
|
||||
const key = 'cleanSysLogLogin';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
cleanSysLogLogin().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('views.system.log.login.delAll'),
|
||||
}),
|
||||
key,
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
fnGetList();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**登录账号解锁 */
|
||||
function fnUnlock() {
|
||||
const username = tableState.selectedUserName;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.system.log.login.unlockSure', { username }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
unlock(username).then(res => {
|
||||
hide();
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.system.log.login.unlockSuss', {
|
||||
userName: username,
|
||||
}),
|
||||
duration: 3,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.system.user.exportSure'),
|
||||
onOk() {
|
||||
const key = 'exportSysLogLogin';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
exportSysLogLogin(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('views.system.user.export'),
|
||||
}),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `sys_log_login_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询登录日志列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
|
||||
// 时间范围
|
||||
if (
|
||||
Array.isArray(queryRangePicker.value) &&
|
||||
queryRangePicker.value.length > 0
|
||||
) {
|
||||
queryParams.beginTime = queryRangePicker.value[0].valueOf();
|
||||
queryParams.endTime = queryRangePicker.value[1].valueOf();
|
||||
} else {
|
||||
queryParams.beginTime = undefined;
|
||||
queryParams.endTime = undefined;
|
||||
}
|
||||
|
||||
listSysLogLogin(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('sys_common_status')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysCommonStatus = resArr[0].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.login.loginIp')"
|
||||
name="loginIp"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.loginIp"
|
||||
allow-clear
|
||||
:maxlength="128"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.login.account')"
|
||||
name="userName"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.userName"
|
||||
allow-clear
|
||||
:maxlength="30"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.login.status')"
|
||||
name="statusFlag"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="queryParams.statusFlag"
|
||||
allow-clear
|
||||
:options="dict.sysCommonStatus"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}</a-button
|
||||
>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.login.loginTime')"
|
||||
name="queryRangePicker"
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
:bordered="true"
|
||||
:allow-clear="true"
|
||||
style="width: 100%"
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!tableState.selectedUserName"
|
||||
@click.prevent="fnUnlock()"
|
||||
v-perms:has="['system:log:login:unlock']"
|
||||
>
|
||||
<template #icon><UnlockOutlined /></template>
|
||||
{{ t('views.system.log.login.unlock') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordDelete()"
|
||||
v-perms:has="['system:log:login:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('common.deleteText') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
danger
|
||||
@click.prevent="fnCleanList()"
|
||||
v-perms:has="['system:log:login:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('views.system.log.operate.delAll') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click.prevent="fnExportList()"
|
||||
v-perms:has="['system:log:login:export']"
|
||||
>
|
||||
<template #icon><ExportOutlined /></template>
|
||||
{{ t('common.export') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.switch.show')"
|
||||
:un-checked-children="t('common.switch.hide')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown placement="bottomRight" trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default"
|
||||
>{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle"
|
||||
>{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small"
|
||||
>{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:scroll="{ x: tableColumns.length * 180 }"
|
||||
:pagination="tablePagination"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRows,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'statusFlag'">
|
||||
<DictTag
|
||||
:options="dict.sysCommonStatus"
|
||||
:value="record.statusFlag"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
766
src/views/log/operation/index.vue
Normal file
766
src/views/log/operation/index.vue
Normal file
@@ -0,0 +1,766 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import {
|
||||
exportSysLogOperate,
|
||||
listSysLogOperate,
|
||||
delSysLogOperate,
|
||||
cleanSysLogOperate,
|
||||
} from '@/api/system/log/operate';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import type { Dayjs } from 'dayjs';
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**业务类型 */
|
||||
sysBusinessType: DictType[];
|
||||
/**登录状态 */
|
||||
sysCommonStatus: DictType[];
|
||||
} = reactive({
|
||||
sysBusinessType: [],
|
||||
sysCommonStatus: [],
|
||||
});
|
||||
|
||||
/**开始结束时间 */
|
||||
let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>(undefined);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**操作模块 */
|
||||
title: '',
|
||||
/**操作人员 */
|
||||
operaBy: '',
|
||||
/**业务类型 */
|
||||
businessType: undefined,
|
||||
/**操作状态 */
|
||||
statusFlag: undefined,
|
||||
/**记录开始时间 */
|
||||
beginTime: undefined as number | undefined,
|
||||
/**记录结束时间 */
|
||||
endTime: undefined as number | undefined,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
title: '',
|
||||
operaBy: '',
|
||||
businessType: undefined,
|
||||
statusFlag: undefined,
|
||||
beginTime: undefined,
|
||||
endTime: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
queryRangePicker.value = undefined;
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('views.system.log.operate.operId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.operate.moduleName'),
|
||||
dataIndex: 'title',
|
||||
align: 'left',
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.operate.workType'),
|
||||
dataIndex: 'businessType',
|
||||
key: 'businessType',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.operate.operUser'),
|
||||
dataIndex: 'operaBy',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
},
|
||||
// {
|
||||
// title: t('views.system.log.operate.requestMe'),
|
||||
// dataIndex: 'operaUrlMethod',
|
||||
// align: 'left',
|
||||
// width: 150,
|
||||
// },
|
||||
{
|
||||
title: t('views.system.log.operate.host'),
|
||||
dataIndex: 'operaIp',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.operate.operStatus'),
|
||||
dataIndex: 'statusFlag',
|
||||
key: 'statusFlag',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.operate.operDate'),
|
||||
dataIndex: 'operaTime',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
customRender(opt) {
|
||||
if (+opt.value <= 0) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('views.system.log.operate.useTime'),
|
||||
dataIndex: 'costTime',
|
||||
key: 'costTime',
|
||||
align: 'right',
|
||||
width: 100,
|
||||
customRender(opt) {
|
||||
return `${opt.value} ms`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(keys: (string | number)[]) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**详情框是否显示 */
|
||||
openByView: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByView: false,
|
||||
title: '操作日志',
|
||||
from: {
|
||||
id: undefined,
|
||||
businessType: 0,
|
||||
deptName: '',
|
||||
operaMethod: '',
|
||||
operaIp: '',
|
||||
operaLocation: '',
|
||||
operaMsg: '',
|
||||
operaBy: '',
|
||||
operaParam: '',
|
||||
operaTime: 0,
|
||||
operaUrl: '',
|
||||
operaUrlMethod: 'PUT',
|
||||
statusFlag: 1,
|
||||
title: '',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 查看
|
||||
* @param row 操作日志信息对象
|
||||
*/
|
||||
function fnModalVisibleByVive(row: Record<string, string>) {
|
||||
modalState.from = Object.assign(modalState.from, row);
|
||||
try {
|
||||
modalState.from.operaParam = JSON.stringify(
|
||||
JSON.parse(modalState.from.operaParam),
|
||||
null,
|
||||
4
|
||||
);
|
||||
} catch (_) {}
|
||||
try {
|
||||
modalState.from.operaMsg = JSON.stringify(
|
||||
JSON.parse(modalState.from.operaMsg),
|
||||
null,
|
||||
4
|
||||
);
|
||||
} catch (_) {}
|
||||
|
||||
modalState.title = t('views.system.log.operate.logInfo');
|
||||
modalState.openByView = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.openByView = false;
|
||||
}
|
||||
|
||||
/**记录删除 */
|
||||
function fnRecordDelete() {
|
||||
const ids = tableState.selectedRowKeys.join(',');
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.system.log.operate.delSure', { ids }),
|
||||
onOk() {
|
||||
const key = 'delSysLogOperate';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
delSysLogOperate(ids).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', { msg: t('common.deleteText') }),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表清空 */
|
||||
function fnCleanList() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.system.log.operate.delAllSure'),
|
||||
onOk() {
|
||||
const key = 'cleanSysLogOperate';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
cleanSysLogOperate().then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('views.system.log.operate.delAllSuss'),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
fnQueryReset();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**列表导出 */
|
||||
function fnExportList() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.system.user.exportSure'),
|
||||
onOk() {
|
||||
const key = 'exportSysLogOperate';
|
||||
message.loading({ content: t('common.loading'), key });
|
||||
exportSysLogOperate(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.msgSuccess', {
|
||||
msg: t('views.system.user.export'),
|
||||
}),
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `sys_log_operate_${Date.now()}.xlsx`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
key,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**查询登录日志列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
// 时间范围
|
||||
if (
|
||||
Array.isArray(queryRangePicker.value) &&
|
||||
queryRangePicker.value.length > 0
|
||||
) {
|
||||
queryParams.beginTime = queryRangePicker.value[0].valueOf();
|
||||
queryParams.endTime = queryRangePicker.value[1].valueOf();
|
||||
} else {
|
||||
queryParams.beginTime = undefined;
|
||||
queryParams.endTime = undefined;
|
||||
}
|
||||
listSysLogOperate(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('sys_oper_type'),
|
||||
getDict('sys_common_status'),
|
||||
]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.sysBusinessType = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.sysCommonStatus = resArr[1].value;
|
||||
}
|
||||
});
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.operModule')"
|
||||
name="title"
|
||||
>
|
||||
<a-input v-model:value="queryParams.title" allow-clear></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.operUser')"
|
||||
name="operaBy"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.operaBy"
|
||||
allow-clear
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.workType')"
|
||||
name="businessType"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="queryParams.businessType"
|
||||
allow-clear
|
||||
:options="dict.sysBusinessType"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.operStatus')"
|
||||
name="statusFlag"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="queryParams.statusFlag"
|
||||
allow-clear
|
||||
:options="dict.sysCommonStatus"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.operTime')"
|
||||
name="queryRangePicker"
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:value="queryRangePicker"
|
||||
:bordered="true"
|
||||
:allow-clear="true"
|
||||
style="width: 100%"
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
></a-range-picker>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="8" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}</a-button
|
||||
>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}</a-button
|
||||
>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
@click.prevent="fnRecordDelete()"
|
||||
v-perms:has="['system:log:operate:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('common.deleteText') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
danger
|
||||
@click.prevent="fnCleanList()"
|
||||
v-perms:has="['system:log:operate:remove']"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('views.system.log.operate.delAll') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click.prevent="fnExportList()"
|
||||
v-perms:has="['system:log:operate:export']"
|
||||
>
|
||||
<template #icon><ExportOutlined /></template>
|
||||
{{ t('common.export') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.switch.show')"
|
||||
:un-checked-children="t('common.switch.hide')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown placement="bottomRight" trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default"
|
||||
>{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle"
|
||||
>{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small"
|
||||
>{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:scroll="{ x: tableColumns.length * 150 }"
|
||||
:pagination="tablePagination"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'businessType'">
|
||||
<DictTag
|
||||
:options="dict.sysBusinessType"
|
||||
:value="record.businessType"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'statusFlag'">
|
||||
<DictTag
|
||||
:options="dict.sysCommonStatus"
|
||||
:value="record.statusFlag"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.viewText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByVive(record)"
|
||||
v-perms:has="['system:log:operate:query']"
|
||||
>
|
||||
<template #icon><ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
:open="modalState.openByView"
|
||||
:title="modalState.title"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form layout="horizontal" :label-col="{ span: 6 }" :label-wrap="true">
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.operId')"
|
||||
name="id"
|
||||
>
|
||||
{{ modalState.from.id }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.operStatus')"
|
||||
name="statusFlag"
|
||||
>
|
||||
<a-tag :color="+modalState.from.statusFlag ? 'success' : 'error'">
|
||||
{{
|
||||
[
|
||||
t('views.system.log.operate.fail'),
|
||||
t('views.system.log.operate.suss'),
|
||||
][+modalState.from.statusFlag]
|
||||
}}
|
||||
</a-tag>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.workType')"
|
||||
name="businessType"
|
||||
>
|
||||
{{ modalState.from.title }} /
|
||||
<DictTag
|
||||
:options="dict.sysBusinessType"
|
||||
:value="modalState.from.businessType"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.operUser')"
|
||||
name="operaBy"
|
||||
>
|
||||
{{ modalState.from.operaBy }} / {{ modalState.from.operaIp }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.RequestIp')"
|
||||
name="operaUrl"
|
||||
>
|
||||
{{ modalState.from.operaUrlMethod }} -
|
||||
{{ modalState.from.operaUrl }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.operTime')"
|
||||
name="operaTime"
|
||||
>
|
||||
<span v-if="+modalState.from.operaTime > 0">
|
||||
{{ parseDateToStr(+modalState.from.operaTime) }}
|
||||
</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.useTime')"
|
||||
name="costTime"
|
||||
>
|
||||
{{ modalState.from.costTime }} ms
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<!-- <a-form-item
|
||||
:label="t('views.system.log.operate.operMe')"
|
||||
name="operaMethod"
|
||||
>
|
||||
{{ modalState.from.operaMethod }}
|
||||
</a-form-item> -->
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.reqParam')"
|
||||
name="operaParam"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.operaParam"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
:disabled="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.system.log.operate.operInfo')"
|
||||
name="operaMsg"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.operaMsg"
|
||||
:auto-size="{ minRows: 2, maxRows: 6 }"
|
||||
:disabled="true"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<template #footer>
|
||||
<a-button key="cancel" @click="fnModalCancel">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
</template>
|
||||
</ProModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
@@ -508,7 +508,7 @@ function fnAutoCompleteKeydown(evt: any) {
|
||||
|
||||
onMounted(() => {
|
||||
// 获取网元网元列表
|
||||
neStore.neCascaderOptions.forEach(item => {
|
||||
neStore.getCoreDataNeCascaderOptions.forEach(item => {
|
||||
if (item.value === 'UDM') {
|
||||
neOptions.value = JSON.parse(JSON.stringify(item.children));
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ const modalStateFrom = Form.useForm(
|
||||
coreUid: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.ne.neInfo.oam.kpiTimerPlease'),
|
||||
message: t('common.selectPlease'),
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -72,7 +72,12 @@ const modalStateFrom = Form.useForm(
|
||||
function fnModalVisible() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
modalState.from.neUid = props.neUid;
|
||||
if (props.coreUid.length === 8) {
|
||||
modalState.from.coreUid = props.coreUid;
|
||||
} else {
|
||||
modalState.from.coreUid = undefined;
|
||||
}
|
||||
modalState.select = 'Associated';
|
||||
modalState.openByEdit = true;
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
@@ -122,6 +127,7 @@ function fnModalOk() {
|
||||
function fnModalCancel() {
|
||||
modalState.openByEdit = false;
|
||||
modalState.confirmLoading = false;
|
||||
modalState.select = 'Associated';
|
||||
modalStateFrom.resetFields();
|
||||
emit('cancel');
|
||||
emit('update:open', false);
|
||||
@@ -131,7 +137,6 @@ function fnModalCancel() {
|
||||
watch(
|
||||
() => props.open,
|
||||
val => {
|
||||
console.log('CoreModal open', val, props.neUid, props.coreUid);
|
||||
if (val && props.neUid) {
|
||||
fnModalVisible();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Modal } from 'ant-design-vue/es';
|
||||
import { defineAsyncComponent, onMounted, onUnmounted, reactive } from 'vue';
|
||||
import { fnRestStepState, stepState } from '../hooks/useStep';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { codeNeLicense, stateNeLicense } from '@/api/ne/neLicense';
|
||||
import { getNeInfo, codeNeLicense } from '@/api/ne/neInfo';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
const { t } = useI18n();
|
||||
const EditModal = defineAsyncComponent(
|
||||
@@ -60,10 +60,10 @@ function fnModalOk(e: any) {
|
||||
return;
|
||||
}
|
||||
if (state.timeCount % 5 === 0) {
|
||||
stateNeLicense(e.neId).then(res => {
|
||||
getNeInfo(stepState.neInfo.id).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
state.from.sn = res.data.sn;
|
||||
state.from.expire = res.data.expire;
|
||||
state.from.sn = res.data.serialNum;
|
||||
state.from.expire = res.data.expiryDate;
|
||||
state.from.ueNumber = res.data.ueNumber;
|
||||
state.from.nbNumber = res.data.ueNumber;
|
||||
|
||||
@@ -95,15 +95,15 @@ function fnStepEnd() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const { neUid } = stepState.neInfo;
|
||||
if (neUid) {
|
||||
const { id, neUid } = stepState.neInfo;
|
||||
if (id && neUid) {
|
||||
state.from.neUid = neUid;
|
||||
state.confirmLoading = true;
|
||||
stateNeLicense(neUid)
|
||||
getNeInfo(id)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
state.from.sn = res.data.sn;
|
||||
state.from.expire = res.data.expire;
|
||||
state.from.sn = res.data.serialNum;
|
||||
state.from.expire = res.data.expiryDate;
|
||||
} else {
|
||||
return codeNeLicense(neUid);
|
||||
}
|
||||
|
||||
448
src/views/ne/quick-setup/components/NeInfoConfig.vue
Normal file
448
src/views/ne/quick-setup/components/NeInfoConfig.vue
Normal file
@@ -0,0 +1,448 @@
|
||||
<script setup lang="ts">
|
||||
import { Form, Modal, message } from 'ant-design-vue/es';
|
||||
import { onMounted, reactive, toRaw } from 'vue';
|
||||
import { addNeInfo, getNeInfo } from '@/api/ne/neInfo';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { NE_TYPE_LIST } from '@/constants/ne-constants';
|
||||
import { regExpIPv4, regExpIPv6 } from '@/utils/regular-utils';
|
||||
import { fnRestStepState, fnToStepName, stepState } from '../hooks/useStep';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
const { getDict } = useDictStore();
|
||||
const neStore = useNeStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**主机类型 */
|
||||
neHostType: DictType[];
|
||||
/**分组 */
|
||||
neHostGroupId: DictType[];
|
||||
/**认证模式 */
|
||||
neHostAuthMode: DictType[];
|
||||
} = reactive({
|
||||
neHostType: [],
|
||||
neHostGroupId: [],
|
||||
neHostAuthMode: [],
|
||||
});
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**是否可以下一步 */
|
||||
stepNext: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
stepNext: false,
|
||||
title: '网元',
|
||||
from: {
|
||||
id: undefined,
|
||||
coreId: undefined,
|
||||
neUid: '',
|
||||
neType: '',
|
||||
neName: '',
|
||||
ipAddr: '',
|
||||
port: 33030,
|
||||
pvFlag: 'PNF',
|
||||
macAddr: '',
|
||||
dn: '-',
|
||||
vendorName: '-',
|
||||
province: 'Area',
|
||||
// 主机
|
||||
hosts: [
|
||||
{
|
||||
id: undefined,
|
||||
hostType: 'ssh',
|
||||
groupId: '1',
|
||||
title: 'SSH_NE_22',
|
||||
addr: '',
|
||||
port: 22,
|
||||
user: 'omcuser',
|
||||
authMode: '2',
|
||||
password: '',
|
||||
privateKey: '',
|
||||
passPhrase: '',
|
||||
remark: '',
|
||||
},
|
||||
{
|
||||
id: undefined,
|
||||
hostType: 'telnet',
|
||||
groupId: '1',
|
||||
title: 'Telnet_NE_4100',
|
||||
addr: '',
|
||||
port: 4100,
|
||||
user: 'admin',
|
||||
authMode: '0',
|
||||
password: 'admin',
|
||||
remark: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
neType: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.ne.common.neTypePlease'),
|
||||
},
|
||||
],
|
||||
neName: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.ne.common.neNamePlease'),
|
||||
},
|
||||
],
|
||||
ipAddr: [
|
||||
{
|
||||
required: true,
|
||||
validator: modalStateFromEqualIPV4AndIPV6,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**表单验证IP地址是否有效 */
|
||||
function modalStateFromEqualIPV4AndIPV6(
|
||||
rule: Record<string, any>,
|
||||
value: string,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
if (!value) {
|
||||
return Promise.reject(t('views.ne.common.ipAddrPlease'));
|
||||
}
|
||||
|
||||
if (value.indexOf('.') === -1 && value.indexOf(':') === -1) {
|
||||
return Promise.reject(t('valid.ipPlease'));
|
||||
}
|
||||
if (value.indexOf('.') !== -1 && !regExpIPv4.test(value)) {
|
||||
return Promise.reject(t('valid.ipv4Reg'));
|
||||
}
|
||||
if (value.indexOf(':') !== -1 && !regExpIPv6.test(value)) {
|
||||
return Promise.reject(t('valid.ipv6Reg'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
const from = toRaw(modalState.from);
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(() => {
|
||||
// 清除更新必要信息
|
||||
from.id = undefined;
|
||||
from.hostIds = '';
|
||||
for (let index = 0; index < from.hosts.length; index++) {
|
||||
from.hosts[index].id = undefined;
|
||||
}
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
addNeInfo(from)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: `${t('common.operateOk')}`,
|
||||
duration: 3,
|
||||
});
|
||||
return res.data;
|
||||
}
|
||||
message.error({
|
||||
content: res.msg,
|
||||
duration: 3,
|
||||
});
|
||||
return 0;
|
||||
})
|
||||
.then(neId => {
|
||||
if (neId === 0) {
|
||||
return;
|
||||
}
|
||||
neStore.fnNelistRefresh(); // 刷新缓存的网元信息
|
||||
getNeInfo(neId).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
stepState.neInfo = res.data; // 保存网元信息
|
||||
modalState.stepNext = true; // 开启下一步
|
||||
} else {
|
||||
message.error({
|
||||
content: res.msg,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单修改网元类型
|
||||
*/
|
||||
function fnNeTypeChange(v: any) {
|
||||
// 网元默认只含22和4100
|
||||
if (modalState.from.hosts.length === 3) {
|
||||
modalState.from.hosts.pop();
|
||||
}
|
||||
const hostsLen = modalState.from.hosts.length;
|
||||
// UPF标准版本可支持5002
|
||||
if (hostsLen === 2 && v === 'UPF') {
|
||||
modalState.from.hosts.push({
|
||||
id: undefined,
|
||||
hostType: 'telnet',
|
||||
groupId: '1',
|
||||
title: 'Telnet_NE_5002',
|
||||
addr: modalState.from.ip,
|
||||
port: 5002,
|
||||
user: 'admin',
|
||||
authMode: '0',
|
||||
password: 'admin',
|
||||
remark: '',
|
||||
});
|
||||
}
|
||||
// UDM可支持6379
|
||||
if (hostsLen === 2 && v === 'UDM') {
|
||||
modalState.from.hosts.push({
|
||||
id: undefined,
|
||||
hostType: 'redis',
|
||||
groupId: '1',
|
||||
title: 'REDIS_NE_6379',
|
||||
addr: modalState.from.ip,
|
||||
port: 6379,
|
||||
user: 'udmdb',
|
||||
authMode: '0',
|
||||
password: 'helloearth',
|
||||
dbName: '0',
|
||||
remark: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表单修改网元IP
|
||||
*/
|
||||
function fnNeIPChange(e: any) {
|
||||
const v = e.target.value;
|
||||
if (v.length < 7) return;
|
||||
for (const host of modalState.from.hosts) {
|
||||
host.addr = v;
|
||||
}
|
||||
}
|
||||
|
||||
/**返回上一步 */
|
||||
function fnStepPrev() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neQuickSetup.stepPrevTip'),
|
||||
onOk() {
|
||||
fnRestStepState(t);
|
||||
fnToStepName('Start');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**下一步操作 */
|
||||
function fnStepNext() {
|
||||
if (!modalState.stepNext) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neQuickSetup.configStepNext'),
|
||||
onOk() {
|
||||
fnToStepName('NeInfoSoftwareInstall');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([
|
||||
getDict('ne_host_type'),
|
||||
getDict('ne_host_groupId'),
|
||||
getDict('ne_host_authMode'),
|
||||
])
|
||||
.then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.neHostType = resArr[0].value;
|
||||
}
|
||||
if (resArr[1].status === 'fulfilled') {
|
||||
dict.neHostGroupId = resArr[1].value;
|
||||
}
|
||||
if (resArr[2].status === 'fulfilled') {
|
||||
dict.neHostAuthMode = resArr[2].value;
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (stepState.neInfo.id) {
|
||||
Object.assign(modalState.from, stepState.neInfo);
|
||||
} else {
|
||||
modalState.from.ipAddr = stepState.neHost.addr;
|
||||
Object.assign(modalState.from.hosts[0], stepState.neHost);
|
||||
Object.assign(modalState.from.hosts[1], {
|
||||
addr: modalState.from.ipAddr,
|
||||
user: 'admin',
|
||||
password: 'admin',
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ne">
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 3 }"
|
||||
:wrapper-col="{ span: 9 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<a-form-item
|
||||
:label="t('views.ne.common.ipAddr')"
|
||||
name="ip"
|
||||
v-bind="modalStateFrom.validateInfos.ipAddr"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.ipAddr"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="128"
|
||||
@change="fnNeIPChange"
|
||||
:disabled="true"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
<div>
|
||||
{{ t('views.ne.common.ipAddrTip') }}
|
||||
</div>
|
||||
</template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.common.port')"
|
||||
name="port"
|
||||
v-bind="modalStateFrom.validateInfos.port"
|
||||
:help="t('views.ne.common.portTip')"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="modalState.from.port"
|
||||
style="width: 100%"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
:maxlength="5"
|
||||
placeholder="<=65535"
|
||||
>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.common.neType')"
|
||||
name="neType"
|
||||
v-bind="modalStateFrom.validateInfos.neType"
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="modalState.from.neType"
|
||||
:options="
|
||||
NE_TYPE_LIST.filter(s => s !== 'OMC').map(v => ({ value: v }))
|
||||
"
|
||||
@change="fnNeTypeChange"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="32"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.ne.common.neTypeTip') }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-auto-complete>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.common.neName')"
|
||||
name="neName"
|
||||
v-bind="modalStateFrom.validateInfos.neName"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.neName"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="24"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<div class="ne-oper">
|
||||
<a-space direction="horizontal" :size="18">
|
||||
<a-button @click="fnStepPrev()">
|
||||
{{ t('views.ne.neQuickSetup.stepPrev') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
@click="fnModalOk()"
|
||||
:loading="modalState.confirmLoading"
|
||||
>
|
||||
{{ t('views.ne.neQuickSetup.stepSave') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="fnStepNext()"
|
||||
:disabled="!modalState.stepNext"
|
||||
>
|
||||
{{ t('views.ne.neQuickSetup.stepNext') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ne {
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& .ant-form {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-oper {
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
433
src/views/ne/quick-setup/components/NeInfoSoftwareInstall.vue
Normal file
433
src/views/ne/quick-setup/components/NeInfoSoftwareInstall.vue
Normal file
@@ -0,0 +1,433 @@
|
||||
<script setup lang="ts">
|
||||
import { Modal, message } from 'ant-design-vue/es';
|
||||
import { defineAsyncComponent, onMounted, reactive, toRaw } from 'vue';
|
||||
import { fnToStepName, stepState } from '../hooks/useStep';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { operateNeVersion } from '@/api/ne/neVersion';
|
||||
import {
|
||||
RESULT_CODE_ERROR,
|
||||
RESULT_CODE_SUCCESS,
|
||||
} from '@/constants/result-constants';
|
||||
import { listNeSoftware, newNeVersion } from '@/api/ne/neSoftware';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { ColumnsType } from 'ant-design-vue/es/table';
|
||||
const { t } = useI18n();
|
||||
const EditModal = defineAsyncComponent(
|
||||
() => import('@/views/ne/neSoftware/components/EditModal.vue')
|
||||
);
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns: ColumnsType = [
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.neType'),
|
||||
dataIndex: 'neType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neSoftware.version'),
|
||||
dataIndex: 'version',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neSoftware.name'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'left',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
title: t('common.description'),
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
dataIndex: 'createTime',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(+opt.value);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 10,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 10,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
tableState.queryParams.pageNum = page;
|
||||
tableState.queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**查询参数 */
|
||||
queryParams: Record<string, any>;
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
queryParams: {
|
||||
neType: '',
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
loading: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
});
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(
|
||||
keys: (string | number)[],
|
||||
selectedRows: any[]
|
||||
) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
// 选择的表单数据填充
|
||||
Object.assign(state.from, selectedRows[0], { id: undefined });
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
tableState.queryParams.pageNum = pageNum;
|
||||
}
|
||||
listNeSoftware(toRaw(tableState.queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**对象信息信息状态类型 */
|
||||
type StateType = {
|
||||
/**是否可以下一步 */
|
||||
stepNext: boolean;
|
||||
/**文件操作类型 上传 or 选择 */
|
||||
optionType: 'upload' | 'option';
|
||||
/**文件上传 */
|
||||
openByFile: boolean;
|
||||
/**网元拓展包列表类型 */
|
||||
depType: string[];
|
||||
/**软件包信息数据 */
|
||||
from: {
|
||||
neType: string; // 版本需要
|
||||
neUid: string; // 版本需要
|
||||
name: string; // 软件需要
|
||||
path: string;
|
||||
version: string; // 软件需要
|
||||
description: string;
|
||||
};
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对象信息状态 */
|
||||
let state: StateType = reactive({
|
||||
stepNext: false,
|
||||
optionType: 'option',
|
||||
openByFile: false,
|
||||
depType: [],
|
||||
from: {
|
||||
id: undefined,
|
||||
neType: '',
|
||||
neUid: '',
|
||||
name: '',
|
||||
path: '',
|
||||
version: '',
|
||||
description: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* 表单修改文件操作类型
|
||||
*/
|
||||
function fnOptionTypeChange() {
|
||||
state.from.name = '';
|
||||
state.from.version = '';
|
||||
tableState.selectedRowKeys = [];
|
||||
if (state.optionType === 'option') {
|
||||
fnGetList(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**对话框弹出 */
|
||||
function fnModalOpen() {
|
||||
state.openByFile = !state.openByFile;
|
||||
}
|
||||
|
||||
/**对话框弹出确认执行函数*/
|
||||
function fnModalOk(e: any) {
|
||||
Object.assign(state.from, e, { id: undefined });
|
||||
}
|
||||
|
||||
/**对话框弹出关闭执行函数*/
|
||||
function fnModalCancel() {
|
||||
state.openByFile = false;
|
||||
}
|
||||
|
||||
/**版本安装 */
|
||||
function fnRecordInstall() {
|
||||
const from = toRaw(state.from);
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neQuickSetup.installConfirmTip', {
|
||||
name: from.name,
|
||||
}),
|
||||
onOk: async () => {
|
||||
if (state.confirmLoading) return;
|
||||
state.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
|
||||
// 选择的软件需要更新版本
|
||||
if (state.optionType === 'option') {
|
||||
const res = await newNeVersion(from);
|
||||
if (res.code === RESULT_CODE_ERROR) {
|
||||
message.error(res.msg, 3);
|
||||
hide();
|
||||
state.confirmLoading = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 进行安装
|
||||
let preinput = {};
|
||||
if (from.neType.toUpperCase() === 'IMS') {
|
||||
preinput = { pisCSCF: 'y', updateMFetc: 'No', updateMFshare: 'No' };
|
||||
}
|
||||
const res = await operateNeVersion({
|
||||
neUid: from.neUid,
|
||||
neType: from.neType,
|
||||
action: 'install',
|
||||
preinput: preinput,
|
||||
});
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.operateOk'), 3);
|
||||
state.stepNext = true;
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
|
||||
// 非选择的重置
|
||||
state.optionType = 'option';
|
||||
fnOptionTypeChange();
|
||||
hide();
|
||||
state.confirmLoading = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**返回上一步 */
|
||||
function fnStepPrev() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neQuickSetup.stepPrevTip'),
|
||||
onOk() {
|
||||
fnToStepName('NeInfoConfig');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**下一步操作 */
|
||||
function fnStepNext() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neQuickSetup.installStepNext'),
|
||||
onOk() {
|
||||
fnToStepName('NeInfoSoftwareLicense');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const { neType, neUid } = stepState.neInfo;
|
||||
if (neType && neUid) {
|
||||
tableState.queryParams.neType = neType;
|
||||
state.from.neUid = neUid;
|
||||
state.from.neType = neType;
|
||||
fnGetList(1);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ne">
|
||||
<a-form
|
||||
name="installStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-form-item
|
||||
:label="t('views.ne.neQuickSetup.installSource')"
|
||||
name="optionType"
|
||||
>
|
||||
<a-radio-group
|
||||
v-model:value="state.optionType"
|
||||
button-style="solid"
|
||||
:disabled="state.confirmLoading"
|
||||
@change="fnOptionTypeChange"
|
||||
>
|
||||
<a-radio-button value="option">
|
||||
{{ t('views.ne.neQuickSetup.installSourceOption') }}
|
||||
</a-radio-button>
|
||||
<a-radio-button value="upload">
|
||||
{{ t('views.ne.neQuickSetup.installSourceUpload') }}
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 选择已上传 -->
|
||||
<template v-if="state.optionType === 'option'">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neQuickSetup.installSelect')"
|
||||
name="option"
|
||||
>
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:pagination="tablePagination"
|
||||
size="small"
|
||||
:scroll="{ x: tableColumns.length * 100, y: '400px' }"
|
||||
:row-selection="{
|
||||
type: 'radio',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>{{ record.path }}</template>
|
||||
<div style="cursor: pointer">{{ record.name }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'description'">
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>{{ record.description }}</template>
|
||||
<div style="cursor: pointer">{{ record.description }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 重新上传 -->
|
||||
<template v-if="state.optionType === 'upload'">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neQuickSetup.installUpload')"
|
||||
name="upload"
|
||||
:help="state.from.name"
|
||||
>
|
||||
<a-button type="primary" @click.prevent="fnModalOpen()">
|
||||
<template #icon><UploadOutlined /></template>
|
||||
{{ t('views.ne.neSoftware.upload') }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
|
||||
<!-- 文件上传框 -->
|
||||
<EditModal
|
||||
v-model:open="state.openByFile"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
></EditModal>
|
||||
|
||||
<div class="ne-oper">
|
||||
<a-space direction="horizontal" :size="18">
|
||||
<a-button @click="fnStepPrev()">
|
||||
{{ t('views.ne.neQuickSetup.stepPrev') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
:disabled="!state.from.version"
|
||||
:loading="state.confirmLoading"
|
||||
@click.prevent="fnRecordInstall()"
|
||||
>
|
||||
<template #icon><ThunderboltOutlined /></template>
|
||||
{{ t('views.ne.neQuickSetup.installText') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="fnStepNext()"
|
||||
:disabled="!state.stepNext"
|
||||
>
|
||||
{{ t('views.ne.neQuickSetup.stepNext') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ne {
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& .ant-form {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-oper {
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
188
src/views/ne/quick-setup/components/NeInfoSoftwareLicense.vue
Normal file
188
src/views/ne/quick-setup/components/NeInfoSoftwareLicense.vue
Normal file
@@ -0,0 +1,188 @@
|
||||
<script setup lang="ts">
|
||||
import { Modal } from 'ant-design-vue/es';
|
||||
import { defineAsyncComponent, onMounted, onUnmounted, reactive } from 'vue';
|
||||
import { fnRestStepState, stepState } from '../hooks/useStep';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { getNeInfo, codeNeLicense } from '@/api/ne/neInfo';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
const { t } = useI18n();
|
||||
const EditModal = defineAsyncComponent(
|
||||
() => import('@/views/ne/neLicense/components/EditModal.vue')
|
||||
);
|
||||
|
||||
/**对象信息信息状态类型 */
|
||||
type StateType = {
|
||||
/**文件上传 */
|
||||
openByFile: boolean;
|
||||
/**授权信息数据 */
|
||||
from: {
|
||||
neUid: string;
|
||||
neType: string;
|
||||
// 下面是状态检查结果
|
||||
expire: string;
|
||||
sn: string;
|
||||
ueNumber: string;
|
||||
nbNumber: string;
|
||||
};
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**定时调度 */
|
||||
timeInterval: any;
|
||||
timeCount: number;
|
||||
};
|
||||
|
||||
/**对象信息状态 */
|
||||
let state: StateType = reactive({
|
||||
openByFile: false,
|
||||
from: {
|
||||
neUid: '',
|
||||
neType: '',
|
||||
// 下面是状态检查结果
|
||||
expire: '',
|
||||
sn: '',
|
||||
ueNumber: '',
|
||||
nbNumber: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
timeInterval: null,
|
||||
timeCount: 30,
|
||||
});
|
||||
|
||||
/**对话框弹出确认执行函数*/
|
||||
function fnModalOk(e: any) {
|
||||
state.timeInterval = setInterval(() => {
|
||||
if (state.timeCount <= 0) {
|
||||
state.from.sn = '';
|
||||
state.from.expire = '';
|
||||
clearInterval(state.timeInterval);
|
||||
state.timeInterval = null;
|
||||
state.timeCount = 30;
|
||||
return;
|
||||
}
|
||||
if (state.timeCount % 5 === 0) {
|
||||
getNeInfo(stepState.neInfo.id).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
state.from.sn = res.data.serialNum;
|
||||
state.from.expire = res.data.expiryDate;
|
||||
state.from.ueNumber = res.data.ueNumber;
|
||||
state.from.nbNumber = res.data.ueNumber;
|
||||
|
||||
clearInterval(state.timeInterval);
|
||||
state.timeInterval = null;
|
||||
state.timeCount = 30;
|
||||
}
|
||||
});
|
||||
}
|
||||
state.timeCount--;
|
||||
}, 1_000);
|
||||
}
|
||||
|
||||
/**对话框弹出关闭执行函数*/
|
||||
function fnModalCancel() {
|
||||
state.openByFile = false;
|
||||
state.confirmLoading = false;
|
||||
}
|
||||
|
||||
/**结束操作 */
|
||||
function fnStepEnd() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neQuickSetup.licenseEndTip'),
|
||||
onOk() {
|
||||
fnRestStepState(t);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const { id, neUid } = stepState.neInfo;
|
||||
if (id && neUid) {
|
||||
state.from.neUid = neUid;
|
||||
state.confirmLoading = true;
|
||||
getNeInfo(id)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS && res.data) {
|
||||
state.from.sn = res.data.serialNum;
|
||||
state.from.expire = res.data.expiryDate;
|
||||
} else {
|
||||
return codeNeLicense(neUid);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
state.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(state.timeInterval);
|
||||
state.timeInterval = null;
|
||||
state.timeCount = 30;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-result
|
||||
:status="!state.from.sn ? 'info' : 'success'"
|
||||
:title="
|
||||
t(
|
||||
!state.from.sn
|
||||
? 'views.ne.neQuickSetup.licenseResultTitle'
|
||||
: 'views.ne.neQuickSetup.licenseResultTitleOk'
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="state.from.sn !== ''"
|
||||
:loading="state.timeCount < 30 || state.confirmLoading"
|
||||
@click="() => (state.openByFile = !state.openByFile)"
|
||||
>
|
||||
{{ t('views.ne.neQuickSetup.licenseUpload') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
:disabled="state.timeCount < 30 || state.confirmLoading"
|
||||
@click="fnStepEnd()"
|
||||
>
|
||||
{{ t('views.ne.neQuickSetup.licenseEnd') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-if="
|
||||
state.timeInterval === null && state.timeCount === 30 && !state.from.sn
|
||||
"
|
||||
>
|
||||
<p>{{ t('views.ne.neQuickSetup.licenseTip1') }}</p>
|
||||
<p>{{ t('views.ne.neQuickSetup.licenseTip2') }}</p>
|
||||
</div>
|
||||
<div v-if="state.timeInterval !== null">
|
||||
<a-space direction="horizontal" :size="16">
|
||||
<a-spin />
|
||||
{{ t('views.ne.neQuickSetup.licenseCheack') }} {{ state.timeCount }}s
|
||||
</a-space>
|
||||
</div>
|
||||
<div v-if="state.from.sn !== ''" style="font-size: 16px">
|
||||
<p>{{ t('views.ne.common.neType') }}:{{ state.from.neType }}</p>
|
||||
<p>{{ t('views.ne.common.neUid') }}:{{ state.from.neUid }}</p>
|
||||
<p>{{ t('views.ne.common.serialNum') }}:{{ state.from.sn }}</p>
|
||||
<p>{{ t('views.ne.common.expiryDate') }}:{{ state.from.expire }}</p>
|
||||
<p>{{ t('views.ne.common.ueNumber') }}:{{ state.from.ueNumber }}</p>
|
||||
<p>{{ t('views.ne.common.nbNumber') }}:{{ state.from.nbNumber }}</p>
|
||||
</div>
|
||||
</a-result>
|
||||
|
||||
<!-- 许可证上传框 -->
|
||||
<EditModal
|
||||
v-model:open="state.openByFile"
|
||||
:ne-type="state.from.neType"
|
||||
:ne-uid="state.from.neUid"
|
||||
:reload="true"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
></EditModal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
564
src/views/ne/quick-setup/components/Para5GForm.vue
Normal file
564
src/views/ne/quick-setup/components/Para5GForm.vue
Normal file
@@ -0,0 +1,564 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['update:data']);
|
||||
const props = defineProps({
|
||||
/**表单数据 */
|
||||
data: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
/**根据网元显示配置项 */
|
||||
ne: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
amf: false,
|
||||
upf: false,
|
||||
ims: false,
|
||||
mme: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
/**表单信息状态 */
|
||||
let fromState = ref({
|
||||
basic: {
|
||||
plmnId: {
|
||||
mcc: '001',
|
||||
mnc: '01',
|
||||
},
|
||||
tac: '4388',
|
||||
snssai: {
|
||||
sst: '1',
|
||||
sd: '000001',
|
||||
},
|
||||
dnn_data: 'internet',
|
||||
dnn_ims: 'ims',
|
||||
},
|
||||
external: {
|
||||
amfn2_ip: '192.168.8.120',
|
||||
upfn3_ip: '192.168.8.190/24',
|
||||
upfn3_gw: '192.168.1.1',
|
||||
upfn6_ip: '192.168.8.191/24',
|
||||
upfn6_gw: '192.168.1.1',
|
||||
ue_pool: '10.2.1.0/24',
|
||||
// 非指定属性
|
||||
mmes1_ip: '192.168.8.220/20',
|
||||
mmes10_ip: '172.16.5.221/24',
|
||||
mmes11_ip: '172.16.5.220/24',
|
||||
ims_sip_ip: '192.168.8.110',
|
||||
upf_type: 'LightUPF',
|
||||
upf_driver_type: 'vmxnet3',
|
||||
upfn3_card_name: 'eth0',
|
||||
upfn3_pci: '0000:00:00.0',
|
||||
upfn3_mac: '00:00:00:00:00:00',
|
||||
upfn6_card_name: 'eth0',
|
||||
upfn6_pci: '0000:00:00.0',
|
||||
upfn6_mac: '00:00:00:00:00:00',
|
||||
},
|
||||
sbi: {
|
||||
omc_ip: '172.16.5.100',
|
||||
ims_ip: '172.16.5.110',
|
||||
amf_ip: '172.16.5.120',
|
||||
ausf_ip: '172.16.5.130',
|
||||
udm_ip: '172.16.5.140',
|
||||
db_ip: '0.0.0.0',
|
||||
smf_ip: '172.16.5.150',
|
||||
pcf_ip: '172.16.5.160',
|
||||
nssf_ip: '172.16.5.170',
|
||||
nrf_ip: '172.16.5.180',
|
||||
upf_ip: '172.16.5.190',
|
||||
lmf_ip: '172.16.5.200',
|
||||
nef_ip: '172.16.5.210',
|
||||
mme_ip: '172.16.5.220',
|
||||
n3iwf_ip: '172.16.5.230',
|
||||
smsc_ip: '172.16.5.240',
|
||||
},
|
||||
});
|
||||
|
||||
/**监听数据 */
|
||||
watch(
|
||||
() => fromState,
|
||||
val => {
|
||||
if (val) emit('update:data', val);
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-form
|
||||
name="para5GFileeFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 8 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-row>
|
||||
<a-col :lg="16" :md="16" :xs="24">
|
||||
<a-divider orientation="left">Basic</a-divider>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="DNN_DATA" name="basic.dnn_data">
|
||||
<a-input
|
||||
v-model:value="fromState.basic.dnn_data"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> DNN </template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="MCC" name="basic.plmnId.mcc">
|
||||
<a-input
|
||||
v-model:value="fromState.basic.plmnId.mcc"
|
||||
placeholder="1-65535"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="SST" name="basic.snssai.sst">
|
||||
<a-input-number
|
||||
v-model:value="fromState.basic.snssai.sst"
|
||||
:min="1"
|
||||
:max="3"
|
||||
placeholder="1-3"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> 1-3 </template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
<a-form-item label="TAC" name="basic.tac">
|
||||
<a-input
|
||||
v-model:value="fromState.basic.tac"
|
||||
placeholder="1-65535"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="DNN_IMS" name="basic.dnn_ims">
|
||||
<a-input
|
||||
v-model:value="fromState.basic.dnn_ims"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="MNC" name="basic.plmnId.mnc">
|
||||
<a-input
|
||||
v-model:value="fromState.basic.plmnId.mnc"
|
||||
placeholder="1-65535"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="SD" name="basic.snssai.sd">
|
||||
<a-input
|
||||
v-model:value="fromState.basic.snssai.sd"
|
||||
placeholder="1-65535"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="UE_POOL" name="external.ue_pool">
|
||||
<a-input
|
||||
v-model:value="fromState.external.ue_pool"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> UE IP and mask </template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="8" :md="8" :xs="24">
|
||||
<a-divider orientation="left">OMC</a-divider>
|
||||
<a-row>
|
||||
<a-col :lg="24" :md="24" :xs="24">
|
||||
<a-form-item
|
||||
label="OMC_IP"
|
||||
name="sbi.omc_ip"
|
||||
:required="true"
|
||||
:validate-on-rule-change="false"
|
||||
:validateTrigger="[]"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="fromState.sbi.omc_ip"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
Network Elemment send data tu EMS IP
|
||||
</template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<template v-if="props.ne.amf">
|
||||
<a-divider orientation="left">AMF</a-divider>
|
||||
<a-row>
|
||||
<a-col :lg="24" :md="24" :xs="24">
|
||||
<a-form-item label="N2_IP" name="external.amfn2_ip">
|
||||
<a-input
|
||||
v-model:value="fromState.external.amfn2_ip"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> AMF N2 </template>
|
||||
<InfoCircleOutlined
|
||||
style="opacity: 0.45; color: inherit"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="16" :md="16" :xs="24" v-if="props.ne.upf">
|
||||
<a-divider orientation="left">UPF</a-divider>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
label="UPF_TYPE"
|
||||
name="external.upf_type"
|
||||
help="Install of Standard or Light"
|
||||
>
|
||||
<a-select
|
||||
v-model:value="fromState.external.upf_type"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
>
|
||||
<a-select-option value="StandardUPF">
|
||||
StandardUPF
|
||||
</a-select-option>
|
||||
<a-select-option value="LightUPF">LightUPF</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
:lg="12"
|
||||
:md="12"
|
||||
:xs="24"
|
||||
v-if="fromState.external.upf_type === 'StandardUPF'"
|
||||
>
|
||||
<a-form-item label="DRIVER_TYPE" name="external.upf_driver_type">
|
||||
<a-select
|
||||
v-model:value="fromState.external.upf_driver_type"
|
||||
:placeholder="t('common.selectPlease')"
|
||||
>
|
||||
<a-select-option value="vmxnet3">vmxnet3</a-select-option>
|
||||
<a-select-option value="dpdk">dpdk</a-select-option>
|
||||
<a-select-option value="host">host</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row v-if="fromState.external.upf_type === 'LightUPF'">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="N3_IP" name="external.upfn3_ip">
|
||||
<a-input
|
||||
v-model:value="fromState.external.upfn3_ip"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> netwrok ip </template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="N3_GW" name="external.upfn3_gw">
|
||||
<a-input
|
||||
v-model:value="fromState.external.upfn3_gw"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> geteway </template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row v-if="fromState.external.upf_type === 'StandardUPF'">
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="N3_IP" name="external.upfn3_ip">
|
||||
<a-input
|
||||
v-model:value="fromState.external.upfn3_ip"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> netwrok ip </template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="N3_GW" name="external.upfn3_gw">
|
||||
<a-input
|
||||
v-model:value="fromState.external.upfn3_gw"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> geteway </template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="N3_PCI" name="external.upfn3_pci">
|
||||
<a-input
|
||||
v-model:value="fromState.external.upfn3_pci"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
use `ip a` show info, to inet name
|
||||
<br />
|
||||
or <br />
|
||||
use `lshw -class network -businfo` show device name
|
||||
</template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="N3_MAC" name="external.upfn3_mac">
|
||||
<a-input
|
||||
v-model:value="fromState.external.upfn3_mac"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
use `ip a` show info, to inet ip
|
||||
</template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="N3_NIC_NAME" name="external.upfn3_card_name">
|
||||
<a-input
|
||||
v-model:value="fromState.external.upfn3_card_name"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
use `ip a` show info, to inet name
|
||||
<br />
|
||||
or <br />
|
||||
use `lshw -class network -businfo` show device name
|
||||
</template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item label="N6_IP" name="external.upfn6_ip">
|
||||
<a-input
|
||||
v-model:value="fromState.external.upfn6_ip"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="N6_GW" name="external.upfn6_gw">
|
||||
<a-input
|
||||
v-model:value="fromState.external.upfn6_gw"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="N6_PCI" name="external.upfn6_pci">
|
||||
<a-input
|
||||
v-model:value="fromState.external.upfn6_pci"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="N6_MAC" name="external.upfn6_mac">
|
||||
<a-input
|
||||
v-model:value="fromState.external.upfn6_mac"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="N6_NIC_NAME" name="external.upfn6_card_name">
|
||||
<a-input
|
||||
v-model:value="fromState.external.upfn6_card_name"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
use `ip a` show info, to inet name
|
||||
<br />
|
||||
or <br />
|
||||
use `lshw -class network -businfo` show device name
|
||||
</template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
|
||||
<a-col :lg="8" :md="8" :xs="24">
|
||||
<template v-if="props.ne.ims">
|
||||
<a-divider orientation="left">IMS</a-divider>
|
||||
<a-row>
|
||||
<a-col :lg="24" :md="24" :xs="24">
|
||||
<a-form-item label="SIP_IP" name="external.ims_sip_ip">
|
||||
<a-input
|
||||
v-model:value="fromState.external.ims_sip_ip"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> IMS SIP </template>
|
||||
<InfoCircleOutlined
|
||||
style="opacity: 0.45; color: inherit"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<template v-if="props.ne.mme">
|
||||
<a-divider orientation="left">MME</a-divider>
|
||||
<a-row>
|
||||
<a-col :lg="24" :md="24" :xs="24">
|
||||
<a-form-item label="S1_IP" name="external.mmes1_ip">
|
||||
<a-input
|
||||
v-model:value="fromState.external.mmes1_ip"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
MME binded interface for S1-C or S1-MME communication
|
||||
(S1AP), can be ethernet interface, virtual ethernet
|
||||
interface, we don't advise wireless interfaces
|
||||
</template>
|
||||
<InfoCircleOutlined
|
||||
style="opacity: 0.45; color: inherit"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="S10_IP" name="external.mmes10_ip">
|
||||
<a-input
|
||||
v-model:value="fromState.external.mmes10_ip"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title> Interface </template>
|
||||
<InfoCircleOutlined
|
||||
style="opacity: 0.45; color: inherit"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="S11_IP" name="external.mmes11_ip">
|
||||
<a-input
|
||||
v-model:value="fromState.external.mmes11_ip"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="50"
|
||||
>
|
||||
<template #prefix>
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
MME binded interface for S11-U communication (GTPV1-U)
|
||||
</template>
|
||||
<InfoCircleOutlined
|
||||
style="opacity: 0.45; color: inherit"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
496
src/views/ne/quick-setup/components/Start.vue
Normal file
496
src/views/ne/quick-setup/components/Start.vue
Normal file
@@ -0,0 +1,496 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw } from 'vue';
|
||||
import { message, Form, Modal } from 'ant-design-vue/es';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { regExpIPv4, regExpIPv6 } from '@/utils/regular-utils';
|
||||
import { neHostAuthorizedRSA, neHostCheckInfo } from '@/api/ne/neHost';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import { fnToStepName, stepState } from '../hooks/useStep';
|
||||
const { getDict } = useDictStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
/**字典数据 */
|
||||
let dict: {
|
||||
/**认证模式 */
|
||||
neHostAuthMode: DictType[];
|
||||
} = reactive({
|
||||
neHostAuthMode: [],
|
||||
});
|
||||
|
||||
/**对象信息状态类型 */
|
||||
type StateType = {
|
||||
/**服务器信息 */
|
||||
info: Record<string, any>;
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**是否可以下一步 */
|
||||
stepNext: boolean;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对象信息状态 */
|
||||
let state: StateType = reactive({
|
||||
info: {
|
||||
addr: '0.0.0.0',
|
||||
kernelName: '-',
|
||||
kernelRelease: '-',
|
||||
machine: '-',
|
||||
nodename: '-',
|
||||
prettyName: '-',
|
||||
sshLink: false,
|
||||
sudo: false,
|
||||
},
|
||||
from: {
|
||||
id: undefined,
|
||||
hostType: 'ssh',
|
||||
groupId: '1',
|
||||
title: 'SSH_NE_22',
|
||||
addr: '',
|
||||
port: 22,
|
||||
user: '',
|
||||
authMode: '2',
|
||||
password: '',
|
||||
privateKey: '',
|
||||
passPhrase: '',
|
||||
remark: '',
|
||||
},
|
||||
stepNext: false,
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**表单属性和校验规则 */
|
||||
const checkStateFrom = Form.useForm(
|
||||
state.from,
|
||||
reactive({
|
||||
addr: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 128,
|
||||
validator: modalStateFromEqualIPV4AndIPV6,
|
||||
},
|
||||
],
|
||||
port: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.ne.neHost.portPlease'),
|
||||
},
|
||||
],
|
||||
user: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 50,
|
||||
message: t('views.ne.neHost.userPlease'),
|
||||
},
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 128,
|
||||
message: t('views.ne.neHost.passwordPlease'),
|
||||
},
|
||||
],
|
||||
privateKey: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 3000,
|
||||
message: t('views.ne.neHost.privateKeyPlease'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**表单验证IP地址是否有效 */
|
||||
function modalStateFromEqualIPV4AndIPV6(
|
||||
rule: Record<string, any>,
|
||||
value: string,
|
||||
callback: (error?: string) => void
|
||||
) {
|
||||
if (!value) {
|
||||
return Promise.reject(t('views.ne.common.ipAddrPlease'));
|
||||
}
|
||||
|
||||
if (value.indexOf('.') === -1 && value.indexOf(':') === -1) {
|
||||
return Promise.reject(t('valid.ipPlease'));
|
||||
}
|
||||
if (value.indexOf('.') !== -1 && !regExpIPv4.test(value)) {
|
||||
return Promise.reject(t('valid.ipv4Reg'));
|
||||
}
|
||||
if (value.indexOf(':') !== -1 && !regExpIPv6.test(value)) {
|
||||
return Promise.reject(t('valid.ipv6Reg'));
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**测试连接检查信息 */
|
||||
function fnCheckInfo() {
|
||||
if (state.confirmLoading) return;
|
||||
const form = toRaw(state.from);
|
||||
const validateArr = ['addr', 'port', 'user'];
|
||||
if (form.authMode === '0') {
|
||||
validateArr.push('password');
|
||||
}
|
||||
if (form.authMode === '1') {
|
||||
validateArr.push('privateKey');
|
||||
}
|
||||
|
||||
state.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
checkStateFrom
|
||||
.validate(validateArr)
|
||||
.then(() => {
|
||||
Object.assign(state.info, {
|
||||
addr: '0.0.0.0',
|
||||
kernelName: '-',
|
||||
kernelRelease: '-',
|
||||
machine: '-',
|
||||
nodename: '-',
|
||||
prettyName: '-',
|
||||
sshLink: false,
|
||||
sudo: false,
|
||||
});
|
||||
return neHostCheckInfo(form);
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
state.info = res.data;
|
||||
if (!res.data.sudo) {
|
||||
message.warning(t('views.ne.neQuickSetup.sudoErr'), 3);
|
||||
return;
|
||||
}
|
||||
// if (!res.data.sshLink) {
|
||||
// message.warning({
|
||||
// content: `请配置服务器间免密信任关系,确保服务器间文件传输功能`,
|
||||
// duration: 2,
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
stepState.neHost = form; // 保存主机连接信息
|
||||
state.stepNext = true; // 开启下一步
|
||||
message.success({
|
||||
content: `${form.addr}:${form.port} ${t('views.ne.neHost.testOk')}`,
|
||||
duration: 2,
|
||||
});
|
||||
} else {
|
||||
message.error({
|
||||
content: `${form.addr}:${form.port} ${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
state.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**测试连接检查信息表单重置 */
|
||||
function fnCheckInfoReset() {
|
||||
Object.assign(state.info, {
|
||||
addr: '0.0.0.0',
|
||||
kernelName: '-',
|
||||
kernelRelease: '-',
|
||||
machine: '-',
|
||||
nodename: '-',
|
||||
prettyName: '-',
|
||||
sshLink: false,
|
||||
sudo: false,
|
||||
});
|
||||
state.stepNext = false;
|
||||
checkStateFrom.resetFields();
|
||||
}
|
||||
|
||||
/**测试主机连接-免密直连 */
|
||||
function fnHostAuthorized() {
|
||||
if (state.confirmLoading) return;
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neHost.authRSATip'),
|
||||
onOk: () => {
|
||||
const form = toRaw(state.from);
|
||||
state.confirmLoading = true;
|
||||
neHostAuthorizedRSA(form).then(res => {
|
||||
state.confirmLoading = false;
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success(t('common.operateOk'), 3);
|
||||
} else {
|
||||
message.error(t('common.operateErr'), 3);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**返回上一步 */
|
||||
function fnStepPrev() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neQuickSetup.stepPrevTip'),
|
||||
onOk() {
|
||||
fnToStepName('Para5G');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**下一步操作 */
|
||||
function fnStepNext() {
|
||||
if (!state.stepNext) return;
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neQuickSetup.startStepNext'),
|
||||
onOk() {
|
||||
fnToStepName('NeInfoConfig');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
Promise.allSettled([getDict('ne_host_authMode')]).then(resArr => {
|
||||
if (resArr[0].status === 'fulfilled') {
|
||||
dict.neHostAuthMode = resArr[0].value;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ne">
|
||||
<a-descriptions :column="{ lg: 3, md: 2, sm: 2, xs: 1 }" bordered>
|
||||
<a-descriptions-item :label="t('views.ne.neQuickSetup.addr')" :span="3">
|
||||
{{ state.info.addr }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.ne.neQuickSetup.kernelName')">
|
||||
{{ state.info.kernelName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.ne.neQuickSetup.machine')">
|
||||
{{ state.info.machine }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.ne.neQuickSetup.kernelRelease')">
|
||||
{{ state.info.kernelRelease }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item>
|
||||
<template #label>
|
||||
{{ t('views.ne.neQuickSetup.prettyName') }}
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>
|
||||
{{ t('views.ne.neQuickSetup.prettyNameTip') }}
|
||||
</template>
|
||||
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
{{ state.info.prettyName }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.ne.neQuickSetup.nodename')">
|
||||
{{ state.info.nodename }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item :label="t('views.ne.neQuickSetup.auth')">
|
||||
<a-tag :color="state.info.sudo ? 'success' : 'error'">
|
||||
<template #icon>
|
||||
<CheckCircleOutlined v-if="state.info.sudo" />
|
||||
<CloseCircleOutlined v-else />
|
||||
</template>
|
||||
{{ t('views.ne.neQuickSetup.sudo') }}
|
||||
</a-tag>
|
||||
|
||||
<a-tag :color="state.info.sshLink ? 'success' : 'error'">
|
||||
<template #icon>
|
||||
<CheckCircleOutlined v-if="state.info.sshLink" />
|
||||
<CloseCircleOutlined v-else />
|
||||
</template>
|
||||
{{ t('views.ne.neQuickSetup.sshLink') }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-form
|
||||
name="checkStateFrom"
|
||||
layout="horizontal"
|
||||
:label-col="{ span: 6 }"
|
||||
:label-wrap="true"
|
||||
style="margin-top: 20px; width: 68%"
|
||||
>
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.addr')"
|
||||
name="addr"
|
||||
v-bind="checkStateFrom.validateInfos.addr"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.from.addr"
|
||||
allow-clear
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.port')"
|
||||
name="port"
|
||||
v-bind="checkStateFrom.validateInfos.port"
|
||||
>
|
||||
<a-input-number
|
||||
v-model:value="state.from.port"
|
||||
:min="10"
|
||||
:max="65535"
|
||||
:step="1"
|
||||
:maxlength="5"
|
||||
style="width: 100%"
|
||||
></a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.user')"
|
||||
name="user"
|
||||
v-bind="checkStateFrom.validateInfos.user"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="state.from.user"
|
||||
allow-clear
|
||||
:maxlength="32"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="12" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.neHost.authMode')">
|
||||
<a-select
|
||||
v-model:value="state.from.authMode"
|
||||
default-value="0"
|
||||
:options="dict.neHostAuthMode"
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item
|
||||
v-if="state.from.authMode === '0'"
|
||||
:label="t('views.ne.neHost.password')"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
name="password"
|
||||
v-bind="checkStateFrom.validateInfos.password"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.from.password"
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
|
||||
<template v-if="state.from.authMode === '1'">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.privateKey')"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
name="privateKey"
|
||||
v-bind="checkStateFrom.validateInfos.privateKey"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="state.from.privateKey"
|
||||
:auto-size="{ minRows: 4, maxRows: 6 }"
|
||||
:maxlength="3000"
|
||||
:show-count="true"
|
||||
:placeholder="t('views.ne.neHost.privateKeyPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neHost.passPhrase')"
|
||||
:label-col="{ span: 3 }"
|
||||
:label-wrap="true"
|
||||
>
|
||||
<a-input-password
|
||||
v-model:value="state.from.passPhrase"
|
||||
:maxlength="128"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item :wrapper-col="{ span: 8, offset: 3 }">
|
||||
<a-space direction="horizontal" :size="18">
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
html-type="submit"
|
||||
@click="fnCheckInfo()"
|
||||
:loading="state.confirmLoading"
|
||||
>
|
||||
{{ t('views.ne.neHost.test') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="dashed"
|
||||
@click="fnHostAuthorized()"
|
||||
:disabled="state.confirmLoading"
|
||||
v-if="state.from.authMode !== '2'"
|
||||
>
|
||||
{{ t('views.ne.neHost.authRSA') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="link"
|
||||
@click="fnCheckInfoReset()"
|
||||
:disabled="state.confirmLoading"
|
||||
>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<div class="ne-oper">
|
||||
<a-space direction="horizontal" :size="18">
|
||||
<a-button @click="fnStepPrev()">
|
||||
{{ t('views.ne.neQuickSetup.stepPrev') }}
|
||||
</a-button>
|
||||
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="fnStepNext()"
|
||||
:disabled="!state.stepNext"
|
||||
>
|
||||
{{ t('views.ne.neQuickSetup.stepNext') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ne {
|
||||
min-height: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& .ant-form {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&-oper {
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
141
src/views/ne/quick-setup/hooks/usePara5G.ts
Normal file
141
src/views/ne/quick-setup/hooks/usePara5G.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { reactive, toRaw } from 'vue';
|
||||
import { updateNeInfo } from '@/api/ne/neInfo';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { getPara5GFilee, savePara5GFile } from '@/api/ne/neAction';
|
||||
const neStore = useNeStore();
|
||||
|
||||
/**对象信息信息状态类型 */
|
||||
type StateType = {
|
||||
/**表单数据 */
|
||||
from: Record<string, any>;
|
||||
/**OMC信息,需修改当前的IP */
|
||||
omcInfo: Record<string, any>;
|
||||
/**根据网元显示配置项 */
|
||||
hasNE: {
|
||||
amf: boolean;
|
||||
upf: boolean;
|
||||
ims: boolean;
|
||||
mme: boolean;
|
||||
};
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
export function usePara5G() {
|
||||
/**对象信息状态 */
|
||||
let state: StateType = reactive({
|
||||
from: {},
|
||||
omcInfo: {},
|
||||
hasNE: {
|
||||
amf: false,
|
||||
upf: false,
|
||||
ims: false,
|
||||
mme: false,
|
||||
},
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**载入数据*/
|
||||
function fnReloadData() {
|
||||
state.confirmLoading = true;
|
||||
Promise.all([getPara5GFilee(), neStore.fnNelistRefresh()]).then(resArr => {
|
||||
// 已保存的配置
|
||||
if (resArr[0].code === RESULT_CODE_SUCCESS) {
|
||||
Object.assign(state.from, resArr[0].data);
|
||||
}
|
||||
// 填充固定网元类型的ip
|
||||
if (
|
||||
resArr[1].code === RESULT_CODE_SUCCESS &&
|
||||
Array.isArray(resArr[1].data)
|
||||
) {
|
||||
for (const item of resArr[1].data) {
|
||||
const ipAddr = item.ipAddr;
|
||||
// 公共配置文件sbi的各网元IP
|
||||
switch (item.neType) {
|
||||
case 'OMC':
|
||||
state.from.sbi.omc_ip = ipAddr;
|
||||
Object.assign(state.omcInfo, item); // 主动改OMC_IP
|
||||
break;
|
||||
case 'IMS':
|
||||
state.from.sbi.ims_ip = ipAddr;
|
||||
state.hasNE.ims = true;
|
||||
break;
|
||||
case 'AMF':
|
||||
state.from.sbi.amf_ip = ipAddr;
|
||||
state.hasNE.amf = true;
|
||||
break;
|
||||
case 'AUSF':
|
||||
state.from.sbi.ausf_ip = ipAddr;
|
||||
break;
|
||||
case 'UDM':
|
||||
state.from.sbi.udm_ip = ipAddr;
|
||||
state.from.sbi.db_ip = '0.0.0.0';
|
||||
break;
|
||||
case 'SMF':
|
||||
state.from.sbi.smf_ip = ipAddr;
|
||||
break;
|
||||
case 'PCF':
|
||||
state.from.sbi.pcf_ip = ipAddr;
|
||||
break;
|
||||
case 'NSSF':
|
||||
state.from.sbi.nssf_ip = ipAddr;
|
||||
break;
|
||||
case 'NRF':
|
||||
state.from.sbi.nrf_ip = ipAddr;
|
||||
break;
|
||||
case 'UPF':
|
||||
state.from.sbi.upf_ip = ipAddr;
|
||||
state.hasNE.upf = true;
|
||||
break;
|
||||
case 'LMF':
|
||||
state.from.sbi.lmf_ip = ipAddr;
|
||||
break;
|
||||
case 'NEF':
|
||||
state.from.sbi.nef_ip = ipAddr;
|
||||
break;
|
||||
case 'MME':
|
||||
state.from.sbi.mme_ip = ipAddr;
|
||||
if (ipAddr.includes('.')) {
|
||||
state.from.external.mmes11_ip = ipAddr + '/24';
|
||||
}
|
||||
state.hasNE.mme = true;
|
||||
break;
|
||||
case 'N3IWF':
|
||||
state.from.sbi.n3iwf_ip = ipAddr;
|
||||
break;
|
||||
case 'SMSC':
|
||||
state.from.sbi.smsc_ip = ipAddr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
state.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**保存数据 */
|
||||
async function fnSaveData() {
|
||||
if (state.confirmLoading) return;
|
||||
state.confirmLoading = true;
|
||||
const res = await savePara5GFile({
|
||||
content: toRaw(state.from),
|
||||
syncNe: [],
|
||||
});
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 更新omc_ip
|
||||
if (state.omcInfo.id) {
|
||||
state.omcInfo.ip = state.from.sbi.omc_ip;
|
||||
await updateNeInfo(toRaw(state.omcInfo));
|
||||
}
|
||||
}
|
||||
state.confirmLoading = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
fnReloadData,
|
||||
fnSaveData,
|
||||
};
|
||||
}
|
||||
103
src/views/ne/quick-setup/hooks/useStep.ts
Normal file
103
src/views/ne/quick-setup/hooks/useStep.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { defineAsyncComponent, reactive, shallowRef, watch } from 'vue';
|
||||
|
||||
/**步骤信息状态类型 */
|
||||
type StepStateType = {
|
||||
/**步骤名称 */
|
||||
stepName: string;
|
||||
/**安装网元步骤信息 */
|
||||
steps: any[];
|
||||
/**安装网元步骤当前选中 */
|
||||
current: number;
|
||||
/**连接主机 */
|
||||
neHost: Record<string, any>;
|
||||
/**网元信息 */
|
||||
neInfo: Record<string, any>;
|
||||
};
|
||||
|
||||
/**步骤信息状态 */
|
||||
export const stepState: StepStateType = reactive({
|
||||
stepName: 'Para5G',
|
||||
steps: [
|
||||
{
|
||||
title: '服务器环境',
|
||||
description: '服务端与网元服务',
|
||||
},
|
||||
{
|
||||
title: '配置网元信息',
|
||||
description: '网元信息设置',
|
||||
},
|
||||
{
|
||||
title: '网元软件安装',
|
||||
description: '软件安装到服务端',
|
||||
},
|
||||
{
|
||||
title: '网元授权激活',
|
||||
description: '网元服务授权激活',
|
||||
},
|
||||
],
|
||||
current: -1,
|
||||
neHost: {},
|
||||
neInfo: {},
|
||||
});
|
||||
|
||||
/**步骤信息状态复位 */
|
||||
export function fnRestStepState(t?: any) {
|
||||
stepState.stepName = 'Para5G';
|
||||
stepState.current = -1;
|
||||
stepState.neHost = {};
|
||||
stepState.neInfo = {};
|
||||
|
||||
// 多语言翻译
|
||||
if (t) {
|
||||
stepState.steps = [
|
||||
{
|
||||
title: t('views.ne.neQuickSetup.startTitle'),
|
||||
description: t('views.ne.neQuickSetup.startDesc'),
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neQuickSetup.configTitle'),
|
||||
description: t('views.ne.neQuickSetup.configDesc'),
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neQuickSetup.installTitle'),
|
||||
description: t('views.ne.neQuickSetup.installDesc'),
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neQuickSetup.licenseTitle'),
|
||||
description: t('views.ne.neQuickSetup.licenseDesc'),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**跳转步骤组件 */
|
||||
export function fnToStepName(stepName: string) {
|
||||
stepState.current = [
|
||||
'Start',
|
||||
'NeInfoConfig',
|
||||
'NeInfoSoftwareInstall',
|
||||
'NeInfoSoftwareLicense',
|
||||
].indexOf(stepName);
|
||||
|
||||
stepState.stepName = stepName;
|
||||
}
|
||||
|
||||
export function useStep({ t }: any) {
|
||||
// 异步加载组件
|
||||
const Start = defineAsyncComponent(() => import('../components/Start.vue'));
|
||||
|
||||
// 当前组件
|
||||
const currentComponent = shallowRef(Start);
|
||||
|
||||
watch(
|
||||
() => stepState.stepName,
|
||||
v => {
|
||||
const loadComponent = defineAsyncComponent(
|
||||
() => import(`../components/${v}.vue`)
|
||||
);
|
||||
currentComponent.value = loadComponent;
|
||||
}
|
||||
);
|
||||
|
||||
return { currentComponent };
|
||||
}
|
||||
90
src/views/ne/quick-setup/index.vue
Normal file
90
src/views/ne/quick-setup/index.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<script lang="ts" setup>
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import Para5GForm from './components/Para5GForm.vue';
|
||||
import {
|
||||
stepState,
|
||||
fnToStepName,
|
||||
fnRestStepState,
|
||||
useStep,
|
||||
} from './hooks/useStep';
|
||||
import { usePara5G } from './hooks/usePara5G';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { onMounted, onUnmounted, watch } from 'vue';
|
||||
const { t } = useI18n();
|
||||
const { currentComponent } = useStep(t);
|
||||
const { state, fnReloadData, fnSaveData } = usePara5G();
|
||||
|
||||
watch(
|
||||
() => stepState.stepName,
|
||||
v => {
|
||||
if (v === 'Para5G') {
|
||||
fnReloadData();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
fnRestStepState(t);
|
||||
fnReloadData();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
fnRestStepState(t);
|
||||
});
|
||||
|
||||
/**公共参数保存前下一步进行网元安装 */
|
||||
function fnNext() {
|
||||
fnSaveData().then(() => {
|
||||
fnToStepName('Start');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card :bordered="false" v-if="stepState.stepName === 'Para5G'">
|
||||
<!-- 公共参数表单 -->
|
||||
<Para5GForm v-model:data="state.from" :ne="state.hasNE"></Para5GForm>
|
||||
|
||||
<div style="padding: 24px 12px 0; text-align: end">
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="default"
|
||||
:disabled="state.confirmLoading"
|
||||
@click.prevent="fnReloadData()"
|
||||
>
|
||||
<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>
|
||||
<a-card :bordered="false" v-else>
|
||||
<!-- 插槽-卡片左侧 -->
|
||||
<template #title>
|
||||
<!-- 步骤进度 -->
|
||||
<a-steps :current="stepState.current" direction="horizontal" style="margin: 24px 0;">
|
||||
<a-step
|
||||
v-for="s in stepState.steps"
|
||||
:key="s.title"
|
||||
:title="s.title"
|
||||
:description="s.description"
|
||||
:disabled="true"
|
||||
/>
|
||||
</a-steps>
|
||||
</template>
|
||||
|
||||
<!-- 步骤页面 -->
|
||||
<component :is="currentComponent" />
|
||||
</a-card>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
473
src/views/software-version/software/components/EditModal.vue
Normal file
473
src/views/software-version/software/components/EditModal.vue
Normal file
@@ -0,0 +1,473 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw, watch } from 'vue';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import { message, Form, Upload, notification } from 'ant-design-vue/es';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { NE_EXPAND_LIST, NE_TYPE_LIST } from '@/constants/ne-constants';
|
||||
import { UploadRequestOption } from 'ant-design-vue/es/vc-upload/interface';
|
||||
import {
|
||||
addNeSoftware,
|
||||
getNeSoftware,
|
||||
updateNeSoftware,
|
||||
} from '@/api/ne/neSoftware';
|
||||
import { FileType } from 'ant-design-vue/es/upload/interface';
|
||||
import { uploadFileChunk } from '@/api/tool/file';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['ok', 'cancel', 'update:open']);
|
||||
const props = defineProps({
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
editId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
openByEdit: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: {
|
||||
id: number | undefined;
|
||||
neType: string;
|
||||
name: string;
|
||||
path: string;
|
||||
version: string;
|
||||
description: string;
|
||||
};
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**上传文件 */
|
||||
uploadFiles: any[];
|
||||
/**上传文件-依赖包 */
|
||||
uploadFilesDep: any[];
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByEdit: false,
|
||||
title: '软件包文件',
|
||||
from: {
|
||||
id: undefined,
|
||||
neType: '',
|
||||
name: '',
|
||||
path: '',
|
||||
version: '',
|
||||
description: '',
|
||||
},
|
||||
confirmLoading: false,
|
||||
uploadFiles: [],
|
||||
uploadFilesDep: [],
|
||||
});
|
||||
|
||||
/**对话框内表单属性和校验规则 */
|
||||
const modalStateFrom = Form.useForm(
|
||||
modalState.from,
|
||||
reactive({
|
||||
neType: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 32,
|
||||
message: t('views.ne.common.neTypePlease'),
|
||||
},
|
||||
],
|
||||
version: [
|
||||
{
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 64,
|
||||
message: t('views.ne.neSoftware.versionPlease'),
|
||||
},
|
||||
],
|
||||
path: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.ne.neSoftware.pathPlease'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalOk() {
|
||||
if (modalState.confirmLoading) return;
|
||||
modalStateFrom
|
||||
.validate()
|
||||
.then(e => {
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
const from = toRaw(modalState.from);
|
||||
|
||||
// 安装带依赖包
|
||||
if (modalState.uploadFilesDep.length > 0) {
|
||||
const depFiles = [];
|
||||
for (const depFile of modalState.uploadFilesDep) {
|
||||
if (depFile.status === 'done' && depFile.path) {
|
||||
depFiles.push(depFile.path);
|
||||
}
|
||||
}
|
||||
depFiles.push(from.path);
|
||||
from.path = depFiles.join(',');
|
||||
}
|
||||
|
||||
const software = from.id ? updateNeSoftware(from) : addNeSoftware(from);
|
||||
software.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 3,
|
||||
});
|
||||
// 返回无引用信息
|
||||
emit('ok', JSON.parse(JSON.stringify(from)));
|
||||
fnModalCancel();
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
hide();
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.openByEdit = false;
|
||||
modalState.confirmLoading = false;
|
||||
modalStateFrom.resetFields();
|
||||
modalState.uploadFiles = [];
|
||||
modalState.uploadFilesDep = [];
|
||||
emit('cancel');
|
||||
emit('update:open', false);
|
||||
}
|
||||
|
||||
/**表单上传前检查或转换压缩 */
|
||||
function fnBeforeUploadFile(file: FileType) {
|
||||
if (modalState.confirmLoading) return false;
|
||||
const fileName = file.name;
|
||||
const suff = fileName.substring(fileName.lastIndexOf('.'));
|
||||
if (!['.deb', '.rpm'].includes(suff)) {
|
||||
message.error(
|
||||
t('views.ne.neSoftware.fileTypeNotEq', {
|
||||
txt: '(.deb、.rpm)',
|
||||
}),
|
||||
3
|
||||
);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
// 取网元类型判断是否支持
|
||||
let neType = '';
|
||||
const neTypeIndex = fileName.indexOf('-');
|
||||
if (neTypeIndex !== -1) {
|
||||
neType = fileName.substring(0, neTypeIndex).toUpperCase();
|
||||
}
|
||||
// 主包类型
|
||||
if (!NE_TYPE_LIST.includes(neType)) {
|
||||
notification.warning({
|
||||
message: fileName,
|
||||
description: t('views.ne.neSoftware.fileCheckType'),
|
||||
});
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
modalState.from.neType = neType;
|
||||
|
||||
// 根据给定的软件名取版本号 ims-r2.2312.x-ub22.deb
|
||||
const matches = fileName.match(/([0-9.]+[0-9a-zA-Z]+)/);
|
||||
if (matches) {
|
||||
modalState.from.version = matches[0];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**表单上传文件 */
|
||||
function fnUploadFile(up: UploadRequestOption) {
|
||||
const uploadFile = modalState.uploadFiles.find(
|
||||
item => item.uid === (up.file as any).uid
|
||||
);
|
||||
if (!uploadFile) return;
|
||||
|
||||
// 发送请求
|
||||
uploadFileChunk(up.file as File, 5, 'software')
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 改为完成状态
|
||||
uploadFile.percent = 100;
|
||||
uploadFile.status = 'done';
|
||||
uploadFile.path = res.data.filePath;
|
||||
// 预置到表单
|
||||
const { filePath, originalFileName } = res.data;
|
||||
modalState.from.name = originalFileName;
|
||||
modalState.from.path = filePath;
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
uploadFile.percent = 0;
|
||||
uploadFile.status = 'error';
|
||||
uploadFile.response = error.message;
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**表单上传前检查或转换压缩-依赖包 */
|
||||
function fnBeforeUploadFileDep(file: FileType) {
|
||||
if (modalState.confirmLoading) return false;
|
||||
const fileName = file.name;
|
||||
const suff = fileName.substring(fileName.lastIndexOf('.'));
|
||||
if (!['.deb', '.rpm'].includes(suff)) {
|
||||
message.error(
|
||||
t('views.ne.neSoftware.fileTypeNotEq', {
|
||||
txt: '(.deb、.rpm)',
|
||||
}),
|
||||
3
|
||||
);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
// 已存在同名文件
|
||||
const hasItem = modalState.uploadFilesDep.find(
|
||||
item => item.name === fileName
|
||||
);
|
||||
if (hasItem) {
|
||||
notification.warning({
|
||||
message: fileName,
|
||||
description: t('views.ne.neSoftware.fileNameExists'),
|
||||
});
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
// 取网元类型判断是否支持
|
||||
let neType = '';
|
||||
const neTypeIndex = fileName.indexOf('-');
|
||||
if (neTypeIndex !== -1) {
|
||||
neType = fileName.substring(0, neTypeIndex).toUpperCase();
|
||||
}
|
||||
// 依赖包类型
|
||||
if (!NE_EXPAND_LIST.includes(neType)) {
|
||||
notification.warning({
|
||||
message: fileName,
|
||||
description: t('views.ne.neSoftware.fileCheckTypeDep'),
|
||||
});
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**表单上传文件-依赖包 */
|
||||
function fnUploadFileDep(up: UploadRequestOption) {
|
||||
const uploadFile = modalState.uploadFilesDep.find(
|
||||
item => item.uid === (up.file as any).uid
|
||||
);
|
||||
if (!uploadFile) return;
|
||||
|
||||
// 发送请求
|
||||
uploadFileChunk(up.file as File, 5, 'software')
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 改为完成状态
|
||||
uploadFile.percent = 100;
|
||||
uploadFile.status = 'done';
|
||||
uploadFile.path = res.data.filePath;
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
uploadFile.percent = 0;
|
||||
uploadFile.status = 'error';
|
||||
uploadFile.response = error.message;
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param id id
|
||||
*/
|
||||
function fnModalVisibleByEdit(id: number) {
|
||||
if (id > 0) {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
getNeSoftware(id)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
Object.assign(modalState.from, res.data);
|
||||
modalState.title = t('views.ne.neSoftware.uploadTitle');
|
||||
modalState.openByEdit = true;
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
modalState.title = t('views.ne.neSoftware.uploadTitle');
|
||||
modalState.openByEdit = true;
|
||||
}
|
||||
|
||||
/**监听是否显示,初始数据 */
|
||||
watch(
|
||||
() => props.open,
|
||||
val => {
|
||||
if (val) fnModalVisibleByEdit(props.editId);
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="650"
|
||||
:destroyOnClose="true"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:open="modalState.openByEdit"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
:label-col="{ span: 8 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<template v-if="modalState.from.id === undefined">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neSoftware.path')"
|
||||
:help="t('views.ne.neSoftware.uploadFileName')"
|
||||
name="file"
|
||||
v-bind="modalStateFrom.validateInfos.path"
|
||||
>
|
||||
<a-upload
|
||||
name="file"
|
||||
v-model:file-list="modalState.uploadFiles"
|
||||
accept=".rpm,.deb"
|
||||
list-type="text"
|
||||
:max-count="1"
|
||||
:show-upload-list="{
|
||||
showPreviewIcon: false,
|
||||
showRemoveIcon: false,
|
||||
showDownloadIcon: false,
|
||||
}"
|
||||
:before-upload="fnBeforeUploadFile"
|
||||
:custom-request="fnUploadFile"
|
||||
:disabled="modalState.confirmLoading"
|
||||
>
|
||||
<a-button type="primary">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
{{ t('views.ne.neSoftware.upload') }}
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
name="dep"
|
||||
:label="t('views.ne.neSoftware.dependFile')"
|
||||
:help="t('views.ne.neSoftware.dependFileTip')"
|
||||
>
|
||||
<a-upload
|
||||
name="file"
|
||||
v-model:file-list="modalState.uploadFilesDep"
|
||||
accept=".rpm,.deb"
|
||||
list-type="text"
|
||||
:multiple="true"
|
||||
:max-count="5"
|
||||
:show-upload-list="{
|
||||
showPreviewIcon: false,
|
||||
showRemoveIcon: true,
|
||||
showDownloadIcon: false,
|
||||
}"
|
||||
:before-upload="fnBeforeUploadFileDep"
|
||||
:custom-request="fnUploadFileDep"
|
||||
:disabled="modalState.confirmLoading"
|
||||
>
|
||||
<a-button type="dashed">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
{{ t('views.ne.neSoftware.upload') }}
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.common.neType')"
|
||||
name="neType"
|
||||
v-bind="modalStateFrom.validateInfos.neType"
|
||||
>
|
||||
<a-auto-complete
|
||||
v-model:value="modalState.from.neType"
|
||||
:options="NE_TYPE_LIST.map(v => ({ value: v }))"
|
||||
:disabled="modalState.from.id !== undefined"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
:maxlength="32"
|
||||
:disabled="modalState.from.id !== undefined"
|
||||
>
|
||||
</a-input>
|
||||
</a-auto-complete>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neSoftware.version')"
|
||||
name="version"
|
||||
v-bind="modalStateFrom.validateInfos.version"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="modalState.from.version"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="t('common.description')" name="description">
|
||||
<a-textarea
|
||||
v-model:value="modalState.from.description"
|
||||
:maxlength="500"
|
||||
:show-count="true"
|
||||
:placeholder="t('common.inputPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,506 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, onMounted, toRaw, watch } from 'vue';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import { message, Upload, notification } from 'ant-design-vue/es';
|
||||
import type { UploadRequestOption } from 'ant-design-vue/es/vc-upload/interface';
|
||||
import type { FileType, UploadFile } from 'ant-design-vue/es/upload/interface';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { NE_TYPE_LIST, NE_EXPAND_LIST } from '@/constants/ne-constants';
|
||||
import { addNeSoftware } from '@/api/ne/neSoftware';
|
||||
import { uploadFileChunk } from '@/api/tool/file';
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['ok', 'cancel', 'update:open']);
|
||||
const props = defineProps({
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
/**网元类型,指定上传 */
|
||||
neType: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
/**网元支持 */
|
||||
const NE_TYPE_EXP = NE_EXPAND_LIST.concat(NE_TYPE_LIST);
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
openByMoreFile: boolean;
|
||||
/**标题 */
|
||||
title: string;
|
||||
/**表单数据 */
|
||||
from: {
|
||||
neType: string;
|
||||
name: string;
|
||||
path: string;
|
||||
version: string;
|
||||
description: string;
|
||||
}[];
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
/**上传文件 */
|
||||
uploadFiles: any[];
|
||||
/**上传文件-依赖包 */
|
||||
uploadFilesDep: any[];
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByMoreFile: false,
|
||||
title: '软件包文件',
|
||||
from: [],
|
||||
confirmLoading: false,
|
||||
uploadFiles: [],
|
||||
uploadFilesDep: [],
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
async function fnModalOk() {
|
||||
if (modalState.confirmLoading) return;
|
||||
if (modalState.uploadFiles.length < 1) {
|
||||
message.warning({
|
||||
content: t('views.ne.neSoftware.uploadNotFile'),
|
||||
duration: 3,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 处理上传文件过滤
|
||||
const uploadFiles = toRaw(modalState.uploadFiles);
|
||||
const from = toRaw(modalState.from);
|
||||
const expandFile: Record<string, string> = {};
|
||||
for (const item of uploadFiles) {
|
||||
if (item.status !== 'done' || !item.path) continue;
|
||||
const neSoftware = from.find(s => s.name === item.name);
|
||||
if (neSoftware && NE_TYPE_LIST.includes(neSoftware.neType)) {
|
||||
neSoftware.path = item.path;
|
||||
}
|
||||
if (neSoftware && NE_EXPAND_LIST.includes(neSoftware.neType)) {
|
||||
expandFile[neSoftware.neType] = item.path;
|
||||
}
|
||||
}
|
||||
|
||||
// IMS拼接拓展包
|
||||
const ims = from.find(s => s.neType === 'IMS');
|
||||
if (ims) {
|
||||
const pkgArr = [];
|
||||
if (expandFile['ADB']) {
|
||||
pkgArr.push(expandFile['ADB']);
|
||||
}
|
||||
if (expandFile['KVDB']) {
|
||||
pkgArr.push(expandFile['KVDB']);
|
||||
}
|
||||
if (expandFile['RTPROXY']) {
|
||||
pkgArr.push(expandFile['RTPROXY']);
|
||||
}
|
||||
if (expandFile['MF']) {
|
||||
pkgArr.push(expandFile['MF']);
|
||||
}
|
||||
pkgArr.push(ims.path);
|
||||
ims.path = pkgArr.join(',');
|
||||
}
|
||||
// UDM拼接拓展包
|
||||
const udm = from.find(s => s.neType === 'UDM');
|
||||
if (udm && expandFile['ADB']) {
|
||||
udm.path = [expandFile['ADB'], udm.path].join(',');
|
||||
} else if (udm && expandFile['KVDB']) {
|
||||
udm.path = [expandFile['KVDB'], udm.path].join(',');
|
||||
}
|
||||
|
||||
// 安装带依赖包-指定网元时
|
||||
if (props.neType && modalState.uploadFilesDep.length > 0) {
|
||||
const neInfo = from.find(s => s.neType === props.neType);
|
||||
if (neInfo) {
|
||||
const depFiles = [];
|
||||
for (const depFile of modalState.uploadFilesDep) {
|
||||
if (depFile.status === 'done' && depFile.path) {
|
||||
depFiles.push(depFile.path);
|
||||
}
|
||||
}
|
||||
depFiles.push(neInfo.path);
|
||||
neInfo.path = depFiles.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
// 开始添加到软件包数据
|
||||
const rows: any[] = [];
|
||||
modalState.confirmLoading = true;
|
||||
for (const item of from.filter(s => NE_TYPE_LIST.includes(s.neType))) {
|
||||
try {
|
||||
const res = await addNeSoftware(item);
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
rows.push(item);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${item.neType} ${res.msg}`,
|
||||
duration: 3,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
emit('ok', rows);
|
||||
fnModalCancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalCancel() {
|
||||
modalState.openByMoreFile = false;
|
||||
modalState.confirmLoading = false;
|
||||
modalState.from = [];
|
||||
modalState.uploadFiles = [];
|
||||
modalState.uploadFilesDep = [];
|
||||
emit('cancel');
|
||||
emit('update:open', false);
|
||||
}
|
||||
|
||||
/**表单上传前检查或转换压缩 */
|
||||
function fnBeforeUploadFile(file: FileType) {
|
||||
if (modalState.confirmLoading) return false;
|
||||
const fileName = file.name;
|
||||
const suff = fileName.substring(fileName.lastIndexOf('.'));
|
||||
if (!['.deb', '.rpm'].includes(suff)) {
|
||||
message.error(
|
||||
t('views.ne.neSoftware.fileTypeNotEq', {
|
||||
txt: '(.deb、.rpm)',
|
||||
}),
|
||||
3
|
||||
);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
// 取网元类型判断是否支持
|
||||
let neType = '';
|
||||
const neTypeIndex = fileName.indexOf('-');
|
||||
if (neTypeIndex !== -1) {
|
||||
neType = fileName.substring(0, neTypeIndex).toUpperCase();
|
||||
}
|
||||
if (!NE_TYPE_EXP.includes(neType)) {
|
||||
notification.warning({
|
||||
message: fileName,
|
||||
description: t('views.ne.neSoftware.fileCheckType'),
|
||||
});
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
// 根据给定的软件名取版本号 amf-r2.2404.xx-ub22.deb
|
||||
let version = '';
|
||||
const matches = fileName.match(/([0-9.]+[0-9a-zA-Z]+)/);
|
||||
if (matches) {
|
||||
version = matches[0];
|
||||
} else {
|
||||
notification.warning({
|
||||
message: fileName,
|
||||
description: t('views.ne.neSoftware.fileCheckVer'),
|
||||
});
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
// 单网元上传
|
||||
if (props.neType && props.neType !== neType) {
|
||||
notification.warning({
|
||||
message: fileName,
|
||||
description: t('views.ne.neSoftware.fileTypeNotEq', {
|
||||
txt: props.neType,
|
||||
}),
|
||||
});
|
||||
return Upload.LIST_IGNORE;
|
||||
} else {
|
||||
// 多文件上传时检查是否有同类型网元包
|
||||
const hasItem = modalState.from.find(item => item.neType === neType);
|
||||
if (hasItem) {
|
||||
notification.warning({
|
||||
message: fileName,
|
||||
description: t('views.ne.neSoftware.fileTypeExists'),
|
||||
});
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
}
|
||||
|
||||
modalState.from.push({
|
||||
name: fileName,
|
||||
neType: neType,
|
||||
version: version,
|
||||
path: '', // 上传完成后提交注入
|
||||
description: '',
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**表单上传前删除 */
|
||||
function fnBeforeRemoveFile(file: UploadFile) {
|
||||
const fileName = file.name;
|
||||
// 取网元类型判断是否支持
|
||||
let neType = '';
|
||||
const neTypeIndex = fileName.indexOf('-');
|
||||
if (neTypeIndex !== -1) {
|
||||
neType = fileName.substring(0, neTypeIndex).toUpperCase();
|
||||
}
|
||||
const idx = modalState.from.findIndex(item => item.neType === neType);
|
||||
modalState.from.splice(idx, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**表单上传文件 */
|
||||
function fnUploadFile(up: UploadRequestOption) {
|
||||
const uploadFile = modalState.uploadFiles.find(
|
||||
item => item.uid === (up.file as any).uid
|
||||
);
|
||||
if (!uploadFile) return;
|
||||
|
||||
// 发送请求
|
||||
uploadFileChunk(up.file as File, 5, 'software')
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 改为完成状态
|
||||
uploadFile.percent = 100;
|
||||
uploadFile.status = 'done';
|
||||
uploadFile.path = res.data.filePath;
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
uploadFile.percent = 0;
|
||||
uploadFile.status = 'error';
|
||||
uploadFile.response = error.message;
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**表单上传前检查或转换压缩-依赖包 */
|
||||
function fnBeforeUploadFileDep(file: FileType) {
|
||||
if (modalState.confirmLoading) return false;
|
||||
const fileName = file.name;
|
||||
const suff = fileName.substring(fileName.lastIndexOf('.'));
|
||||
if (!['.deb', '.rpm'].includes(suff)) {
|
||||
message.error(
|
||||
t('views.ne.neSoftware.fileTypeNotEq', {
|
||||
txt: '(.deb、.rpm)',
|
||||
}),
|
||||
3
|
||||
);
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
// 已存在同名文件
|
||||
const hasItem = modalState.uploadFilesDep.find(
|
||||
item => item.name === fileName
|
||||
);
|
||||
if (hasItem) {
|
||||
notification.warning({
|
||||
message: fileName,
|
||||
description: t('views.ne.neSoftware.fileNameExists'),
|
||||
});
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
// 取网元类型判断是否支持
|
||||
let neType = '';
|
||||
const neTypeIndex = fileName.indexOf('-');
|
||||
if (neTypeIndex !== -1) {
|
||||
neType = fileName.substring(0, neTypeIndex).toUpperCase();
|
||||
}
|
||||
// 依赖包类型
|
||||
if (!NE_EXPAND_LIST.includes(neType)) {
|
||||
notification.warning({
|
||||
message: fileName,
|
||||
description: t('views.ne.neSoftware.fileCheckTypeDep'),
|
||||
});
|
||||
return Upload.LIST_IGNORE;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**表单上传文件-依赖包 */
|
||||
function fnUploadFileDep(up: UploadRequestOption) {
|
||||
const uploadFile = modalState.uploadFilesDep.find(
|
||||
item => item.uid === (up.file as any).uid
|
||||
);
|
||||
if (!uploadFile) return;
|
||||
|
||||
// 发送请求
|
||||
uploadFileChunk(up.file as File, 5, 'software')
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 改为完成状态
|
||||
uploadFile.percent = 100;
|
||||
uploadFile.status = 'done';
|
||||
uploadFile.path = res.data.filePath;
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
uploadFile.percent = 0;
|
||||
uploadFile.status = 'error';
|
||||
uploadFile.response = error.message;
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**监听是否显示,初始数据 */
|
||||
watch(
|
||||
() => props.open,
|
||||
val => {
|
||||
if (val) {
|
||||
modalState.title = t('views.ne.neSoftware.uploadTitle');
|
||||
modalState.openByMoreFile = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:open="modalState.openByMoreFile"
|
||||
:title="modalState.title"
|
||||
:confirm-loading="modalState.confirmLoading"
|
||||
@ok="fnModalOk"
|
||||
@cancel="fnModalCancel"
|
||||
>
|
||||
<a-form
|
||||
name="modalStateFrom"
|
||||
layout="horizontal"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
:label-col="{ span: 6 }"
|
||||
:labelWrap="true"
|
||||
>
|
||||
<template v-if="props.neType">
|
||||
<a-form-item :label="t('views.ne.common.neType')" name="type">
|
||||
<a-tag color="processing">
|
||||
{{ props.neType }}
|
||||
</a-tag>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
:label="t('views.ne.neSoftware.path')"
|
||||
:help="t('views.ne.neSoftware.uploadFileName')"
|
||||
:required="true"
|
||||
:validate-on-rule-change="false"
|
||||
:validateTrigger="[]"
|
||||
name="file"
|
||||
>
|
||||
<a-upload
|
||||
name="file"
|
||||
v-model:file-list="modalState.uploadFiles"
|
||||
accept=".rpm,.deb"
|
||||
list-type="text"
|
||||
:max-count="1"
|
||||
:show-upload-list="{
|
||||
showPreviewIcon: false,
|
||||
showRemoveIcon: false,
|
||||
showDownloadIcon: false,
|
||||
}"
|
||||
:before-upload="fnBeforeUploadFile"
|
||||
:custom-request="fnUploadFile"
|
||||
:disabled="modalState.confirmLoading"
|
||||
>
|
||||
<a-button type="primary">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
{{ t('views.ne.neSoftware.upload') }}
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
name="dep"
|
||||
:label="t('views.ne.neSoftware.dependFile')"
|
||||
:help="t('views.ne.neSoftware.dependFileTip')"
|
||||
>
|
||||
<a-upload
|
||||
name="file"
|
||||
v-model:file-list="modalState.uploadFilesDep"
|
||||
accept=".rpm,.deb"
|
||||
list-type="text"
|
||||
:multiple="true"
|
||||
:max-count="5"
|
||||
:show-upload-list="{
|
||||
showPreviewIcon: false,
|
||||
showRemoveIcon: true,
|
||||
showDownloadIcon: false,
|
||||
}"
|
||||
:before-upload="fnBeforeUploadFileDep"
|
||||
:custom-request="fnUploadFileDep"
|
||||
:disabled="modalState.confirmLoading"
|
||||
>
|
||||
<a-button type="dashed">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
{{ t('views.ne.neSoftware.upload') }}
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<a-form-item :label="t('common.description')">
|
||||
{{
|
||||
t('views.ne.neSoftware.uploadBatchMax', {
|
||||
txt: NE_TYPE_EXP.length,
|
||||
})
|
||||
}}
|
||||
<br />
|
||||
{{ t('views.ne.neSoftware.uploadFileName') }}
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
:label="t('views.ne.neSoftware.path')"
|
||||
:required="true"
|
||||
:validate-on-rule-change="false"
|
||||
:validateTrigger="[]"
|
||||
name="file"
|
||||
>
|
||||
<a-upload
|
||||
name="file"
|
||||
v-model:file-list="modalState.uploadFiles"
|
||||
accept=".rpm,.deb"
|
||||
list-type="text"
|
||||
:multiple="true"
|
||||
:max-count="NE_TYPE_EXP.length"
|
||||
:show-upload-list="{
|
||||
showPreviewIcon: false,
|
||||
showRemoveIcon: true,
|
||||
showDownloadIcon: false,
|
||||
}"
|
||||
@remove="fnBeforeRemoveFile"
|
||||
:before-upload="fnBeforeUploadFile"
|
||||
:custom-request="fnUploadFile"
|
||||
:disabled="modalState.confirmLoading"
|
||||
>
|
||||
<a-button type="primary">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
{{ t('views.ne.neSoftware.upload') }}
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</ProModal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
577
src/views/software-version/software/index.vue
Normal file
577
src/views/software-version/software/index.vue
Normal file
@@ -0,0 +1,577 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw, defineAsyncComponent } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { Modal, TableColumnsType, message } from 'ant-design-vue/es';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listNeSoftware, delNeSoftware } from '@/api/ne/neSoftware';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import { downloadFile } from '@/api/tool/file';
|
||||
import { saveAs } from 'file-saver';
|
||||
const neStore = useNeStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
// 异步加载组件
|
||||
const EditModal = defineAsyncComponent(
|
||||
() => import('./components/EditModal.vue')
|
||||
);
|
||||
const UploadMoreFile = defineAsyncComponent(
|
||||
() => import('./components/UploadMoreFile.vue')
|
||||
);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neType: undefined,
|
||||
/**包名称 */
|
||||
name: '',
|
||||
/**包版本 */
|
||||
version: '',
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
neType: undefined,
|
||||
name: '',
|
||||
version: '',
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: object[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
/**勾选单行记录 */
|
||||
selectedRowOne: any;
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: false,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
/**勾选单行记录 */
|
||||
selectedRowOne: { neType: '' },
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns = ref<TableColumnsType>([
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.neType'),
|
||||
dataIndex: 'neType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neSoftware.version'),
|
||||
dataIndex: 'version',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neSoftware.name'),
|
||||
dataIndex: 'name',
|
||||
align: 'left',
|
||||
width: 250,
|
||||
resizable: true,
|
||||
minWidth: 150,
|
||||
maxWidth: 400,
|
||||
},
|
||||
{
|
||||
title: t('common.description'),
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
resizable: true,
|
||||
minWidth: 100,
|
||||
maxWidth: 400,
|
||||
},
|
||||
{
|
||||
title: t('common.createTime'),
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(
|
||||
keys: (string | number)[],
|
||||
selectedRows: any[]
|
||||
) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
// 勾选单个上传
|
||||
if (selectedRows.length === 1) {
|
||||
tableState.selectedRowOne = selectedRows[0];
|
||||
} else {
|
||||
tableState.selectedRowOne = { neType: '' };
|
||||
}
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listNeSoftware(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
} else {
|
||||
tablePagination.total = 0;
|
||||
tableState.data = [];
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**新增框或修改框是否显示 */
|
||||
openByEdit: boolean;
|
||||
/**新增框或修改框ID */
|
||||
editId: number;
|
||||
/**多文件上传 */
|
||||
openByMoreFile: boolean;
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByEdit: false,
|
||||
editId: 0,
|
||||
openByMoreFile: false,
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出显示为 新增或者修改
|
||||
* @param noticeId 网元id, 不传为新增
|
||||
*/
|
||||
function fnModalVisibleByEdit(id: number) {
|
||||
modalState.editId = id;
|
||||
modalState.openByEdit = !modalState.openByEdit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalEditOk() {
|
||||
fnGetList(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalEditCancel() {
|
||||
modalState.editId = 0;
|
||||
modalState.openByEdit = false;
|
||||
modalState.openByMoreFile = false;
|
||||
}
|
||||
|
||||
/**删除软件包 */
|
||||
function fnRecordDelete(id: string) {
|
||||
if (!id || modalState.confirmLoading) return;
|
||||
let msg = t('views.ne.neSoftware.delTip');
|
||||
if (id === '0') {
|
||||
msg = `${msg} ...${tableState.selectedRowKeys.length}`;
|
||||
id = tableState.selectedRowKeys.join(',');
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: msg,
|
||||
onOk() {
|
||||
if (modalState.confirmLoading) return;
|
||||
modalState.confirmLoading = true;
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
delNeSoftware(id)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
} else {
|
||||
message.error(res.msg, 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
modalState.confirmLoading = false;
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**下载软件包 */
|
||||
function fnDownloadFile(row: Record<string, any>) {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neSoftware.downTip', { txt: row.name }),
|
||||
onOk() {
|
||||
const hide = message.loading(t('common.loading'), 0);
|
||||
downloadFile(row.path)
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
message.success({
|
||||
content: t('common.operateOk'),
|
||||
duration: 2,
|
||||
});
|
||||
saveAs(res.data, `${row.name}`);
|
||||
} else {
|
||||
message.error({
|
||||
content: `${res.msg}`,
|
||||
duration: 2,
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
hide();
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录更多操作
|
||||
*/
|
||||
function fnRecordMore(type: string | number, row: Record<string, any>) {
|
||||
if (type === 'download') {
|
||||
fnDownloadFile(row);
|
||||
return;
|
||||
}
|
||||
if (type === 'delete') {
|
||||
fnRecordDelete(row.id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.common.neType')" name="neType ">
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.neType"
|
||||
:options="neStore.getNeSelectOtions"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.neSoftware.name')" name="name">
|
||||
<a-input
|
||||
v-model:value="queryParams.name"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item
|
||||
:label="t('views.ne.neSoftware.version')"
|
||||
name="version"
|
||||
>
|
||||
<a-input
|
||||
v-model:value="queryParams.version"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
></a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button type="primary" @click.prevent="fnModalVisibleByEdit(0)">
|
||||
<template #icon><UploadOutlined /></template>
|
||||
{{ t('views.ne.neSoftware.upload') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="tableState.selectedRowKeys.length > 1"
|
||||
@click.prevent="
|
||||
() => (modalState.openByMoreFile = !modalState.openByMoreFile)
|
||||
"
|
||||
>
|
||||
<template #icon><UploadOutlined /></template>
|
||||
<template v-if="tableState.selectedRowOne.neType">
|
||||
{{ t('views.ne.neSoftware.upload') }}
|
||||
{{ tableState.selectedRowOne.neType }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t('views.ne.neSoftware.uploadBatch') }}
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button
|
||||
type="default"
|
||||
danger
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.confirmLoading"
|
||||
@click.prevent="fnRecordDelete('0')"
|
||||
>
|
||||
<template #icon><DeleteOutlined /></template>
|
||||
{{ t('common.deleteText') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.switch.show')"
|
||||
:un-checked-children="t('common.switch.hide')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown placement="bottomRight" trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">
|
||||
{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: tableColumns.length * 180 }"
|
||||
@resizeColumn="(w:number, col:any) => (col.width = w)"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'description'">
|
||||
<a-tooltip placement="topLeft">
|
||||
<template #title>{{ record.description }}</template>
|
||||
<div
|
||||
style="
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
width: 50px;
|
||||
"
|
||||
:style="{ width: column.width + 'px' }"
|
||||
>
|
||||
{{ record.description }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title> {{ t('common.editText') }}</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnModalVisibleByEdit(record.id)"
|
||||
>
|
||||
<template #icon> <ProfileOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip placement="left">
|
||||
<template #title>{{ t('common.moreText') }}</template>
|
||||
<a-dropdown placement="bottomRight" trigger="click">
|
||||
<a-button type="link">
|
||||
<template #icon><EllipsisOutlined /> </template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu @click="({ key }:any) => fnRecordMore(key, record)">
|
||||
<a-menu-item key="download">
|
||||
<DownloadOutlined />
|
||||
{{ t('common.downloadText') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="delete">
|
||||
<DeleteOutlined />
|
||||
{{ t('common.deleteText') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增框或修改框 -->
|
||||
<EditModal
|
||||
v-model:open="modalState.openByEdit"
|
||||
:edit-id="modalState.editId"
|
||||
@ok="fnModalEditOk"
|
||||
@cancel="fnModalEditCancel"
|
||||
></EditModal>
|
||||
|
||||
<!-- 新增多文件上传框 -->
|
||||
<UploadMoreFile
|
||||
v-model:open="modalState.openByMoreFile"
|
||||
:ne-type="tableState.selectedRowOne.neType"
|
||||
@ok="fnModalEditOk"
|
||||
@cancel="fnModalEditCancel"
|
||||
></UploadMoreFile>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
724
src/views/software-version/version/index.vue
Normal file
724
src/views/software-version/version/index.vue
Normal file
@@ -0,0 +1,724 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, toRaw, defineAsyncComponent } from 'vue';
|
||||
import { PageContainer } from 'antdv-pro-layout';
|
||||
import { ProModal } from 'antdv-pro-modal';
|
||||
import {
|
||||
Modal,
|
||||
TableColumnsType,
|
||||
message,
|
||||
notification,
|
||||
} from 'ant-design-vue/es';
|
||||
import { SizeType } from 'ant-design-vue/es/config-provider';
|
||||
import { MenuInfo } from 'ant-design-vue/es/menu/src/interface';
|
||||
import useNeStore from '@/store/modules/ne';
|
||||
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
|
||||
import { listNeVersion, operateNeVersion } from '@/api/ne/neVersion';
|
||||
import { parseDateToStr } from '@/utils/date-utils';
|
||||
import useI18n from '@/hooks/useI18n';
|
||||
import useDictStore from '@/store/modules/dict';
|
||||
import useMaskStore from '@/store/modules/mask';
|
||||
const maskStore = useMaskStore();
|
||||
const neStore = useNeStore();
|
||||
const { t } = useI18n();
|
||||
const { getDict } = useDictStore();
|
||||
|
||||
// 异步加载组件
|
||||
const EditModal = defineAsyncComponent(
|
||||
() => import('@/views/ne/neSoftware/components/EditModal.vue')
|
||||
);
|
||||
const UploadMoreFile = defineAsyncComponent(
|
||||
() => import('@/views/ne/neSoftware/components/UploadMoreFile.vue')
|
||||
);
|
||||
|
||||
/**字典数据-状态 */
|
||||
let dictStatus = ref<DictType[]>([]);
|
||||
|
||||
/**查询参数 */
|
||||
let queryParams = reactive({
|
||||
/**网元类型 */
|
||||
neType: undefined,
|
||||
/**当前页数 */
|
||||
pageNum: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
});
|
||||
|
||||
/**查询参数重置 */
|
||||
function fnQueryReset() {
|
||||
queryParams = Object.assign(queryParams, {
|
||||
neType: undefined,
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
});
|
||||
tablePagination.current = 1;
|
||||
tablePagination.pageSize = 20;
|
||||
fnGetList();
|
||||
}
|
||||
|
||||
/**表格状态类型 */
|
||||
type TabeStateType = {
|
||||
/**加载等待 */
|
||||
loading: boolean;
|
||||
/**紧凑型 */
|
||||
size: SizeType;
|
||||
/**搜索栏 */
|
||||
seached: boolean;
|
||||
/**记录数据 */
|
||||
data: any[];
|
||||
/**勾选记录 */
|
||||
selectedRowKeys: (string | number)[];
|
||||
/**勾选单行记录 */
|
||||
selectedRowOne: any;
|
||||
};
|
||||
|
||||
/**表格状态 */
|
||||
let tableState: TabeStateType = reactive({
|
||||
loading: false,
|
||||
size: 'middle',
|
||||
seached: true,
|
||||
data: [],
|
||||
selectedRowKeys: [],
|
||||
/**勾选单行记录 */
|
||||
selectedRowOne: { neType: '' },
|
||||
});
|
||||
|
||||
/**表格字段列 */
|
||||
let tableColumns = ref<TableColumnsType>([
|
||||
{
|
||||
title: t('common.rowId'),
|
||||
dataIndex: 'id',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.common.neType'),
|
||||
dataIndex: 'neType',
|
||||
align: 'left',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neVersion.version'),
|
||||
dataIndex: 'version',
|
||||
key: 'version',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
resizable: true,
|
||||
minWidth: 150,
|
||||
maxWidth: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neVersion.preVersion'),
|
||||
dataIndex: 'preVersion',
|
||||
key: 'preVersion',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
resizable: true,
|
||||
minWidth: 150,
|
||||
maxWidth: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neVersion.newVersion'),
|
||||
dataIndex: 'newVersion',
|
||||
align: 'left',
|
||||
width: 150,
|
||||
resizable: true,
|
||||
minWidth: 150,
|
||||
maxWidth: 200,
|
||||
},
|
||||
{
|
||||
title: t('views.ne.neVersion.status'),
|
||||
key: 'status',
|
||||
dataIndex: 'status',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: t('common.updateTime'),
|
||||
dataIndex: 'updateTime',
|
||||
align: 'left',
|
||||
customRender(opt) {
|
||||
if (!opt.value) return '';
|
||||
return parseDateToStr(opt.value);
|
||||
},
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: t('common.operate'),
|
||||
key: 'id',
|
||||
align: 'left',
|
||||
},
|
||||
]);
|
||||
|
||||
/**表格分页器参数 */
|
||||
let tablePagination = reactive({
|
||||
/**当前页数 */
|
||||
current: 1,
|
||||
/**每页条数 */
|
||||
pageSize: 20,
|
||||
/**默认的每页条数 */
|
||||
defaultPageSize: 20,
|
||||
/**指定每页可以显示多少条 */
|
||||
pageSizeOptions: ['10', '20', '50', '100'],
|
||||
/**只有一页时是否隐藏分页器 */
|
||||
hideOnSinglePage: false,
|
||||
/**是否可以快速跳转至某页 */
|
||||
showQuickJumper: true,
|
||||
/**是否可以改变 pageSize */
|
||||
showSizeChanger: true,
|
||||
/**数据总数 */
|
||||
total: 0,
|
||||
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
|
||||
onChange: (page: number, pageSize: number) => {
|
||||
tablePagination.current = page;
|
||||
tablePagination.pageSize = pageSize;
|
||||
queryParams.pageNum = page;
|
||||
queryParams.pageSize = pageSize;
|
||||
fnGetList();
|
||||
},
|
||||
});
|
||||
|
||||
/**表格紧凑型变更操作 */
|
||||
function fnTableSize({ key }: MenuInfo) {
|
||||
tableState.size = key as SizeType;
|
||||
}
|
||||
|
||||
/**表格多选 */
|
||||
function fnTableSelectedRowKeys(
|
||||
keys: (string | number)[],
|
||||
selectedRows: any[]
|
||||
) {
|
||||
tableState.selectedRowKeys = keys;
|
||||
// 勾选单个上传
|
||||
if (selectedRows.length === 1) {
|
||||
tableState.selectedRowOne = selectedRows[0];
|
||||
} else {
|
||||
tableState.selectedRowOne = { neType: '' };
|
||||
}
|
||||
}
|
||||
|
||||
/**查询列表, pageNum初始页数 */
|
||||
function fnGetList(pageNum?: number) {
|
||||
if (tableState.loading) return;
|
||||
tableState.loading = true;
|
||||
if (pageNum) {
|
||||
queryParams.pageNum = pageNum;
|
||||
}
|
||||
listNeVersion(toRaw(queryParams)).then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// 取消勾选
|
||||
if (tableState.selectedRowKeys.length > 0) {
|
||||
tableState.selectedRowKeys = [];
|
||||
}
|
||||
const { total, rows } = res.data;
|
||||
tablePagination.total = total;
|
||||
tableState.data = rows;
|
||||
if (
|
||||
tablePagination.total <=
|
||||
(queryParams.pageNum - 1) * tablePagination.pageSize &&
|
||||
queryParams.pageNum !== 1
|
||||
) {
|
||||
tableState.loading = false;
|
||||
fnGetList(queryParams.pageNum - 1);
|
||||
}
|
||||
} else {
|
||||
tablePagination.total = 0;
|
||||
tableState.data = [];
|
||||
}
|
||||
tableState.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**对话框对象信息状态类型 */
|
||||
type ModalStateType = {
|
||||
/**单文件上传 */
|
||||
openByEdit: boolean;
|
||||
/**多文件上传 */
|
||||
openByMoreFile: boolean;
|
||||
/**勾选升级情况 */
|
||||
openByUpgrade: boolean;
|
||||
/**操作数据进行版本升级 */
|
||||
operateDataUpgrade: any[];
|
||||
/**确定按钮 loading */
|
||||
confirmLoading: boolean;
|
||||
};
|
||||
|
||||
/**对话框对象信息状态 */
|
||||
let modalState: ModalStateType = reactive({
|
||||
openByEdit: false,
|
||||
openByMoreFile: false,
|
||||
openByUpgrade: false,
|
||||
operateDataUpgrade: [],
|
||||
confirmLoading: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* 对话框弹出确认执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalEditOk() {
|
||||
fnGetList(1);
|
||||
if (modalState.openByUpgrade) {
|
||||
fnModalEditCancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对话框弹出关闭执行函数
|
||||
* 进行表达规则校验
|
||||
*/
|
||||
function fnModalEditCancel() {
|
||||
modalState.openByEdit = false;
|
||||
modalState.openByMoreFile = false;
|
||||
modalState.openByUpgrade = false;
|
||||
modalState.operateDataUpgrade = [];
|
||||
}
|
||||
|
||||
/**版本控制升级回退 */
|
||||
function fnRecordVersion(
|
||||
action: 'upgrade' | 'rollback',
|
||||
row: Record<string, any>
|
||||
) {
|
||||
let contentTip = `${action} version packages?`;
|
||||
if (action === 'upgrade') {
|
||||
contentTip = t('views.ne.neVersion.upgradeTip');
|
||||
if (row.newVersion === '' || row.newVersion === '-') {
|
||||
message.warning(t('views.ne.neVersion.upgradeTipEmpty'), 3);
|
||||
return;
|
||||
}
|
||||
if (row.newVersion === row.version) {
|
||||
contentTip = t('views.ne.neVersion.upgradeTipEqual');
|
||||
}
|
||||
}
|
||||
if (action === 'rollback') {
|
||||
contentTip = t('views.ne.neVersion.rollbackTip');
|
||||
if (row.preVersion === '' || row.preVersion === '-') {
|
||||
message.warning(t('views.ne.neVersion.rollbackTipEmpty'), 3);
|
||||
return;
|
||||
}
|
||||
if (row.prePath === '' || row.prePath === '-') {
|
||||
message.warning(t('views.ne.neVersion.noPath'), 3);
|
||||
return;
|
||||
}
|
||||
if (row.preVersion === row.version) {
|
||||
contentTip = t('views.ne.neVersion.rollbackTipEqual');
|
||||
}
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: contentTip,
|
||||
onOk() {
|
||||
if (modalState.confirmLoading) return;
|
||||
modalState.confirmLoading = true;
|
||||
const notificationKey = 'NE_VERSION_' + action;
|
||||
notification.info({
|
||||
key: notificationKey,
|
||||
message: t('common.tipTitle'),
|
||||
description: `${row.neType} ${t('common.loading')}`,
|
||||
duration: 0,
|
||||
});
|
||||
let preinput = {};
|
||||
if (row.neType.toUpperCase() === 'IMS') {
|
||||
preinput = { pisCSCF: 'y', updateMFetc: 'No', updateMFshare: 'No' };
|
||||
}
|
||||
operateNeVersion({
|
||||
neType: row.neType,
|
||||
neUid: row.neUid,
|
||||
action: action,
|
||||
preinput: preinput,
|
||||
})
|
||||
.then(res => {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
// OMC自升级
|
||||
if (row.neType.toUpperCase() === 'OMC') {
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
maskStore.handleMaskType('reload');
|
||||
} else {
|
||||
message.error(t('views.ne.neVersion.upgradeFail'), 3);
|
||||
}
|
||||
return;
|
||||
}
|
||||
fnGetList(1);
|
||||
} else {
|
||||
message.error(t('views.ne.neVersion.upgradeFail'), 3);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
notification.close(notificationKey);
|
||||
modalState.confirmLoading = false;
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**版本升级弹出确认是否升级 */
|
||||
function fnRecordUpgradeConfirm() {
|
||||
Modal.confirm({
|
||||
title: t('common.tipTitle'),
|
||||
content: t('views.ne.neVersion.upgradeBatchTip'),
|
||||
onOk() {
|
||||
fnRecordUpgrade();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**版本升级进行 */
|
||||
async function fnRecordUpgrade() {
|
||||
if (modalState.confirmLoading) return;
|
||||
modalState.confirmLoading = true;
|
||||
modalState.openByUpgrade = true;
|
||||
// 操作升级的网元数据
|
||||
const selectRows = tableState.data.filter(item =>
|
||||
tableState.selectedRowKeys.includes(item.id)
|
||||
);
|
||||
for (const row of selectRows) {
|
||||
if (row.newVersion === '-' || row.newVersion === '') {
|
||||
modalState.operateDataUpgrade.push({
|
||||
neType: row.neType,
|
||||
neId: row.neId,
|
||||
status: 'fail',
|
||||
log: t('views.ne.neVersion.upgradeNotNewVer'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
// OMC跳过操作
|
||||
if (row.neType.toUpperCase() === 'OMC') {
|
||||
modalState.operateDataUpgrade.push({
|
||||
neType: row.neType,
|
||||
neId: row.neId,
|
||||
status: 'fail',
|
||||
log: t('views.ne.neVersion.upgradeOMCVer'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
// 开始升级
|
||||
let preinput = {};
|
||||
if (row.neType.toUpperCase() === 'IMS') {
|
||||
preinput = { pisCSCF: 'y', updateMFetc: 'No', updateMFshare: 'No' };
|
||||
}
|
||||
const installData = {
|
||||
neType: row.neType,
|
||||
neId: row.neId,
|
||||
action: 'upgrade',
|
||||
preinput: preinput,
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await operateNeVersion(installData);
|
||||
const operateData = {
|
||||
neType: row.neType,
|
||||
neId: row.neId,
|
||||
status: 'fail',
|
||||
log: t('common.operateErr'),
|
||||
};
|
||||
if (res.code === RESULT_CODE_SUCCESS) {
|
||||
operateData.status = 'done';
|
||||
operateData.log = t('views.ne.neVersion.upgradeDone');
|
||||
} else {
|
||||
operateData.status = 'fail';
|
||||
operateData.log = t('views.ne.neVersion.upgradeFail');
|
||||
}
|
||||
modalState.operateDataUpgrade.unshift(operateData);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
// 结束
|
||||
modalState.confirmLoading = false;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始字典数据
|
||||
getDict('ne_version_status')
|
||||
.then(res => {
|
||||
dictStatus.value = res;
|
||||
})
|
||||
.finally(() => {
|
||||
// 获取列表数据
|
||||
fnGetList();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PageContainer>
|
||||
<a-card
|
||||
v-show="tableState.seached"
|
||||
:bordered="false"
|
||||
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
|
||||
>
|
||||
<!-- 表格搜索栏 -->
|
||||
<a-form :model="queryParams" name="queryParams" layout="horizontal">
|
||||
<a-row :gutter="16">
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item :label="t('views.ne.common.neType')" name="neType ">
|
||||
<a-auto-complete
|
||||
v-model:value="queryParams.neType"
|
||||
:options="neStore.getNeSelectOtions"
|
||||
allow-clear
|
||||
:placeholder="t('common.inputPlease')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :lg="6" :md="12" :xs="24">
|
||||
<a-form-item>
|
||||
<a-space :size="8">
|
||||
<a-button type="primary" @click.prevent="fnGetList(1)">
|
||||
<template #icon><SearchOutlined /></template>
|
||||
{{ t('common.search') }}
|
||||
</a-button>
|
||||
<a-button type="default" @click.prevent="fnQueryReset">
|
||||
<template #icon><ClearOutlined /></template>
|
||||
{{ t('common.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" :body-style="{ padding: '0px' }">
|
||||
<!-- 插槽-卡片左侧侧 -->
|
||||
<template #title>
|
||||
<a-space :size="8" align="center">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click.prevent="
|
||||
() => (modalState.openByEdit = !modalState.openByEdit)
|
||||
"
|
||||
>
|
||||
<template #icon><UploadOutlined /></template>
|
||||
{{ t('views.ne.neSoftware.upload') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="tableState.selectedRowKeys.length > 1"
|
||||
@click.prevent="
|
||||
() => (modalState.openByMoreFile = !modalState.openByMoreFile)
|
||||
"
|
||||
>
|
||||
<template #icon><UploadOutlined /></template>
|
||||
<template v-if="tableState.selectedRowOne.neType">
|
||||
{{ t('views.ne.neSoftware.upload') }}
|
||||
{{ tableState.selectedRowOne.neType }}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ t('views.ne.neSoftware.uploadBatch') }}
|
||||
</template>
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:ghost="true"
|
||||
:disabled="tableState.selectedRowKeys.length <= 0"
|
||||
:loading="modalState.confirmLoading"
|
||||
@click.prevent="fnRecordUpgradeConfirm()"
|
||||
>
|
||||
<template #icon><ThunderboltOutlined /></template>
|
||||
{{ t('views.ne.neVersion.upgradeBatch') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 插槽-卡片右侧 -->
|
||||
<template #extra>
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.searchBarText') }}</template>
|
||||
<a-switch
|
||||
v-model:checked="tableState.seached"
|
||||
:checked-children="t('common.switch.show')"
|
||||
:un-checked-children="t('common.switch.hide')"
|
||||
size="small"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<a-tooltip>
|
||||
<template #title>{{ t('common.reloadText') }}</template>
|
||||
<a-button type="text" @click.prevent="fnGetList()">
|
||||
<template #icon><ReloadOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>{{ t('common.sizeText') }}</template>
|
||||
<a-dropdown placement="bottomRight" trigger="click">
|
||||
<a-button type="text">
|
||||
<template #icon><ColumnHeightOutlined /></template>
|
||||
</a-button>
|
||||
<template #overlay>
|
||||
<a-menu
|
||||
:selected-keys="[tableState.size as string]"
|
||||
@click="fnTableSize"
|
||||
>
|
||||
<a-menu-item key="default">
|
||||
{{ t('common.size.default') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="middle">
|
||||
{{ t('common.size.middle') }}
|
||||
</a-menu-item>
|
||||
<a-menu-item key="small">
|
||||
{{ t('common.size.small') }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<!-- 表格列表 -->
|
||||
<a-table
|
||||
class="table"
|
||||
row-key="id"
|
||||
:columns="tableColumns"
|
||||
:loading="tableState.loading"
|
||||
:data-source="tableState.data"
|
||||
:size="tableState.size"
|
||||
:pagination="tablePagination"
|
||||
:scroll="{ x: tableColumns.length * 150 }"
|
||||
@resizeColumn="(w:number, col:any) => (col.width = w)"
|
||||
:row-selection="{
|
||||
type: 'checkbox',
|
||||
columnWidth: '48px',
|
||||
selectedRowKeys: tableState.selectedRowKeys,
|
||||
onChange: fnTableSelectedRowKeys,
|
||||
}"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'status'">
|
||||
<DictTag :options="dictStatus" :value="record.status" />
|
||||
</template>
|
||||
<template v-if="column.key === 'version'">
|
||||
{{ record.version }}
|
||||
<a-tooltip
|
||||
placement="topRight"
|
||||
v-if="
|
||||
record.version && (record.path === '' || record.path === '-')
|
||||
"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('views.ne.neVersion.noPath') }}
|
||||
</template>
|
||||
<InfoCircleOutlined />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'preVersion'">
|
||||
{{ record.preVersion }}
|
||||
<a-tooltip
|
||||
placement="topRight"
|
||||
v-if="
|
||||
record.preVersion &&
|
||||
(record.prePath === '' || record.prePath === '-')
|
||||
"
|
||||
>
|
||||
<template #title>
|
||||
{{ t('views.ne.neVersion.noPath') }}
|
||||
</template>
|
||||
<InfoCircleOutlined />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'id'">
|
||||
<a-space :size="8" align="center">
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{ t('views.ne.neVersion.upgrade') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordVersion('upgrade', record)"
|
||||
>
|
||||
<template #icon><ThunderboltOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip placement="topRight">
|
||||
<template #title>
|
||||
{{ t('views.ne.neVersion.rollback') }}
|
||||
</template>
|
||||
<a-button
|
||||
type="link"
|
||||
@click.prevent="fnRecordVersion('rollback', record)"
|
||||
>
|
||||
<template #icon><RollbackOutlined /></template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 新增单文件上传 -->
|
||||
<EditModal
|
||||
v-model:open="modalState.openByEdit"
|
||||
@ok="fnModalEditOk"
|
||||
@cancel="fnModalEditCancel"
|
||||
></EditModal>
|
||||
|
||||
<!-- 新增多文件上传框 -->
|
||||
<UploadMoreFile
|
||||
v-model:open="modalState.openByMoreFile"
|
||||
:ne-type="tableState.selectedRowOne.neType"
|
||||
@ok="fnModalEditOk"
|
||||
@cancel="fnModalEditCancel"
|
||||
></UploadMoreFile>
|
||||
|
||||
<!-- 勾选网元版本进行升级框 -->
|
||||
<ProModal
|
||||
:drag="true"
|
||||
:width="800"
|
||||
:destroyOnClose="true"
|
||||
:body-style="{ height: '520px', overflowY: 'scroll' }"
|
||||
:keyboard="false"
|
||||
:mask-closable="false"
|
||||
:open="modalState.openByUpgrade"
|
||||
:title="t('views.ne.neVersion.upgradeModal')"
|
||||
:closable="false"
|
||||
@ok="fnModalEditOk"
|
||||
@cancel="fnModalEditCancel"
|
||||
>
|
||||
<template #footer>
|
||||
<a-button
|
||||
key="submit"
|
||||
type="primary"
|
||||
:disabled="modalState.confirmLoading"
|
||||
@click="fnModalEditOk"
|
||||
>
|
||||
{{ t('common.close') }}
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<p>
|
||||
<a-alert
|
||||
v-if="modalState.confirmLoading"
|
||||
:message="t('common.loading')"
|
||||
type="info"
|
||||
show-icon
|
||||
>
|
||||
<template #icon>
|
||||
<LoadingOutlined />
|
||||
</template>
|
||||
</a-alert>
|
||||
</p>
|
||||
|
||||
<p v-for="o in modalState.operateDataUpgrade" :key="o.neUid">
|
||||
<a-alert
|
||||
:message="`${o.neType}-${o.neUid}`"
|
||||
:description="o.log"
|
||||
:type="o.status === 'done' ? 'success' : 'error'"
|
||||
show-icon
|
||||
>
|
||||
<template #icon>
|
||||
<CheckCircleOutlined v-if="o.status === 'done'" />
|
||||
<InfoCircleOutlined v-else />
|
||||
</template>
|
||||
</a-alert>
|
||||
</p>
|
||||
</ProModal>
|
||||
</PageContainer>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.table :deep(.ant-pagination) {
|
||||
padding: 0 24px;
|
||||
}
|
||||
</style>
|
||||
@@ -394,7 +394,6 @@ onBeforeUnmount(() => {});
|
||||
url="/tool/ping/run"
|
||||
:ne-type="state.params.neType"
|
||||
:ne-uid="state.params.neUid"
|
||||
:core-uid="state.params.coreUid"
|
||||
:rows="state.params.rows"
|
||||
:cols="state.params.cols"
|
||||
:process-messages="fnProcessMessage"
|
||||
|
||||
Reference in New Issue
Block a user