Merge remote-tracking branch 'origin/main' into multi-tenant

This commit is contained in:
TsMask
2025-02-14 20:16:54 +08:00
20 changed files with 1462 additions and 375 deletions

View File

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

View File

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

View File

@@ -19,12 +19,12 @@
"@codemirror/merge": "^6.8.0",
"@codemirror/theme-one-dark": "^6.1.2",
"@tato30/vue-pdf": "^1.11.3",
"@vueuse/core": "^12.3.0",
"@vueuse/core": "^12.5.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"ant-design-vue": "^4.2.6",
"antdv-pro-layout": "^4.2.0",
"antdv-pro-modal": "^4.0.5",
"antdv-pro-modal": "^4.0.6",
"codemirror": "^6.0.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.11",
@@ -39,7 +39,7 @@
"p-queue": "~8.0.1",
"pinia": "^2.3.0",
"vue": "^3.5.13",
"vue-i18n": "^11.0.1",
"vue-i18n": "^11.1.0",
"vue-router": "^4.5.0",
"vue3-smooth-dnd": "^0.0.6",
"xlsx": "~0.18.5"
@@ -54,8 +54,8 @@
"less": "^4.2.1",
"typescript": "~5.6.3",
"unplugin-vue-components": "^0.28.0",
"vite": "^6.0.6",
"vite": "^6.1.0",
"vite-plugin-compression": "~0.5.1",
"vue-tsc": "~2.1.10"
"vue-tsc": "^2.2.0"
}
}

BIN
public/nbStateImput/en.xlsx Normal file

Binary file not shown.

BIN
public/nbStateImput/zh.xlsx Normal file

Binary file not shown.

View File

@@ -1,3 +1,5 @@
import { CACHE_SESSION_CRYPTO_API } from '@/constants/cache-keys-constants';
import { sessionGet } from '@/utils/cache-session-utils';
import { request } from '@/plugins/http-fetch';
/**
@@ -62,6 +64,7 @@ export function updateFTPInfo(data: Record<string, any>) {
url: `/lm/table/ftp`,
method: 'post',
data: data,
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
});
}
@@ -74,6 +77,7 @@ export function getFTPInfo() {
return request({
url: `/lm/table/ftp`,
method: 'get',
crypto: sessionGet(CACHE_SESSION_CRYPTO_API) !== 'false',
});
}

View File

@@ -0,0 +1,30 @@
import { request } from '@/plugins/http-fetch';
/**
* 历史记录列表
* @param query 查询参数
* @returns object
*/
export function listNBState(query: Record<string, any>) {
return request({
url: '/neData/nb-state/list',
method: 'get',
params: query,
timeout: 60_000,
});
}
/**
* 历史记录列表导出
* @param data 查询列表条件
* @returns object
*/
export function exportNBState(data: Record<string, any>) {
return request({
url: '/neData/nb-state/export',
method: 'post',
data,
responseType: 'blob',
timeout: 60_000,
});
}

View File

@@ -726,6 +726,9 @@ export default {
time: "Change Time",
addRadio: "Add Radio Info",
editRadio: "Edit Radio Info",
history: "Status History",
exportTip: "Confirm exporting xlsx table files based on search criteria?",
importDataEmpty: "Imported data is empty",
},
},
neUser: {
@@ -738,6 +741,7 @@ export default {
checkExport : 'Check Export',
checkExportConfirm: 'Confirm exporting the checked authenticated user data?',
import: 'Import',
importFail: 'Failure Record',
loadDataConfirm: 'Are you sure you want to reload the data?',
loadData: 'Load Data',
loadDataTip: 'Successfully fetched loaded data: {num} items, the system is internally updating the data, it will take about {timer} seconds, please wait!!!!!.',
@@ -766,6 +770,7 @@ export default {
checkExport : 'Check Export',
checkExportConfirm: 'Are you sure to export the data of the checked subscribers?',
import: 'Import',
importFail: 'Failure Record',
loadDataConfirm: 'Are you sure you want to reload the data?',
loadData: 'Load Data',
loadDataTip: 'Successfully fetched loaded data: {num} items, the system is internally updating the data, it will take about {timer} seconds, please wait!!!!!.',
@@ -968,6 +973,7 @@ export default {
expressionModal:'Expression Modal',
expressionErrorTip:'Please check the expression, the wrong indicator is {kpiId}',
expressionNoIdTip:'Please check the expression, no valid indicator is found',
unitSelect:'To better display the image, the same unit needs to be selected. The current unit is:',
},
kpiKeyTarget:{
"time":"Time",

View File

@@ -726,6 +726,9 @@ export default {
time: "变更时间",
addRadio: "添加基站信息",
editRadio: "更新基站信息",
history: "历史记录",
exportTip: "确认根据搜索条件导出xlsx表格文件吗?",
importDataEmpty: "导入数据为空",
},
},
neUser: {
@@ -738,6 +741,7 @@ export default {
checkExport : '勾选导出',
checkExportConfirm: '确认导出已勾选的鉴权用户数据吗?',
import: '导入',
importFail: '失败记录',
loadDataConfirm: '确认要重新加载数据吗?',
loadData: '加载数据',
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!',
@@ -766,6 +770,7 @@ export default {
checkExport : '勾选导出',
checkExportConfirm: '确认导出已勾选的签约用户数据吗?',
import: '导入',
importFail: '失败记录',
loadDataConfirm: '确认要重新加载数据吗?',
loadData: '加载数据',
loadDataTip: '成功获取加载数据:{num}条,系统内部正在进行数据更新,大约需要{timer}秒,请稍候!!!',
@@ -968,6 +973,7 @@ export default {
expressionModal:'表达式模块',
expressionErrorTip:'请检查表达式,错误的指标为{kpiId}',
expressionNoIdTip:'请检查表达式,没有找到任何有效的指标',
unitSelect:'为更好展示图需选择相同单位,当前单位为:',
},
kpiKeyTarget:{
"time":"时间",

View File

@@ -42,7 +42,7 @@ export async function readLoalXlsx(
/**
* 读取表格数据 工作表
* @param fileBolb 文件对象
* @param index 文件保存路径
* @param index SheetName索引
* @return 表格对象列表
*/
export async function readSheet(

View File

@@ -290,13 +290,13 @@ type ModalStateType = {
/**FTP日志对象信息状态 */
let modalState: ModalStateType = reactive({
openByEdit: false,
title: 'FTP上报服务设置',
title: '设置远程备份配置',
from: {
useranme: '',
username: '',
password: '',
toIp: '',
toPort: 22,
protocol: 'ftp',
enable: false,
dir: '',
},
confirmLoading: false,
@@ -343,12 +343,12 @@ function fnModalVisibleByEdit() {
hide();
if (res.code === RESULT_CODE_SUCCESS && res.data) {
modalState.from = Object.assign(modalState.from, res.data);
modalState.title = 'FTP Info';
modalState.title = 'Setting Remote Backup';
modalState.openByEdit = true;
} else {
message.error(res.msg, 3);
modalState.title = 'FTP Info';
modalState.openByEdit = true;
modalState.title = 'Setting Remote Backup';
modalState.openByEdit = false;
}
});
}
@@ -361,9 +361,10 @@ function fnModalOk() {
updateFTPInfo(from)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(`FTP configuration saved successfully`, 3);
message.success(`Configuration saved successfully`, 3);
fnModalCancel();
} else {
message.warning(`FTP configuration save exception`, 3);
message.warning(`Configuration save exception`, 3);
}
})
.finally(() => {
@@ -389,7 +390,7 @@ function fnSyncFileToFTP(row: Record<string, any>) {
putFTPInfo(row.filePath, row.fileName)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success(`OK`, 3);
message.success(t('common.operateOk'), 3);
} else {
message.warning(res.msg, 3);
}
@@ -435,7 +436,7 @@ function fnSyncFileToFTP(row: Record<string, any>) {
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>Setting</template>
<template #title>Setting Remote Backup</template>
<a-button type="text" @click.prevent="fnModalVisibleByEdit()">
<template #icon><DeliveredProcedureOutlined /></template>
</a-button>
@@ -512,91 +513,84 @@ function fnSyncFileToFTP(row: Record<string, any>) {
:label-col="{ span: 6 }"
:label-wrap="true"
>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="Service IP"
name="toIp"
v-bind="modalStateFrom.validateInfos.toIp"
>
<a-input
v-model:value="modalState.from.toIp"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="Service Port"
name="toPort"
v-bind="modalStateFrom.validateInfos.toPort"
>
<a-input-number
v-model:value="modalState.from.toPort"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="UserName"
name="username"
v-bind="modalStateFrom.validateInfos.username"
>
<a-input
v-model:value="modalState.from.username"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="Password"
name="password"
v-bind="modalStateFrom.validateInfos.password"
>
<a-input-password
v-model:value="modalState.from.password"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input-password>
</a-form-item>
</a-col>
</a-row>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="Protocol" name="protocol">
<a-select
v-model:value="modalState.from.protocol"
default-value="N"
:placeholder="t('common.selectPlease')"
:options="[
{ value: 'ftp', label: 'FTP' },
{ value: 'ssh', label: 'SSH' },
]"
<a-form-item label="Enable" name="enable" :label-col="{ span: 3 }">
<a-switch
v-model:checked="modalState.from.enable"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
/>
</a-form-item>
<template v-if="modalState.from.enable">
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="Service IP"
name="toIp"
v-bind="modalStateFrom.validateInfos.toIp"
>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="Save Dir"
name="dir"
v-bind="modalStateFrom.validateInfos.dir"
>
<a-input
v-model:value="modalState.from.dir"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
</a-row>
<a-input
v-model:value="modalState.from.toIp"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="Service Port"
name="toPort"
v-bind="modalStateFrom.validateInfos.toPort"
>
<a-input-number
v-model:value="modalState.from.toPort"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="UserName"
name="username"
v-bind="modalStateFrom.validateInfos.username"
>
<a-input
v-model:value="modalState.from.username"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="Password"
name="password"
v-bind="modalStateFrom.validateInfos.password"
>
<a-input-password
v-model:value="modalState.from.password"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input-password>
</a-form-item>
</a-col>
</a-row>
<a-form-item
label="Save Dir"
name="dir"
v-bind="modalStateFrom.validateInfos.dir"
:label-col="{ span: 3 }"
>
<a-input
v-model:value="modalState.from.dir"
allow-clear
:placeholder="t('common.inputPlease')"
></a-input>
</a-form-item>
</template>
</a-form>
</ProModal>
</PageContainer>

View File

@@ -0,0 +1,383 @@
<script setup lang="ts">
import { reactive, ref, toRaw, watch } from 'vue';
import { ProModal } from 'antdv-pro-modal';
import { message, Modal } from 'ant-design-vue/es';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { ColumnsType } from 'ant-design-vue/es/table';
import { saveAs } from 'file-saver';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useI18n from '@/hooks/useI18n';
import { exportNBState, listNBState } from '@/api/neData/nb-state';
import { Dayjs } from 'dayjs';
const { t } = useI18n();
const emit = defineEmits(['cancel', 'update:open']);
const props = defineProps({
title: {
type: String,
default: '标题',
},
open: {
type: Boolean,
default: false,
},
/**网元ID */
neId: {
type: String,
default: false,
},
neType: {
type: String,
default: false,
},
});
/**开始结束时间 */
let queryRangePicker = ref<[Dayjs, Dayjs] | undefined>(undefined);
/**状态字典 */
const nbStateOptions = ref<DictType[]>([
{
value: 'ON',
label: t('views.neData.baseStation.online'),
tagType: 'green',
tagClass: '',
},
{
value: 'OFF',
label: t('views.neData.baseStation.offline'),
tagType: 'red',
tagClass: '',
},
]);
/**查询参数 */
let queryParams = reactive({
/**网元 */
neType: '',
neId: '',
/**排序字段 */
sortField: 'id',
sortOrder: 'desc',
/**状态 */
status: undefined as undefined | string,
/**开始时间 */
startTime: undefined as undefined | number,
/**结束时间 */
endTime: undefined as undefined | number,
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
status: undefined,
startTime: 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: true,
data: [],
selectedRowKeys: [],
});
/**表格字段列 */
let tableColumns = ref<ColumnsType>([
{
title: t('common.rowId'),
dataIndex: 'id',
align: 'left',
width: 80,
},
{
title: t('views.neData.baseStation.name'),
dataIndex: 'name',
align: 'left',
resizable: true,
width: 120,
minWidth: 100,
maxWidth: 250,
ellipsis: true,
},
{
title: t('views.neData.baseStation.position'),
dataIndex: 'position',
align: 'left',
resizable: true,
width: 150,
minWidth: 100,
maxWidth: 400,
ellipsis: true,
},
{
title: t('views.neData.baseStation.address'),
dataIndex: 'address',
align: 'left',
width: 100,
},
{
title: t('views.neData.baseStation.nbName'),
dataIndex: 'nbName',
align: 'left',
resizable: true,
width: 100,
minWidth: 100,
maxWidth: 200,
},
{
title: t('views.neData.baseStation.state'),
dataIndex: 'state',
key: 'state',
align: 'left',
width: 80,
},
{
title: t('views.neData.baseStation.time'),
dataIndex: 'time',
align: 'left',
width: 150,
},
]);
/**表格分页器参数 */
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: total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
queryParams.pageNum = page;
queryParams.pageSize = pageSize;
fnGetList();
},
});
/**列表导出 */
function fnExportList() {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.neData.baseStation.exportTip'),
onOk() {
const hide = message.loading(t('common.loading'), 0);
const querys = toRaw(queryParams);
exportNBState(querys)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
saveAs(res.data, `nb_state_history_records_export_${Date.now()}.xlsx`);
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
hide();
});
},
});
}
/**查询字典数据列表, 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.startTime = queryRangePicker.value[0].valueOf();
queryParams.endTime = queryRangePicker.value[1].valueOf();
} else {
queryParams.startTime = undefined;
queryParams.endTime = undefined;
}
listNBState(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// 取消勾选
if (tableState.selectedRowKeys.length > 0) {
tableState.selectedRowKeys = [];
}
tablePagination.total = res.total;
tableState.data = res.rows;
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
fnGetList(queryParams.pageNum - 1);
}
}
tableState.loading = false;
});
}
/**弹框取消按钮事件 */
function fnModalCancel() {
emit('update:open', false);
emit('cancel');
}
/**监听是否显示,初始数据 */
watch(
() => props.open,
val => {
if (val) {
queryParams.neType = props.neType;
queryParams.neId = props.neId;
// 获取列表数据
fnGetList();
}
}
);
</script>
<template>
<ProModal
:drag="true"
:destroyOnClose="true"
:width="1200"
:title="props.title"
:open="props.open"
:keyboard="false"
:mask-closable="false"
@cancel="fnModalCancel"
:footer="null"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16" style="margin-left: 0; margin-right: 0">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item
:label="t('views.neData.baseStation.state')"
name="status"
>
<a-select
v-model:value="queryParams.status"
allow-clear
:placeholder="t('common.selectPlease')"
:options="nbStateOptions"
>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="16" :md="12" :xs="24">
<a-form-item
:label="t('views.neData.baseStation.time')"
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="24" :md="24" :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-button type="dashed" @click.prevent="fnExportList()">
<template #icon><ExportOutlined /></template>
{{ t('common.export') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
<!-- 表格列表 -->
<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, y: 'calc(100vh - 480px)' }"
@resizeColumn="(w:number, col:any) => (col.width = w)"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'state'">
<DictTag
:options="nbStateOptions"
:value="record.state"
value-default="OFF"
/>
</template>
</template>
</a-table>
</ProModal>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -1,8 +1,8 @@
<script setup lang="ts">
import { ref } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import ListComponent from '@/views/ne-data/base-station/components/list.vue';
import TopologyComponent from '@/views/ne-data/base-station/components/topology.vue';
import ListComponent from './list.vue';
import TopologyComponent from './topology.vue';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
const value = ref<string>('list');

View File

@@ -1,27 +1,39 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw, computed } from 'vue';
import {
reactive,
ref,
onMounted,
toRaw,
computed,
defineAsyncComponent,
} from 'vue';
import { Form, message, Modal } from 'ant-design-vue';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { ColumnsType } from 'ant-design-vue/es/table';
import { ProModal } from 'antdv-pro-modal';
import UploadModal from '@/components/UploadModal/index.vue';
import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
const { t, currentLocale } = useI18n();
import {
addAMFNbState,
delAMFNbState,
editAMFNbState,
listAMFNbStatelist,
} from '@/api/neData/amf';
const { t } = useI18n();
import { useRoute } from 'vue-router';
import {
addMMENbState,
delMMENbState,
editMMENbState,
listMMENbStatelist,
} from '@/api/neData/mme';
const route = useRoute();
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import saveAs from 'file-saver';
import { readSheet, writeSheet } from '@/utils/execl-utils';
//
const HistoryModal = defineAsyncComponent(
() => import('./components/history.vue')
);
const nbState = ref<DictType[]>([
{
@@ -308,6 +320,11 @@ type ModalStateType = {
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
/**历史框 */
openByHistory: boolean;
/**导入框 */
openByImport: boolean;
importMsgArr: string[];
};
/**对话框对象信息状态 */
@@ -326,6 +343,9 @@ let modalState: ModalStateType = reactive({
ueNum: undefined,
},
confirmLoading: false,
openByHistory: false,
openByImport: false,
importMsgArr: [],
});
/**对话框内表单属性和校验规则 */
@@ -432,9 +452,205 @@ function fnModalOk() {
*/
function fnModalCancel() {
modalState.openByEdit = false;
modalState.openByHistory = false;
modalStateFrom.resetFields();
}
/**导出当前列表 */
function fnExportList() {
Modal.confirm({
title: t('common.tipTitle'),
content: t('views.neData.baseStation.exportTip'),
onOk: async () => {
if (modalState.confirmLoading) return;
modalState.confirmLoading = true;
let rows: Record<string, any>[] = [];
//
if (tableState.selectedRowKeys.length > 0) {
rows = tableState.data.filter(item =>
tableState.selectedRowKeys.includes(item.index)
);
} else {
rows = tableState.data;
}
const dataArr: Record<string, any>[] = [];
for (const row of rows) {
let data: any = {};
data[t('views.neData.baseStation.name')] = row.name;
data[t('views.neData.baseStation.position')] = row.position;
data[t('views.neData.baseStation.address')] = row.address;
data[t('views.neData.baseStation.nbName')] = row.nbName;
data[t('views.neData.baseStation.ueNum')] = row.ueNum;
nbState.value.find(item => {
if (item.value === row.state) {
data[t('views.neData.baseStation.state')] = item.label;
}
});
data[t('views.neData.baseStation.time')] = row.time || '-';
dataArr.push(data);
}
//
writeSheet(dataArr, 'Sheet1', {
header: [
t('views.neData.baseStation.name'),
t('views.neData.baseStation.position'),
t('views.neData.baseStation.address'),
t('views.neData.baseStation.nbName'),
t('views.neData.baseStation.ueNum'),
t('views.neData.baseStation.state'),
t('views.neData.baseStation.time'),
],
}).then(fileBlob =>
saveAs(fileBlob, `nb_state_records_export_${Date.now()}.xlsx`)
);
modalState.confirmLoading = false;
tableState.selectedRowKeys = [];
},
});
}
/**对话框弹出历史窗口 */
function fnHistoryView() {
modalState.openByHistory = true;
}
/**对话框表格信息导入弹出窗口 */
function fnModalImportOpen() {
modalState.openByImport = true;
}
function fnModalImportClose() {
modalState.openByImport = false;
fnQueryReset();
}
/**对话框表格信息导入上传 */
function fnModalImportUpload(file: File) {
const hide = message.loading(t('common.loading'), 0);
const [neType, neId] = neTypeAndId.value;
modalState.importMsgArr = [];
// index
let index = 0;
if (tableState.data.length <= 0) {
index = 1;
} else {
const last = tableState.data[tableState.data.length - 1];
index = last.index + 1;
}
const reader = new FileReader();
reader.onload = function (e: any) {
const arrayBuffer = e.target.result;
readSheet(arrayBuffer).then(async rows => {
console.log(rows);
if (rows.length <= 0) {
hide();
message.error({
content: t('views.neData.baseStation.importDataEmpty'),
duration: 3,
});
return;
}
//
modalState.confirmLoading = true;
for (const row of rows) {
const rowId = row[t('common.rowId')];
const name = row[t('views.neData.baseStation.name')];
const position = row[t('views.neData.baseStation.position')];
const address = row[t('views.neData.baseStation.address')];
let result: any = null;
// IP
const hasAddress = tableState.data.find(
item => item.address === address
);
if (hasAddress) {
//
if (neType === 'MME') {
result = await editMMENbState(
neId,
Object.assign({}, hasAddress, {
name,
position,
})
);
}
if (neType === 'AMF') {
result = await editAMFNbState(
neId,
Object.assign({}, hasAddress, {
name,
position,
})
);
}
let msg = `${t('common.rowId')}: ${rowId} ${t(
'views.neData.baseStation.editRadio'
)}${t('common.operateErr')}`;
if (result.code === RESULT_CODE_SUCCESS) {
msg = `${t('common.rowId')}: ${rowId} ${t(
'views.neData.baseStation.editRadio'
)}${t('common.operateOk')}`;
}
modalState.importMsgArr.push(msg);
} else {
//
const form = {
index,
name: `${name}`,
position: `${position}`,
address: `${address}`,
};
if (neType === 'MME') {
result = await addMMENbState(neId, form);
}
if (neType === 'AMF') {
result = await addAMFNbState(neId, form);
}
let msg = `${t('common.rowId')}: ${rowId} ${t(
'views.neData.baseStation.addRadio'
)}${t('common.operateErr')}`;
if (result.code === RESULT_CODE_SUCCESS) {
index += 1;
msg = `${t('common.rowId')}: ${rowId} ${t(
'views.neData.baseStation.addRadio'
)}${t('common.operateOk')}`;
}
modalState.importMsgArr.push(msg);
}
}
hide();
modalState.confirmLoading = false;
});
};
reader.onerror = function (e) {
hide();
console.error('reader file error:', e);
};
reader.readAsArrayBuffer(file);
}
/**对话框表格信息导入模板 */
async function fnModalImportTemplate() {
const baseUrl = import.meta.env.VITE_HISTORY_BASE_URL;
const xlsxUrl = `${
baseUrl.length === 1 && baseUrl.indexOf('/') === 0
? ''
: baseUrl.indexOf('/') === -1
? '/' + baseUrl
: baseUrl
}/nbStateImput`;
const lang = currentLocale.value.split('_')[0];
saveAs(
`${xlsxUrl}/${lang}.xlsx`,
`nb_state_records_import_template_${Date.now()}.xlsx`
);
}
onMounted(() => {
//
useNeInfoStore()
@@ -450,8 +666,7 @@ onMounted(() => {
});
neCascaderOptions.value = arr;
// neType AMF
const queryNeType = (route.query.neType as string) || 'AMF';
const item = arr.find(s => s.value === queryNeType);
const item = arr.find(s => s.value === 'AMF');
if (item && item.children) {
const info = item.children[0];
neTypeAndId.value = [info.neType, info.neId];
@@ -554,6 +769,18 @@ onMounted(() => {
<template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }}
</a-button>
<a-button type="dashed" @click.prevent="fnModalImportOpen()">
<template #icon><ImportOutlined /></template>
{{ t('common.import') }}
</a-button>
<a-button type="dashed" @click.prevent="fnExportList()">
<template #icon><ExportOutlined /></template>
{{ t('common.export') }}
</a-button>
<a-button type="default" @click.prevent="fnHistoryView()">
<template #icon><ContainerOutlined /></template>
{{ t('views.neData.baseStation.history') }}
</a-button>
</a-space>
</template>
@@ -677,6 +904,47 @@ onMounted(() => {
</a-form-item>
</a-form>
</ProModal>
<!-- 状态历史框 -->
<HistoryModal
v-model:open="modalState.openByHistory"
:title="t('views.neData.baseStation.history')"
:ne-type="neTypeAndId[0]"
:ne-id="neTypeAndId[1]"
@cancel="fnModalCancel"
></HistoryModal>
<!-- 上传导入表格数据文件框 -->
<UploadModal
:title="t('common.import')"
@upload="fnModalImportUpload"
@close="fnModalImportClose"
v-model:open="modalState.openByImport"
:ext="['.xls', '.xlsx']"
:size="10"
>
<template #default>
<a-row justify="space-between" align="middle">
<a-col :span="12"> </a-col>
<a-col :span="6">
<a-button
type="link"
:title="t('views.system.user.downloadObj')"
@click.prevent="fnModalImportTemplate"
>
{{ t('views.system.user.downloadObj') }}
</a-button>
</a-col>
</a-row>
<a-textarea
:disabled="true"
:hidden="modalState.importMsgArr.length <= 0"
:value="modalState.importMsgArr.join('\r\n')"
:auto-size="{ minRows: 2, maxRows: 8 }"
style="background-color: transparent; color: rgba(0, 0, 0, 0.85)"
/>
</template>
</UploadModal>
</div>
</template>

View File

@@ -25,6 +25,7 @@ import {
listUDMAuth,
} from '@/api/neData/udm_auth';
import { uploadFile } from '@/api/tool/file';
import { getNeViewFile } from '@/api/tool/neFile';
const { t } = useI18n();
/**网元参数 */
@@ -98,12 +99,6 @@ let tableColumns = ref<ColumnsType>([
align: 'center',
width: 80,
},
{
title: 'Status',
dataIndex: 'status',
align: 'center',
width: 80,
},
// {
// title: 'KI',
// dataIndex: 'ki',
@@ -598,6 +593,8 @@ type ModalUploadImportStateType = {
loading: boolean;
/**上传结果信息 */
msg: string;
/**含失败信息 */
hasFail: boolean;
/**导入类型 */
typeOptions: { label: string; value: string }[];
/**表单 */
@@ -610,6 +607,7 @@ let uploadImportState: ModalUploadImportStateType = reactive({
title: t('components.UploadModal.uploadTitle'),
loading: false,
msg: '',
hasFail: false,
typeOptions: [
{ label: 'Default', value: 'default' },
{ label: 'K4', value: 'k4' },
@@ -626,9 +624,37 @@ function fnModalUploadImportTypeChange() {
uploadImportState.msg = '';
}
/**对话框表格信息导入失败原因 */
function fnModalUploadImportFailReason() {
const neId = queryParams.neId;
if (!neId) return;
const hide = message.loading(t('common.loading'), 0);
getNeViewFile({
neType: 'UDM',
neId: neId,
path: '/tmp',
fileName: 'import_authdata_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_authdata_err_records_${Date.now()}.txt`);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
}
/**对话框表格信息导入弹出窗口 */
function fnModalUploadImportOpen() {
uploadImportState.msg = '';
uploadImportState.hasFail = false;
uploadImportState.from.typeVal = 'default';
uploadImportState.from.typeData = undefined;
uploadImportState.loading = false;
@@ -675,6 +701,14 @@ function fnModalUploadImportUpload(file: File) {
.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();
@@ -973,25 +1007,7 @@ onMounted(() => {
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
:label="t('views.neUser.auth.numAdd')"
name="num"
v-bind="modalStateFrom.validateInfos.num"
v-show="!modalState.from.id"
>
<a-input-number
v-model:value="modalState.from.num"
style="width: 100%"
:min="1"
:max="10000"
placeholder="<=10000"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row>
<a-row v-show="!modalState.from.id">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item
label="IMSI"
@@ -1019,11 +1035,20 @@ onMounted(() => {
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="Status" name="status">
<a-select value="1">
<a-select-option value="1">Active</a-select-option>
<a-select-option value="0">Inactive</a-select-option>
</a-select>
<a-form-item
:label="t('views.neUser.auth.numAdd')"
name="num"
v-bind="modalStateFrom.validateInfos.num"
:label-col="{ span: 10 }"
:labelWrap="false"
>
<a-input-number
v-model:value="modalState.from.num"
style="width: 100%"
:min="1"
:max="10000"
placeholder="<=10000"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
@@ -1056,6 +1081,8 @@ onMounted(() => {
label="Algo Index"
name="algo"
v-bind="modalStateFrom.validateInfos.algoIndex"
:label-col="{ span: 10 }"
:labelWrap="false"
>
<a-input-number
v-model:value="modalState.from.algoIndex"
@@ -1141,7 +1168,7 @@ onMounted(() => {
<a-form
name="modalStateBatchDelFrom"
layout="horizontal"
:label-col="{ span: 6 }"
:label-col="{ span: 8 }"
:labelWrap="true"
>
<a-row>
@@ -1210,13 +1237,23 @@ onMounted(() => {
v-model:value="uploadImportState.from.typeData"
:placeholder="t('common.inputPlease')"
/>
<a-textarea
:disabled="true"
:hidden="!uploadImportState.msg"
:value="uploadImportState.msg"
:auto-size="{ minRows: 2, maxRows: 8 }"
style="background-color: transparent; color: rgba(0, 0, 0, 0.85)"
/>
<a-alert
:message="uploadImportState.msg"
:type="uploadImportState.hasFail ? 'warning' : 'info'"
v-show="uploadImportState.msg.length > 0"
>
<template #action>
<a-button
size="small"
type="link"
danger
@click="fnModalUploadImportFailReason"
v-if="uploadImportState.hasFail"
>
{{ t('views.neUser.auth.importFail') }}
</a-button>
</template>
</a-alert>
</template>
</UploadModal>
</PageContainer>

View File

@@ -26,6 +26,7 @@ import {
} from '@/api/neData/udm_sub';
import { listTenant } from '@/api/system/tenant';
import { uploadFile } from '@/api/tool/file';
import { getNeViewFile } from '@/api/tool/neFile';
const { t } = useI18n();
/**网元参数 */
@@ -577,7 +578,8 @@ function transformFormData(data: any) {
if (isValid) {
smStaticIpArr.push(dnnParts);
}
} else {//无/ 无:也有可能为dnn的字符串
} else {
//无/ 无:也有可能为dnn的字符串
smallRowJson.dnn += '-' + dnnParts;
}
}
@@ -668,8 +670,8 @@ function fnModalOk() {
const result = from.id
? updateUDMSub(from)
: from.num === 1
? addUDMSub(from)
: batchAddUDMSub(from, from.num);
? addUDMSub(from)
: batchAddUDMSub(from, from.num);
const hide = message.loading(t('common.loading'), 0);
result
.then(res => {
@@ -999,7 +1001,7 @@ function fnGetList(pageNum?: number) {
tableState.data = res.rows;
if (
tablePagination.total <=
(queryParams.pageNum - 1) * tablePagination.pageSize &&
(queryParams.pageNum - 1) * tablePagination.pageSize &&
queryParams.pageNum !== 1
) {
tableState.loading = false;
@@ -1020,6 +1022,8 @@ type ModalUploadImportStateType = {
loading: boolean;
/**上传结果信息 */
msg: string;
/**含失败信息 */
hasFail: boolean;
};
/**对话框表格信息导入对象信息状态 */
@@ -1028,11 +1032,40 @@ let uploadImportState: ModalUploadImportStateType = reactive({
title: t('components.UploadModal.uploadTitle'),
loading: false,
msg: '',
hasFail: false,
});
/**对话框表格信息导入失败原因 */
function fnModalUploadImportFailReason() {
const neId = queryParams.neId;
if (!neId) return;
const hide = message.loading(t('common.loading'), 0);
getNeViewFile({
neType: 'UDM',
neId: neId,
path: '/tmp',
fileName: 'import_udmuser_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_udmuser_err_records_${Date.now()}.txt`);
} else {
message.error(`${res.msg}`, 3);
}
})
.finally(() => {
hide();
});
}
/**对话框表格信息导入弹出窗口 */
function fnModalUploadImportOpen() {
uploadImportState.msg = '';
uploadImportState.hasFail = false;
uploadImportState.loading = false;
uploadImportState.open = true;
}
@@ -1076,6 +1109,14 @@ function fnModalUploadImportUpload(file: File) {
.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();
@@ -1174,14 +1215,22 @@ onMounted(() => {
<template>
<PageContainer>
<a-card v-show="tableState.seached" :bordered="false" :body-style="{ marginBottom: '24px', paddingBottom: 0 }">
<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.neUser.sub.neType')" name="neId ">
<a-select v-model:value="queryParams.neId" :options="neOtions" :placeholder="t('common.selectPlease')"
@change="fnGetList(1)" />
<a-select
v-model:value="queryParams.neId"
:options="neOtions"
:placeholder="t('common.selectPlease')"
@change="fnGetList(1)"
/>
</a-form-item>
</a-col>
@@ -1258,16 +1307,31 @@ onMounted(() => {
{{ t('common.addText') }}
</a-button>
<a-button type="primary" danger ghost @click.prevent="fnModalVisibleByBatch()">
<a-button
type="primary"
danger
ghost
@click.prevent="fnModalVisibleByBatch()"
>
<template #icon>
<DeleteOutlined />
</template>
{{ t('views.neUser.auth.batchDelText') }}
</a-button>
<a-popconfirm :title="t('views.neUser.sub.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">
<a-popconfirm
:title="t('views.neUser.sub.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>
@@ -1282,8 +1346,13 @@ onMounted(() => {
{{ t('views.neUser.sub.import') }}
</a-button>
<a-popconfirm :title="t('views.neUser.sub.exportConfirm')" placement="topRight" ok-text="TXT"
ok-type="default" @confirm="fnExportList('txt')">
<a-popconfirm
:title="t('views.neUser.sub.exportConfirm')"
placement="topRight"
ok-text="TXT"
ok-type="default"
@confirm="fnExportList('txt')"
>
<a-button type="dashed">
<template #icon>
<ExportOutlined />
@@ -1292,17 +1361,31 @@ onMounted(() => {
</a-button>
</a-popconfirm>
<a-button type="default" danger :disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.loadDataLoading" @click.prevent="fnRecordDelete('0')">
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.loadDataLoading"
@click.prevent="fnRecordDelete('0')"
>
<template #icon>
<DeleteOutlined />
</template>
{{ t('views.neUser.sub.checkDel') }}
</a-button>
<a-popconfirm :title="t('views.neUser.sub.checkExportConfirm')" placement="topRight" ok-text="TXT"
ok-type="default" @confirm="fnRecordExport('txt')" :disabled="tableState.selectedRowKeys.length <= 0">
<a-button type="default" :disabled="tableState.selectedRowKeys.length <= 0">
<a-popconfirm
:title="t('views.neUser.sub.checkExportConfirm')"
placement="topRight"
ok-text="TXT"
ok-type="default"
@confirm="fnRecordExport('txt')"
:disabled="tableState.selectedRowKeys.length <= 0"
>
<a-button
type="default"
:disabled="tableState.selectedRowKeys.length <= 0"
>
<template #icon>
<ExportOutlined />
</template>
@@ -1317,8 +1400,12 @@ onMounted(() => {
<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-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>
@@ -1328,7 +1415,11 @@ onMounted(() => {
</template>
</a-button>
</a-tooltip>
<TableColumnsDnd cache-id="udmSubData" :columns="tableColumns" v-model:columns-dnd="tableColumnsDnd">
<TableColumnsDnd
cache-id="udmSubData"
:columns="tableColumns"
v-model:columns-dnd="tableColumnsDnd"
>
</TableColumnsDnd>
<a-tooltip placement="topRight">
<template #title>{{ t('common.sizeText') }}</template>
@@ -1339,7 +1430,10 @@ onMounted(() => {
</template>
</a-button>
<template #overlay>
<a-menu :selected-keys="[tableState.size as string]" @click="fnTableSize">
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
@@ -1357,14 +1451,23 @@ onMounted(() => {
</template>
<!-- 表格列表 -->
<a-table class="table" row-key="imsi" :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="{
<a-table
class="table"
row-key="imsi"
: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 === 'cnFlag'">
{{
@@ -1384,7 +1487,10 @@ onMounted(() => {
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.editText') }}</template>
<a-button type="link" @click.prevent="fnModalVisibleByEdit(record.imsi)">
<a-button
type="link"
@click.prevent="fnModalVisibleByEdit(record.imsi)"
>
<template #icon>
<FormOutlined />
</template>
@@ -1392,7 +1498,10 @@ onMounted(() => {
</a-tooltip>
<a-tooltip>
<template #title>{{ t('common.deleteText') }}</template>
<a-button type="link" @click.prevent="fnRecordDelete(record.imsi)">
<a-button
type="link"
@click.prevent="fnRecordDelete(record.imsi)"
>
<template #icon>
<DeleteOutlined />
</template>
@@ -1405,26 +1514,59 @@ onMounted(() => {
</a-card>
<!-- 新增框或修改框 -->
<ProModal :drag="true" :width="800" :destroyOnClose="true" style="top: 0px"
:body-style="{ maxHeight: '600px', 'overflow-y': 'auto' }" :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">
<ProModal
:drag="true"
:width="800"
:destroyOnClose="true"
style="top: 0px"
:body-style="{ maxHeight: '600px', 'overflow-y': 'auto' }"
: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"
>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.neUser.sub.numAdd')" name="num" v-bind="modalStateFrom.validateInfos.num"
v-show="!modalState.from.id">
<a-input-number v-model:value="modalState.from.num" style="width: 100%" :min="1" :max="10000"
placeholder="<=10000"></a-input-number>
<a-col :lg="12" :md="12" :xs="24" v-show="!modalState.from.id">
<a-form-item
:label="t('views.neUser.sub.numAdd')"
name="num"
v-bind="modalStateFrom.validateInfos.num"
:label-col="{ span: 12 }"
:labelWrap="false"
>
<a-input-number
v-model:value="modalState.from.num"
style="width: 100%"
:min="1"
:max="10000"
placeholder="<=10000"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
<a-row>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="IMSI" name="imsi" v-bind="modalStateFrom.validateInfos.imsi">
<a-input v-model:value="modalState.from.imsi" allow-clear :maxlength="15"
:disabled="!!modalState.from.id">
<a-form-item
label="IMSI"
name="imsi"
v-bind="modalStateFrom.validateInfos.imsi"
>
<a-input
v-model:value="modalState.from.imsi"
allow-clear
:maxlength="15"
:disabled="!!modalState.from.id"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
@@ -1440,8 +1582,16 @@ onMounted(() => {
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="MSISDN" name="msisdn" v-bind="modalStateFrom.validateInfos.msisdn">
<a-input v-model:value="modalState.from.msisdn" allow-clear :maxlength="32">
<a-form-item
label="MSISDN"
name="msisdn"
v-bind="modalStateFrom.validateInfos.msisdn"
>
<a-input
v-model:value="modalState.from.msisdn"
allow-clear
:maxlength="32"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
@@ -1455,24 +1605,49 @@ onMounted(() => {
</a-col>
</a-row>
<a-form-item :label="t('common.remark')" :label-col="{ span: 3 }" :label-wrap="true">
<a-textarea v-model:value="modalState.from.remark" :auto-size="{ minRows: 1, maxRows: 6 }" :maxlength="500"
:show-count="true" :placeholder="t('common.inputPlease')" />
<a-form-item
:label="t('common.remark')"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-textarea
v-model:value="modalState.from.remark"
:auto-size="{ minRows: 1, maxRows: 6 }"
:maxlength="500"
:show-count="true"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
<!-- SM Data ---- S -->
<a-divider orientation="left">
Subscribed SM Data
<a-tooltip title="Add SM Data">
<a-button shape="circle" @click="addBigRow" style="margin-left: 10px">
<a-button
shape="circle"
@click="addBigRow"
style="margin-left: 10px"
>
<template #icon><plus-outlined /></template>
</a-button> </a-tooltip></a-divider>
</a-button> </a-tooltip
></a-divider>
<!-- 大数组布局 -->
<div v-for="(row, index) in bigRows" :key="String(row.id)">
<a-row>
<a-col :lg="6" :md="6" :xs="24">
<a-form-item label="SST" name="row.sst" :label-col="{ span: 12 }" :validateTrigger="[]" :required="true">
<a-input-number v-model:value="row.sst" :min="1" :max="3" :step="1" />
<a-form-item
label="SST"
name="row.sst"
:label-col="{ span: 12 }"
:validateTrigger="[]"
:required="true"
>
<a-input-number
v-model:value="row.sst"
:min="1"
:max="3"
:step="1"
/>
</a-form-item>
</a-col>
@@ -1485,7 +1660,11 @@ onMounted(() => {
<a-row>
<a-col :span="4">
<a-tooltip title="Add DNN">
<a-button shape="circle" @click="addSmallRow(row.id)" style="margin-left:10px ;">
<a-button
shape="circle"
@click="addSmallRow(row.id)"
style="margin-left: 10px"
>
<template #icon><plus-square-outlined /></template>
</a-button>
</a-tooltip>
@@ -1502,27 +1681,51 @@ onMounted(() => {
</a-row>
<!-- 小数组布局 -->
<div v-for="(smallRow, smallIndex) in row.smallRows" :key="String(smallRow.id)">
<div
v-for="(smallRow, smallIndex) in row.smallRows"
:key="String(smallRow.id)"
>
<a-row>
<a-col :lg="6" :md="6" :xs="24">
<a-form-item label="DNN/APN" name="dnn" :validateTrigger="[]" :required="true"
:label-col="{ span: 12 }">
<a-form-item
label="DNN/APN"
name="dnn"
:validateTrigger="[]"
:required="true"
:label-col="{ span: 12 }"
>
<a-input v-model:value="smallRow.dnn" allow-clear></a-input>
</a-form-item>
</a-col>
<a-col :lg="8" :md="8" :xs="24">
<a-form-item label="Static IP" name="smStaticIp" :label-col="{ span: 5 }">
<a-input v-model:value="smallRow.smStaticIp" allow-clear></a-input>
<a-form-item
label="Static IP"
name="smStaticIp"
:label-col="{ span: 5 }"
>
<a-input
v-model:value="smallRow.smStaticIp"
allow-clear
></a-input>
</a-form-item>
</a-col>
<a-col :lg="8" :md="8" :xs="24">
<a-form-item label="Routing Behind MS IP" style="margin-left:10px ;" name="msIp">
<a-form-item
label="Routing Behind MS IP"
style="margin-left: 10px"
name="msIp"
>
<a-input v-model:value="smallRow.msIp" allow-clear></a-input>
</a-form-item>
</a-col>
<a-col :lg="2" :md="2" :xs="24" v-if="smallIndex !== 0">
<a-tooltip title="Delete DNN">
<a-button danger shape="circle" @click="delDNN(smallIndex, row.id)" style="margin-left:10px ;">
<a-button
danger
shape="circle"
@click="delDNN(smallIndex, row.id)"
style="margin-left: 10px"
>
<template #icon><close-square-outlined /></template>
</a-button>
</a-tooltip>
@@ -1539,7 +1742,11 @@ onMounted(() => {
</template>
<a-row>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="5GC Flag" name="cnFlag" :help="t('views.neUser.sub.cnFlag')">
<a-form-item
label="5GC Flag"
name="cnFlag"
:help="t('views.neUser.sub.cnFlag')"
>
<a-select v-model:value="modalState.from.cnType">
<a-select-option value="3">
{{ t('views.neUser.sub.enable') }}
@@ -1551,44 +1758,71 @@ onMounted(() => {
</a-form-item>
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="5G Subscribed UE AMBR Template" name="ambr"
v-bind="modalStateFrom.validateInfos.ambr">
<a-input v-model:value="modalState.from.ambr" allow-clear :maxlength="50">
<a-form-item
label="5G Subscribed UE AMBR Template"
name="ambr"
v-bind="modalStateFrom.validateInfos.ambr"
>
<a-input
v-model:value="modalState.from.ambr"
allow-clear
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.sub.inputTip', { num: '50' }) }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
<InfoCircleOutlined
style="opacity: 0.45; color: inherit"
/>
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="5G Subscribed SNSSAIs Template" name="nssai"
v-bind="modalStateFrom.validateInfos.nssai">
<a-input v-model:value="modalState.from.nssai" allow-clear :maxlength="50">
<a-form-item
label="5G Subscribed SNSSAIs Template"
name="nssai"
v-bind="modalStateFrom.validateInfos.nssai"
>
<a-input
v-model:value="modalState.from.nssai"
allow-clear
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.sub.inputTip', { num: '50' }) }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
<InfoCircleOutlined
style="opacity: 0.45; color: inherit"
/>
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="5G Subscribed SMF Selection Data Template" name="smfSel">
<a-input v-model:value="modalState.from.smfSel" allow-clear :maxlength="50">
<a-form-item
label="5G Subscribed SMF Selection Data Template"
name="smfSel"
>
<a-input
v-model:value="modalState.from.smfSel"
allow-clear
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.sub.inputTip', { num: '50' }) }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
<InfoCircleOutlined
style="opacity: 0.45; color: inherit"
/>
</a-tooltip>
</template>
</a-input>
@@ -1596,48 +1830,77 @@ onMounted(() => {
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="5G Forbidden Areas Template" name="arfb">
<a-input v-model:value="modalState.from.arfb" allow-clear :maxlength="50">
<a-input
v-model:value="modalState.from.arfb"
allow-clear
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.sub.arfbTip') }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
<InfoCircleOutlined
style="opacity: 0.45; color: inherit"
/>
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="5G CAG Template" name="cag" v-bind="modalStateFrom.validateInfos.cag">
<a-input v-model:value="modalState.from.cag" allow-clear :maxlength="50">
<a-form-item
label="5G CAG Template"
name="cag"
v-bind="modalStateFrom.validateInfos.cag"
>
<a-input
v-model:value="modalState.from.cag"
allow-clear
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.sub.inputTip', { num: '50' }) }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
<InfoCircleOutlined
style="opacity: 0.45; color: inherit"
/>
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="5G Service Area Restriction Template" name="sar">
<a-input v-model:value="modalState.from.sar" allow-clear :maxlength="50">
<a-form-item
label="5G Service Area Restriction Template"
name="sar"
>
<a-input
v-model:value="modalState.from.sar"
allow-clear
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.sub.sarTip') }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
<InfoCircleOutlined
style="opacity: 0.45; color: inherit"
/>
</a-tooltip>
</template>
</a-input>
</a-form-item>
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="5G MICO Mode" name="mico" :help="t('views.neUser.sub.micoTip')">
<a-form-item
label="5G MICO Mode"
name="mico"
:help="t('views.neUser.sub.micoTip')"
>
<a-select v-model:value="modalState.from.mico">
<a-select-option value="1">
{{ t('views.neUser.sub.enable') }}
@@ -1660,14 +1923,21 @@ onMounted(() => {
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="5G UE Usage Type" name="ueUsageType">
<a-input-number v-model:value="modalState.from.ueUsageType" style="width: 100%" :min="0" :max="127"
placeholder="0 ~ 127">
<a-input-number
v-model:value="modalState.from.ueUsageType"
style="width: 100%"
:min="0"
:max="127"
placeholder="0 ~ 127"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.sub.ueTypeTip') }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
<InfoCircleOutlined
style="opacity: 0.45; color: inherit"
/>
</a-tooltip>
</template>
</a-input-number>
@@ -1675,14 +1945,21 @@ onMounted(() => {
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="5G RFSP Index" name="rfspIndex">
<a-input-number v-model:value="modalState.from.rfspIndex" style="width: 100%" :min="0" :max="127"
placeholder="0 ~ 127">
<a-input-number
v-model:value="modalState.from.rfspIndex"
style="width: 100%"
:min="0"
:max="127"
placeholder="0 ~ 127"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.sub.rfspTip') }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
<InfoCircleOutlined
style="opacity: 0.45; color: inherit"
/>
</a-tooltip>
</template>
</a-input-number>
@@ -1697,7 +1974,11 @@ onMounted(() => {
</template>
<a-row>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="4G EPS Flag" name="epsFlag" :help="t('views.neUser.sub.epsFlagTip')">
<a-form-item
label="4G EPS Flag"
name="epsFlag"
:help="t('views.neUser.sub.epsFlagTip')"
>
<a-select v-model:value="modalState.from.epsFlag">
<a-select-option value="1">
{{ t('views.neUser.sub.enable') }}
@@ -1709,15 +1990,24 @@ onMounted(() => {
</a-form-item>
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="4G EPS User Template Name" name="epstpl"
v-bind="modalStateFrom.validateInfos.epstpl">
<a-input v-model:value="modalState.from.epstpl" allow-clear :maxlength="50">
<a-form-item
label="4G EPS User Template Name"
name="epstpl"
v-bind="modalStateFrom.validateInfos.epstpl"
>
<a-input
v-model:value="modalState.from.epstpl"
allow-clear
:maxlength="50"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.sub.inputTip', { num: '50' }) }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
<InfoCircleOutlined
style="opacity: 0.45; color: inherit"
/>
</a-tooltip>
</template>
</a-input>
@@ -1726,14 +2016,20 @@ onMounted(() => {
</a-row>
<a-row>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item label="4G Static IP" v-bind="modalStateFrom.validateInfos.staticIp" name="staticIp">
<a-form-item
label="4G Static IP"
v-bind="modalStateFrom.validateInfos.staticIp"
name="staticIp"
>
<a-input v-model:value="modalState.from.staticIp" allow-clear>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
{{ t('views.neUser.sub.staticIpTip') }}
</template>
<InfoCircleOutlined style="opacity: 0.45; color: inherit" />
<InfoCircleOutlined
style="opacity: 0.45; color: inherit"
/>
</a-tooltip>
</template>
</a-input>
@@ -1753,33 +2049,73 @@ onMounted(() => {
</a-input>
</a-form-item>
<a-form-item label="4G APN Context List" name="apnContext" :help="t('views.neUser.sub.apnContextTip')">
<a-form-item
label="4G APN Context List"
name="apnContext"
:help="t('views.neUser.sub.apnContextTip')"
>
<a-input-group compact>
<a-input-number v-for="(_, i) in modalState.from.apnContext" :key="i" :title="i" style="width: 16.5%"
:min="0" :max="99" v-model:value="modalState.from.apnContext[i]"></a-input-number>
<a-input-number
v-for="(_, i) in modalState.from.apnContext"
:key="i"
:title="i"
style="width: 16.5%"
:min="0"
:max="99"
v-model:value="modalState.from.apnContext[i]"
></a-input-number>
</a-input-group>
</a-form-item>
<a-form-item label="4G EPS ODB" name="epsOdb" v-bind="modalStateFrom.validateInfos.epsOdb">
<a-tooltip :title="t('views.neUser.sub.epsOdbTip')" placement="topLeft">
<a-select v-model:value="modalState.from.epsOdb" mode="multiple" style="width: 100%"
placeholder="Please select" :options="modalStateFromOption.odbJson" @change="">
<a-form-item
label="4G EPS ODB"
name="epsOdb"
v-bind="modalStateFrom.validateInfos.epsOdb"
>
<a-tooltip
:title="t('views.neUser.sub.epsOdbTip')"
placement="topLeft"
>
<a-select
v-model:value="modalState.from.epsOdb"
mode="multiple"
style="width: 100%"
placeholder="Please select"
:options="modalStateFromOption.odbJson"
@change=""
>
</a-select>
</a-tooltip>
</a-form-item>
<a-form-item label="4G HPLMN ODB" name="hplmnOdb">
<a-tooltip :title="t('views.neUser.sub.hplmnOdbTip')" placement="topLeft">
<a-select v-model:value="modalState.from.hplmnOdb" mode="multiple" style="width: 100%"
:options="modalStateFromOption.hplmnOdb" @change="">
<a-tooltip
:title="t('views.neUser.sub.hplmnOdbTip')"
placement="topLeft"
>
<a-select
v-model:value="modalState.from.hplmnOdb"
mode="multiple"
style="width: 100%"
:options="modalStateFromOption.hplmnOdb"
@change=""
>
</a-select>
</a-tooltip>
</a-form-item>
<a-form-item label="4G Access Restriction Data" name="ard">
<a-tooltip :title="t('views.neUser.sub.ardTip')" placement="topLeft">
<a-select v-model:value="modalState.from.ard" mode="multiple" style="width: 100%"
:options="modalStateFromOption.ardJson" @change="">
<a-tooltip
:title="t('views.neUser.sub.ardTip')"
placement="topLeft"
>
<a-select
v-model:value="modalState.from.ard"
mode="multiple"
style="width: 100%"
:options="modalStateFromOption.ardJson"
@change=""
>
</a-select>
</a-tooltip>
</a-form-item>
@@ -1789,15 +2125,36 @@ onMounted(() => {
</ProModal>
<!-- 批量删除框 -->
<ProModal :drag="true" :destroyOnClose="true" style="top: 0px" :keyboard="false" :mask-closable="false"
:open="modalState.openByBatchDel" :title="modalState.title" :confirm-loading="modalState.confirmLoading"
@ok="fnBatchDelModalOk" @cancel="fnBatchDelModalCancel">
<a-form name="modalStateBatchDelFrom" layout="horizontal" :label-col="{ span: 6 }" :labelWrap="true">
<ProModal
:drag="true"
:destroyOnClose="true"
style="top: 0px"
:keyboard="false"
:mask-closable="false"
:open="modalState.openByBatchDel"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnBatchDelModalOk"
@cancel="fnBatchDelModalCancel"
>
<a-form
name="modalStateBatchDelFrom"
layout="horizontal"
:label-col="{ span: 8 }"
:labelWrap="true"
>
<a-row>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item :label="t('views.neUser.sub.startIMSI')" name="imsi"
v-bind="modalStateBatchDelFrom.validateInfos.imsi">
<a-input v-model:value="modalState.BatchDelForm.imsi" allow-clear :maxlength="15">
<a-form-item
:label="t('views.neUser.sub.startIMSI')"
name="imsi"
v-bind="modalStateBatchDelFrom.validateInfos.imsi"
>
<a-input
v-model:value="modalState.BatchDelForm.imsi"
allow-clear
:maxlength="15"
>
<template #prefix>
<a-tooltip placement="topLeft">
<template #title>
@@ -1813,10 +2170,18 @@ onMounted(() => {
</a-form-item>
</a-col>
<a-col :lg="24" :md="24" :xs="24">
<a-form-item :label="t('views.neUser.sub.numDel')" name="num"
v-bind="modalStateBatchDelFrom.validateInfos.num">
<a-input-number v-model:value="modalState.BatchDelForm.num" style="width: 100%" :min="1" :max="10000"
placeholder="<=10000"></a-input-number>
<a-form-item
:label="t('views.neUser.sub.numDel')"
name="num"
v-bind="modalStateBatchDelFrom.validateInfos.num"
>
<a-input-number
v-model:value="modalState.BatchDelForm.num"
style="width: 100%"
:min="1"
:max="10000"
placeholder="<=10000"
></a-input-number>
</a-form-item>
</a-col>
</a-row>
@@ -1824,12 +2189,33 @@ onMounted(() => {
</ProModal>
<!-- 上传导入表格数据文件框 -->
<UploadModal :title="uploadImportState.title" :loading="uploadImportState.loading"
@upload="fnModalUploadImportUpload" @close="fnModalUploadImportClose" v-model:open="uploadImportState.open"
:ext="['.txt']" :size="10">
<UploadModal
:title="uploadImportState.title"
:loading="uploadImportState.loading"
@upload="fnModalUploadImportUpload"
@close="fnModalUploadImportClose"
v-model:open="uploadImportState.open"
:ext="['.txt']"
:size="10"
>
<template #default>
<a-textarea :disabled="true" :hidden="!uploadImportState.msg" :value="uploadImportState.msg"
:auto-size="{ minRows: 2, maxRows: 8 }" style="background-color: transparent; color: rgba(0, 0, 0, 0.85)" />
<a-alert
:message="uploadImportState.msg"
:type="uploadImportState.hasFail ? 'warning' : 'info'"
v-show="uploadImportState.msg.length > 0"
>
<template #action>
<a-button
size="small"
type="link"
danger
@click="fnModalUploadImportFailReason"
v-if="uploadImportState.hasFail"
>
{{ t('views.neUser.auth.importFail') }}
</a-button>
</template>
</a-alert>
</template>
</UploadModal>
</PageContainer>

View File

@@ -242,6 +242,13 @@ const statsColumns: TableColumnType<any>[] = [
key: 'title',
width: '65%',
},
{
title: t('views.perfManage.kpiOverView.totalValue'),
dataIndex: 'total',
key: 'total',
width: '12%',
sortDirections: ['ascend', 'descend'],
},
{
title: t('views.perfManage.kpiOverView.avgValue'),
dataIndex: 'avg',
@@ -356,6 +363,7 @@ function fnGetListTitle() {
dataIndex: kpiValue,
align: 'left',
key: kpiValue,
unit: item[`unit`],
resizable: true,
width: 100,
minWidth: 150,
@@ -448,16 +456,17 @@ function fnGetList() {
const total = Number(
values.reduce((sum, val) => sum + val, 0).toFixed(2)
);
// 计算平均值
const avg =
values.length > 0 ? Number((total / values.length).toFixed(2)) : 0;
kpiStats.value.push({
kpiId: columns.key,
title: columns.title,
unit: columns.unit,
max: values.length > 0 ? Math.max(...values) : 0,
min: values.length > 0 ? Math.min(...values) : 0,
avg,
total: total,
});
}
}
@@ -606,7 +615,7 @@ function fnRanderChartData() {
for (const item of orgData) {
const keys = Object.keys(item);
//console.log(keys,item);//
//console.log(keys,item);
for (const y of chartDataYSeriesData) {
for (const key of keys) {
if (y.key === key) {
@@ -744,25 +753,42 @@ function wsMessage(res: Record<string, any>) {
// 添加一个变量来跟踪当前选中的行
const selectedRow = ref<string[]>([]);
const selectedUnit = ref<string | null>(null);
// 添加处理行点击的方法
function handleRowClick(record: any) {
const index = selectedRow.value.indexOf(record.kpiId);
console.log(record)
// 如果已经选中,取消选中
if (index > -1) {
selectedRow.value.splice(index, 1);
chartLegendSelected[record.title] = false;
// 如果取消选中的是最后一个,重置 selectedUnit
if (selectedRow.value.length === 0) {
selectedUnit.value = null;
}
} else {
// 检查单位是否一致
if (selectedUnit.value && selectedUnit.value !== record.unit) {
message.error(`${t('views.perfManage.customTarget.unitSelect')} ${selectedUnit.value}`);
return;
}
// 添加新的选中项
selectedRow.value.push(record.kpiId);
// 设置选中的单位
if (!selectedUnit.value) {
selectedUnit.value = record.unit;
}
// 如果只有一个选中项,重置为 false
if (selectedRow.value.length === 1) {
Object.keys(chartLegendSelected).forEach(key => {
chartLegendSelected[key] = false;
});
}
chartLegendSelected[record.title] = true;
}
@@ -914,50 +940,28 @@ onBeforeUnmount(() => {
<template>
<PageContainer>
<a-card
v-show="tableState.seached"
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<a-card v-show="tableState.seached" :bordered="false" :body-style="{ marginBottom: '24px', paddingBottom: 0 }">
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParamsFrom" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item name="neType" :label="t('views.ne.common.neType')">
<a-cascader
v-model:value="state.neType"
:options="neCascaderOptions"
:allow-clear="false"
:placeholder="t('common.selectPlease')"
/>
<a-cascader v-model:value="state.neType" :options="neCascaderOptions" :allow-clear="false"
:placeholder="t('common.selectPlease')" />
</a-form-item>
</a-col>
<a-col :lg="10" :md="12" :xs="24">
<a-form-item
:label="t('views.perfManage.goldTarget.timeFrame')"
name="timeFrame"
>
<a-range-picker
v-model:value="queryRangePicker"
bordered
:allow-clear="false"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
value-format="x"
:presets="ranges"
style="width: 100%"
></a-range-picker>
<a-form-item :label="t('views.perfManage.goldTarget.timeFrame')" name="timeFrame">
<a-range-picker v-model:value="queryRangePicker" bordered :allow-clear="false"
:show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" value-format="x" :presets="ranges"
style="width: 100%"></a-range-picker>
</a-form-item>
</a-col>
<a-col :lg="2" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button
type="primary"
:loading="tableState.loading"
@click.prevent="fnGetListTitle()"
>
<a-button type="primary" :loading="tableState.loading" @click.prevent="fnGetListTitle()">
<template #icon>
<SearchOutlined />
</template>
@@ -974,11 +978,7 @@ onBeforeUnmount(() => {
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
<a-button
type="primary"
:loading="tableState.loading"
@click.prevent="fnChangShowType()"
>
<a-button type="primary" :loading="tableState.loading" @click.prevent="fnChangShowType()">
<template #icon>
<AreaChartOutlined />
</template>
@@ -988,12 +988,8 @@ onBeforeUnmount(() => {
: t('views.perfManage.goldTarget.kpiTableTitle')
}}
</a-button>
<a-button
type="dashed"
:loading="tableState.loading"
@click.prevent="fnRecordExport()"
v-show="tableState.showTable"
>
<a-button type="dashed" :loading="tableState.loading" @click.prevent="fnRecordExport()"
v-show="tableState.showTable">
<template #icon>
<ExportOutlined />
</template>
@@ -1013,12 +1009,8 @@ onBeforeUnmount(() => {
</template>
</a-button>
</a-tooltip>
<TableColumnsDnd
v-if="tableColumns.length > 0"
:cache-id="`kpiTarget_${state.neType[0]}`"
:columns="tableColumns"
v-model:columns-dnd="tableColumnsDnd"
></TableColumnsDnd>
<TableColumnsDnd v-if="tableColumns.length > 0" :cache-id="`kpiTarget_${state.neType[0]}`"
:columns="tableColumns" v-model:columns-dnd="tableColumnsDnd"></TableColumnsDnd>
<a-tooltip>
<template #title>{{ t('common.sizeText') }}</template>
<a-dropdown trigger="click" placement="bottomRight">
@@ -1028,10 +1020,7 @@ onBeforeUnmount(() => {
</template>
</a-button>
<template #overlay>
<a-menu
:selected-keys="[tableState.size as string]"
@click="fnTableSize"
>
<a-menu :selected-keys="[tableState.size as string]" @click="fnTableSize">
<a-menu-item key="default">
{{ t('common.size.default') }}
</a-menu-item>
@@ -1060,61 +1049,32 @@ onBeforeUnmount(() => {
size="small"
/>
</a-form-item> -->
<a-form-item
:label="t('views.perfManage.goldTarget.realTimeData')"
name="chartRealTime"
>
<a-switch
:disabled="tableState.loading"
v-model:checked="state.chartRealTime"
:checked-children="t('common.switch.open')"
:un-checked-children="t('common.switch.shut')"
@change="fnRealTimeSwitch"
size="small"
/>
<a-form-item :label="t('views.perfManage.goldTarget.realTimeData')" name="chartRealTime">
<a-switch :disabled="tableState.loading" v-model:checked="state.chartRealTime"
:checked-children="t('common.switch.open')" :un-checked-children="t('common.switch.shut')"
@change="fnRealTimeSwitch" size="small" />
</a-form-item>
</a-form>
</template>
<!-- 表格列表 -->
<a-table
v-show="tableState.showTable"
class="table"
row-key="id"
:columns="tableColumnsDnd"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumnsDnd.length * 200, y: 'calc(100vh - 480px)' }"
@resizeColumn="(w: number, col: any) => (col.width = w)"
:show-expand-column="false"
@change="fnTableChange"
>
<a-table v-show="tableState.showTable" class="table" row-key="id" :columns="tableColumnsDnd"
:loading="tableState.loading" :data-source="tableState.data" :size="tableState.size"
:pagination="tablePagination" :scroll="{ x: tableColumnsDnd.length * 200, y: 'calc(100vh - 480px)' }"
@resizeColumn="(w: number, col: any) => (col.width = w)" :show-expand-column="false" @change="fnTableChange">
</a-table>
<!-- 图表 -->
<div style="padding: 24px" v-show="!tableState.showTable">
<div
ref="kpiChartDom"
class="chart-container"
style="height: 450px; width: 100%"
></div>
<div ref="kpiChartDom" class="chart-container" style="height: 450px; width: 100%"></div>
<div class="table-container">
<a-table
:columns="statsColumns"
:data-source="kpiStats"
:pagination="false"
:scroll="{ y: 250 }"
size="small"
:custom-row="
record => ({
onClick: () => handleRowClick(record),
class: selectedRow.includes(record.kpiId) ? 'selected-row' : '',
})
"
>
<a-table :columns="statsColumns" :data-source="kpiStats" :pagination="false" :scroll="{ y: 250 }" size="small"
:custom-row="record => ({
onClick: () => handleRowClick(record),
class: selectedRow.includes(record.kpiId) ? 'selected-row' : '',
})
">
<template #headerCell="{ column }">
<template v-if="column.key === 'total'">
<span>
@@ -1169,6 +1129,14 @@ onBeforeUnmount(() => {
</span>
</template>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'avg'">
<span v-if="record.unit !== '%'">-</span>
</template>
<template v-if="column.key === 'total'">
<span v-if="record.unit == '%'">-</span>
</template>
</template>
</a-table>
</div>
</div>

View File

@@ -131,6 +131,9 @@ type TabeStateType = {
selectedRowKeys: (string | number)[];
};
//是否显示type框
const drawerVisible = ref(true);
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
@@ -502,9 +505,11 @@ onMounted(() => {
message.error(t('views.system.dictData.typeDataErr'), 3);
}
});
drawerVisible.value = true;
} else {
// 获取列表数据
fnGetList();
drawerVisible.value = false;
}
});
</script>
@@ -881,7 +886,7 @@ onMounted(() => {
default-value="sys_oper_type"
:placeholder="t('common.selectPlease')"
:options="dict.sysDictType"
:disabled="true"
:disabled="drawerVisible"
>
</a-select>
</a-form-item>

View File

@@ -161,13 +161,13 @@ watchEffect(() => {
item.number === props.selectedFrame
? 'blue'
: item.bg
? `#${Number(item.bg).toString(16).padStart(6, '0')}`
? `#${Number(item.bg).toString(16)}`
: '',
color:
item.number === props.selectedFrame
? 'white'
: item.fg
? `#${Number(item.fg).toString(16).padStart(6, '0')}`
? `#${Number(item.fg).toString(16)}`
: '',
}"
@click="onSelectedFrame(item.number)"
@@ -250,8 +250,8 @@ watchEffect(() => {
}
.thead-item:nth-child(5),
.tbody-item:nth-child(5) {
flex-basis: 6rem;
width: 6rem;
flex-basis: 7rem;
width: 7rem;
}
.thead-item:nth-child(6),
.tbody-item:nth-child(6) {