feat: 实训教学模块

This commit is contained in:
TsMask
2024-11-23 11:18:40 +08:00
parent 2fb3467fb5
commit 285c9b660b
26 changed files with 16854 additions and 0 deletions

View File

@@ -0,0 +1,713 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, computed } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { ProModal } from 'antdv-pro-modal';
import { Form, 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 { parseDateToStr } from '@/utils/date-utils';
import useDictStore from '@/store/modules/dict';
import useNeInfoStore from '@/store/modules/neinfo';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import useI18n from '@/hooks/useI18n';
import {
getPtNeConfigApplyList,
stuPtNeConfigApply,
updatePtNeConfigApply,
} from '@/api/pt/neConfigApply';
import { hasRoles } from '@/plugins/auth-user';
const { t } = useI18n();
const { getDict } = useDictStore();
const neInfoStore = useNeInfoStore();
/**字典数据 */
let dict: {
/**配置申请应用状态 */
ptConfigApplyStatus: DictType[];
} = reactive({
ptConfigApplyStatus: [],
});
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: '',
/**申请人 */
createBy: '',
/**状态 */
status: undefined,
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 20,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
neType: '',
createBy: '',
status: undefined,
pageNum: 1,
pageSize: 20,
});
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: ColumnsType = [
{
title: t('common.rowId'),
dataIndex: 'id',
align: 'center',
width: 100,
},
{
title: t('views.ne.common.neType'),
dataIndex: 'neType',
align: 'left',
width: 100,
},
{
title: '申请人',
dataIndex: 'createBy',
align: 'left',
width: 120,
},
{
title: '申请时间',
dataIndex: 'createTime',
align: 'left',
width: 150,
customRender(opt) {
if (+opt.value <= 0) return '';
return parseDateToStr(+opt.value);
},
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
align: 'left',
width: 100,
},
{
title: '处理人',
dataIndex: 'updateBy',
align: 'left',
width: 120,
},
{
title: '处理时间',
dataIndex: 'updateTime',
align: 'left',
width: 150,
customRender(opt) {
if (+opt.value <= 0) return '';
return parseDateToStr(+opt.value);
},
},
{
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: 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)[], infos: any) {
const arr = [];
for (const item of infos) {
if (item.status === '0') {
arr.push(item.id);
}
}
tableState.selectedRowKeys = arr;
}
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (tableState.loading) return;
tableState.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
}
getPtNeConfigApplyList(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
// 取消勾选
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;
});
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**详情框是否显示 */
openByView: boolean;
/**新增框或修改框是否显示 */
openByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
/**cron生成框是否显示 */
openByCron: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
openByView: false,
openByEdit: false,
title: '任务',
from: {
id: undefined,
createBy: '',
createTime: 0,
updateBy: '',
updateTime: 0,
neType: 'MME',
status: '0',
backInfo: '',
},
confirmLoading: false,
openByCron: false,
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
status: [
{
required: true,
message: t('common.selectPlease'),
},
],
})
);
/**
* 对话框弹出显示为 查看
* @param jobId 任务id
*/
function fnModalVisibleByVive(row: Record<string, any>) {
modalState.from = Object.assign(modalState.from, row);
modalState.title = '查看';
modalState.openByView = true;
}
/**
* 对话框弹出显示为 编辑
* @param jobId 任务id
*/
function fnModalVisibleByEdit(row: Record<string, any>) {
Object.assign(modalState.from, row);
modalState.from.status = '3';
modalState.title = '编辑状态';
modalState.openByEdit = true;
}
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
modalStateFrom
.validate()
.then(() => {
modalState.confirmLoading = true;
const from = toRaw(modalState.from);
updatePtNeConfigApply({
applyId: from.id,
neType: from.neType,
status: from.status,
backInfo: from.backInfo,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
modalState.openByEdit = false;
modalStateFrom.resetFields();
fnGetList();
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
modalState.confirmLoading = false;
});
})
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
});
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.openByEdit = false;
modalState.openByView = false;
modalState.from = {};
}
/**批量退回 */
function fnRecordBack(row?: Record<string, any>) {
Modal.confirm({
title: t('common.tipTitle'),
content: row
? '确认要撤回配置应用申请吗?'
: '确认要批量退回学生的配置应用申请吗?',
onOk() {
let result: any;
if (row) {
result = stuPtNeConfigApply({ neType: row.neType, status: '1' });
} else {
result = updatePtNeConfigApply({
status: "3",
backId: tableState.selectedRowKeys.join(","),
backInfo: "请重新检查配置",
});
}
result.then((res: any) => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
});
},
});
}
/**应用状态 */
const applyStatus = computed(() => {
if (hasRoles(['student'])) {
return dict.ptConfigApplyStatus.filter(s => ['0', '1'].includes(s.value));
}
let data = dict.ptConfigApplyStatus;
if (modalState.openByEdit && modalState.from.id) {
data = dict.ptConfigApplyStatus.filter(s => ['2', '3'].includes(s.value));
}
return data;
});
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('pt_config_apply_status')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.ptConfigApplyStatus = resArr[0].value;
}
});
// 获取网元列表
neInfoStore.fnNelist().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.configManage.license.neType')"
name="neType "
>
<a-auto-complete
v-model:value="queryParams.neType"
:options="neInfoStore.getNeSelectOtions"
allow-clear
:placeholder="t('views.configManage.license.neTypePlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="状态" name="status">
<a-select
v-model:value="queryParams.status"
allow-clear
:placeholder="t('common.selectPlease')"
:options="applyStatus"
>
</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-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<div class="button-container">
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
@click.prevent="fnRecordBack()"
v-roles:has="['admin', 'teacher']"
>
<template #icon><DeleteOutlined /></template>
批量退回
</a-button>
</div>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<div class="button-container">
<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>
</div>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:scroll="{ x: tableColumns.length * 120 }"
:pagination="tablePagination"
:row-selection="{
type: 'checkbox',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<DictTag
:options="dict.ptConfigApplyStatus"
:value="record.status"
/>
</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)"
>
<template #icon><ProfileOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip v-if="record.status === '0' && hasRoles(['student'])">
<template #title>撤回</template>
<a-button type="link" @click.prevent="fnRecordBack(record)">
<template #icon><RollbackOutlined /></template>
</a-button>
</a-tooltip>
<a-tooltip
v-if="record.status === '0' && hasRoles(['admin', 'teacher'])"
>
<template #title>{{ t('common.editText') }}</template>
<a-button
type="link"
@click.prevent="fnModalVisibleByEdit(record)"
>
<template #icon><FormOutlined /></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 :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neType')" name="neType">
{{ modalState.from.neType }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="状态" name="status">
<DictTag
:options="dict.ptConfigApplyStatus"
:value="modalState.from.status"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="申请人" name="createBy">
{{ modalState.from.createBy }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="申请时间" name="createTime">
{{ parseDateToStr(+modalState.from.createTime) }}
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16" v-if="modalState.from.status !== '0'">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="处理人" name="updateBy">
{{ modalState.from.updateBy }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="处理时间" name="updateTime">
{{ parseDateToStr(+modalState.from.updateTime) }}
</a-form-item>
</a-col>
</a-row>
<a-form-item
v-if="modalState.from.status === '3'"
label="退回说明"
name="backInfo"
:label-col="{ span: 3 }"
:label-wrap="true"
>
{{ modalState.from.backInfo }}
</a-form-item>
</a-form>
<template #footer>
<a-button key="cancel" @click="fnModalCancel">
{{ t('common.close') }}
</a-button>
</template>
</ProModal>
<!-- 新增框或修改框 -->
<ProModal
:drag="true"
:width="800"
:open="modalState.openByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
:destroyOnClose="true"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="modalStateFrom"
layout="horizontal"
:label-col="{ span: 6 }"
:label-wrap="true"
>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neType')" name="neType">
{{ modalState.from.neType }}
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="申请人" name="createBy">
{{ modalState.from.createBy }}
</a-form-item>
</a-col>
<a-col :lg="12" :md="12" :xs="24">
<a-form-item label="申请时间" name="createTime">
{{ parseDateToStr(+modalState.from.createTime) }}
</a-form-item>
</a-col>
</a-row>
<a-form-item
label="状态"
name="status"
:label-col="{ span: 3 }"
:label-wrap="true"
v-bind="modalStateFrom.validateInfos.status"
>
<a-select
v-model:value="modalState.from.status"
:placeholder="t('common.selectPlease')"
:options="applyStatus"
>
</a-select>
</a-form-item>
<a-form-item
v-if="modalState.from.status === '3'"
label="退回说明"
name="backInfo"
:label-col="{ span: 3 }"
:label-wrap="true"
>
<a-textarea
v-model:value="modalState.from.backInfo"
:auto-size="{ minRows: 2, maxRows: 6 }"
:maxlength="400"
:placeholder="t('common.inputPlease')"
/>
</a-form-item>
</a-form>
</ProModal>
</PageContainer>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -0,0 +1,327 @@
<script setup lang="ts">
import { reactive, onMounted, toRaw, watch } from 'vue';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import CodemirrorEditeDiff from '@/components/CodemirrorEditeDiff/index.vue';
import { parseDateToStr } from '@/utils/date-utils';
import {
getPtNeConfigDataLogList,
restorePtNeConfigDataLog,
} from '@/api/pt/neConfigDataLog';
import useDictStore from '@/store/modules/dict';
import { message } from 'ant-design-vue';
const { t } = useI18n();
const emit = defineEmits(['ok', 'cancel', 'update:open']);
const props = defineProps({
open: {
type: Boolean,
default: false,
},
/**网元类型 */
neType: {
type: String,
default: '',
},
/**参数名 */
paramName: {
type: String,
default: '',
},
/**学生用户账号 */
student: {
type: String,
default: '',
},
});
const { getDict } = useDictStore();
/**字典数据 */
let dict: {
/**业务类型 */
sysBusinessType: DictType[];
} = reactive({
sysBusinessType: [],
});
/**对话框对象信息状态类型 */
type StateType = {
/**新增框或修改框是否显示 */
openByList: boolean;
/**差异比较框是否显示 */
openByDiff: boolean;
/**标题 */
title: string;
/**加载状态 */
loading: boolean;
/**数据 */
data: Record<string, any>[];
/**差异数据 */
dataDiff: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let state: StateType = reactive({
openByList: false,
openByDiff: false,
title: '操作参数名称-学生账号',
loading: false,
data: [],
dataDiff: {},
confirmLoading: false,
});
function onClose() {
state.loading = false;
state.openByList = false;
state.openByDiff = false;
state.data = [];
state.dataDiff = {};
emit('cancel');
emit('update:open', false);
queryParams = {
neType: '',
paramName: '',
student: '',
pageNum: 1,
pageSize: 10,
};
}
/**查询参数 */
let queryParams = reactive({
/**网元类型 */
neType: '',
/**可用属性值 */
paramName: '',
/**学生账号 */
student: '',
/**当前页数 */
pageNum: 1,
/**每页条数 */
pageSize: 10,
});
/**查询列表, pageNum初始页数 */
function fnGetList(pageNum?: number) {
if (state.loading) return;
state.loading = true;
if (pageNum) {
queryParams.pageNum = pageNum;
if (pageNum === 1) state.data = [];
}
getPtNeConfigDataLogList(toRaw(queryParams)).then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.rows)) {
// tablePagination.total = res.total;
state.data = state.data.concat(res.rows);
// 去首个做标题
if (queryParams.pageNum === 1 && state.data.length > 0) {
const item = state.data[0];
state.title = `${item.paramDisplay} - ${item.createBy}`;
}
if (state.data.length <= res.total && res.rows.length > 0) {
queryParams.pageNum++;
}
}
state.loading = false;
});
}
/**差异比较框打开 */
function fnMergeCellOpen(row: Record<string, any>) {
state.dataDiff = row;
state.dataDiff.paramJsonOld = JSON.stringify(
JSON.parse(state.dataDiff.paramJsonOld),
null,
2
);
state.dataDiff.paramJsonNew = JSON.stringify(
JSON.parse(state.dataDiff.paramJsonNew),
null,
2
);
state.openByDiff = true;
}
/**差异比较框关闭 */
function fnMergeCellClose() {
state.openByDiff = false;
state.dataDiff = {};
}
/**差异比较还原 */
function fnMergeCellRestore(value: 'old' | 'new') {
if (state.confirmLoading) return;
const id = state.dataDiff.id;
restorePtNeConfigDataLog({ id, value })
.then((res: any) => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
fnMergeCellClose();
fnGetList(1);
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
state.confirmLoading = false;
});
}
/**监听是否显示,初始数据 */
watch(
() => props.open,
val => {
if (val) {
if (props.neType && props.paramName) {
state.title = '';
state.openByList = true;
// 根据条件查询数据
queryParams.neType = props.neType;
queryParams.paramName = props.paramName;
if (props.student) {
queryParams.student = props.student;
}
fnGetList();
}
}
}
);
onMounted(() => {
// 初始字典数据
Promise.allSettled([getDict('sys_oper_type')]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.sysBusinessType = resArr[0].value;
}
});
});
</script>
<template>
<div>
<a-drawer
:width="500"
:title="state.title"
placement="right"
:open="state.openByList"
@close="onClose"
>
<a-list
class="demo-loadmore-list"
item-layout="horizontal"
:data-source="state.data"
>
<template #loadMore>
<div
:style="{
textAlign: 'center',
marginTop: '12px',
height: '32px',
lineHeight: '32px',
}"
>
<a-button @click="fnGetList()" :loading="state.loading">
{{ t('views.configManage.configParamForm.ptDiffLoad') }}
</a-button>
</div>
</template>
<template #renderItem="{ item }">
<a-list-item>
<template #actions>
<a-tooltip>
<template #title>
{{ t('views.configManage.configParamForm.ptDiffMerge') }}
</template>
<a-button type="primary" @click.prevent="fnMergeCellOpen(item)">
<template #icon><MergeCellsOutlined /></template>
</a-button>
</a-tooltip>
</template>
<a-list-item-meta>
<template #title>
<DictTag
:options="dict.sysBusinessType"
:value="item.operaType"
/>
</template>
<template #description>
{{ parseDateToStr(item.createTime) }}
</template>
</a-list-item-meta>
</a-list-item>
</template>
</a-list>
</a-drawer>
<a-modal
:width="800"
:destroyOnClose="true"
:mask-closable="false"
v-model:open="state.openByDiff"
:footer="null"
:body-style="{ padding: 0, maxHeight: '650px', 'overflow-y': 'auto' }"
@ok="fnMergeCellClose()"
@cancel="fnMergeCellClose()"
>
<template #title>
<DictTag
:options="dict.sysBusinessType"
:value="state.dataDiff.operaType"
/>
{{ parseDateToStr(state.dataDiff.createTime) }}
</template>
<div class="diffBack">
<div>
<a-button
type="text"
:loading="state.confirmLoading"
@click.prevent="fnMergeCellRestore('old')"
>
<template #icon><MergeCellsOutlined /></template>
{{ t('views.configManage.configParamForm.ptDiffRest') }}
</a-button>
</div>
<div>
<a-button
type="text"
:loading="state.confirmLoading"
@click.prevent="fnMergeCellRestore('new')"
>
<template #icon><MergeCellsOutlined /></template>
{{ t('views.configManage.configParamForm.ptDiffRest') }}
</a-button>
</div>
</div>
<CodemirrorEditeDiff
:old-area="state.dataDiff.paramJsonOld"
:new-area="state.dataDiff.paramJsonNew"
></CodemirrorEditeDiff>
</a-modal>
</div>
</template>
<style lang="less" scoped>
.diffBack {
display: flex;
flex-direction: row;
justify-content: space-between;
& > div:first-child {
flex: 1;
background: #fa9;
}
& > div:last-child {
flex: 1;
background: #8f8;
}
}
</style>

View File

@@ -0,0 +1,342 @@
import {
addPtNeConfigData,
delPtNeConfigData,
editPtNeConfigData,
} from '@/api/pt/neConfig';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { Modal } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import message from 'ant-design-vue/lib/message';
import { reactive, watch } from 'vue';
/**
* 参数配置array类型
* @param param 父级传入 { t, treeState, fnActiveConfigNode, ruleVerification, modalState, fnModalCancel}
* @returns
*/
export default function useConfigArray({
t,
treeState,
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);
}
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>) {
// 遍历提取属性和值
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);
editPtNeConfigData({
neType: treeState.neType,
paramName: treeState.selectNode.paramName,
paramData: data,
loc: `${from['index']['value']}`,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.configManage.configParamForm.updateItem', {
num: modalState.title,
}),
duration: 3,
});
fnActiveConfigNode('#');
} else {
message.warning({
content: t('views.configManage.configParamForm.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.configManage.configParamForm.delItemTip', {
num: title,
}),
onOk() {
delPtNeConfigData({
neType: treeState.neType,
paramName: treeState.selectNode.paramName,
loc: loc,
}).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.configManage.configParamForm.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);
}
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>) {
// 遍历提取属性和值
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);
addPtNeConfigData({
neType: treeState.neType,
paramName: treeState.selectNode.paramName,
paramData: data,
loc: `${from['index']['value']}`,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.configManage.configParamForm.addItemOk', {
num: modalState.title,
}),
duration: 3,
});
fnActiveConfigNode('#');
} else {
message.warning({
content: t('views.configManage.configParamForm.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);
}
}
return ruleFrom;
}
// 监听表格字段列排序变化关闭展开
watch(
() => arrayState.columnsDnd,
() => {
arrayEditClose();
}
);
return {
arrayState,
arrayEdit,
arrayEditClose,
arrayEditOk,
arrayDelete,
arrayAdd,
arrayAddOk,
arrayInitEdit,
arrayInitAdd,
};
}

View File

@@ -0,0 +1,349 @@
import {
addPtNeConfigData,
delPtNeConfigData,
editPtNeConfigData,
} from '@/api/pt/neConfig';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { Modal } from 'ant-design-vue/lib';
import { SizeType } from 'ant-design-vue/lib/config-provider';
import message from 'ant-design-vue/lib/message';
import { nextTick, reactive } from 'vue';
/**
* 参数配置array类型的嵌套array
* @param param 父级传入 { t, treeState, fnActiveConfigNode, ruleVerification, modalState, arrayState, arrayInitEdit, arrayInitAdd, arrayEditClose}
* @returns
*/
export default function useConfigArrayChild({
t,
treeState,
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);
editPtNeConfigData({
neType: treeState.neType,
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.configManage.configParamForm.updateItem', {
num: modalState.title,
}),
duration: 3,
});
fnActiveConfigNode('#');
} else {
message.warning({
content: t('views.configManage.configParamForm.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.configManage.configParamForm.delItemTip', {
num: title,
}),
onOk() {
delPtNeConfigData({
neType: treeState.neType,
paramName: treeState.selectNode.paramName,
loc,
}).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.configManage.configParamForm.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);
addPtNeConfigData({
neType: treeState.neType,
paramName: treeState.selectNode.paramName,
paramData: data,
loc,
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.configManage.configParamForm.addItemOk', {
num: modalState.title,
}),
duration: 3,
});
fnActiveConfigNode('#');
} else {
message.warning({
content: t('views.configManage.configParamForm.addItemErr'),
duration: 3,
});
}
})
.finally(() => {
hide();
arrayEditClose();
});
}
return {
arrayChildState,
arrayChildExpand,
arrayChildEdit,
arrayChildEditOk,
arrayChildDelete,
arrayChildAdd,
arrayChildAddOk,
};
}

View File

@@ -0,0 +1,139 @@
import { editPtNeConfigData } from '@/api/pt/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/message';
import { reactive, toRaw } from 'vue';
/**
* list类型参数处理
* @param param 父级传入 {t, treeState, ruleVerification}
* @returns
*/
export default function useConfigList({ t, treeState, 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>) {
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);
editPtNeConfigData({
neType: treeState.neType,
paramName: treeState.selectNode.paramName,
paramData: {
[from['name']]: from['value'],
},
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('views.configManage.configParamForm.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.configManage.configParamForm.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 };
}

View File

@@ -0,0 +1,192 @@
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 && 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.configManage.configParamForm.requireInt', {
display,
filter,
}),
];
}
}
break;
case 'ipv4':
if (!regExpIPv4.test(value)) {
return [
false,
t('views.configManage.configParamForm.requireIpv4', { display }),
];
}
break;
case 'ipv6':
if (!regExpIPv6.test(value)) {
return [
false,
t('views.configManage.configParamForm.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.configManage.configParamForm.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.configManage.configParamForm.requireBool', { display }),
];
}
}
break;
case 'string':
// filter: "0~128"
// 字符串长度判断
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.configManage.configParamForm.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
// 字符串http判断
if (value.startsWith('http')) {
try {
if (!validURL(value)) {
return [
false,
t('views.configManage.configParamForm.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.configManage.configParamForm.requireString', {
display,
}),
];
}
} catch (error) {
console.error(error);
}
}
break;
default:
return [
false,
t('views.configManage.configParamForm.requireUn', { display }),
];
}
return result;
}
/**upfId可选择 */
const SMFByUPFIdOptions = ref<{ value: string; label: string }[]>([]);
/**加载smf配置的upfId */
function getConfigSMFByUPFIds(neId: string) {
getNeConfigData({
neType: 'SMF',
neId: neId,
paramName: 'upfConfig',
}).then(res => {
SMFByUPFIdOptions.value = [];
for (const s of res.data) {
SMFByUPFIdOptions.value.push({
value: s.id,
label: s.id,
});
}
});
}
return {
ruleVerification,
getConfigSMFByUPFIds,
SMFByUPFIdOptions,
};
}

View File

@@ -0,0 +1,228 @@
import { ptSaveAsDefault, ptResetAsDefault } from '@/api/pt/neConfig';
import {
getPtClassStudents,
stuPtNeConfigApply,
updatePtNeConfigApply,
} from '@/api/pt/neConfigApply';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { hasRoles } from '@/plugins/auth-user';
import { message } from 'ant-design-vue/lib';
import { computed, onMounted, reactive } from 'vue';
/**
* 实训教学函数
* @param param 父级传入 {t,fnActiveConfigNode}
* @returns
*/
export default function usePtOptions({ t, fnActiveConfigNode }: any) {
const ptConfigState = reactive({
saveLoading: false,
restLoading: false,
applyLoading: false,
});
/**(管理员)保存网元下所有配置为示例配置 */
function ptConfigSave(neType: string) {
ptConfigState.saveLoading = true;
ptSaveAsDefault(neType, '001')
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
ptConfigState.saveLoading = false;
fnActiveConfigNode('#');
});
}
/**重置网元下所有配置 */
function ptConfigReset(neType: string) {
ptConfigState.restLoading = true;
ptResetAsDefault(neType)
.then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
ptConfigState.restLoading = false;
fnActiveConfigNode('#');
});
}
/**配置下方应用(学生)申请撤回和(管理/教师)应用退回 */
function ptConfigApply(
neType: string,
status: '0' | '1' | '2' | '3',
student?: string
) {
let result: any;
if (status === '2' || status === '3') {
let from: {
neType: string;
status: string;
student?: string;
backInfo?: string;
} = {
neType,
status: '2',
};
if (student) {
if (status === '2') {
from = { neType, status, student };
}
if (status === '3') {
from = { neType, status, student, backInfo: '请重新检查配置' };
}
}
result = updatePtNeConfigApply(from);
}
if (status === '0' || status === '1') {
result = stuPtNeConfigApply({ neType, status });
}
if (!result) return;
ptConfigState.applyLoading = true;
result
.then((res: any) => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
// 教师修改学生时改变状态
if (student) {
const item = classState.studentOptionsDef.find(
s => s.value === classState.student
);
if (item) {
item.applyStatus = status;
}
}
} else {
message.error({
content: `${res.msg}`,
duration: 3,
});
}
})
.finally(() => {
ptConfigState.applyLoading = false;
});
}
const classState = reactive<{
/**学生账号 */
student: string | undefined;
/**学生可选择列表 */
studentOptions: {
value: string;
label: string;
applyId: string;
applyStatus: string;
}[];
studentOptionsDef: {
value: string;
label: string;
applyId: string;
applyStatus: string;
}[];
}>({
student: undefined,
studentOptions: [],
studentOptionsDef: [],
});
// 仅教师加载
if (hasRoles(['teacher'])) {
onMounted(() => {
classStudents(); // 初始学生列表
});
}
/**学生选择搜索 */
function studentChange(v: any) {
if (!v) {
Object.assign(classState.studentOptions, classState.studentOptionsDef);
}
fnActiveConfigNode('#');
}
let timeout: any;
/**学生选择搜索 */
function studentSearch(val: string) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
if (!val) {
Object.assign(classState.studentOptions, classState.studentOptionsDef);
return;
}
timeout = setTimeout(() => classStudents(val), 500);
}
/**班级学生列表 */
function classStudents(val?: string) {
getPtClassStudents({ userName: val }).then(res => {
classState.studentOptions = [];
if (!Array.isArray(res.data) || res.data.length <= 0) {
return;
}
for (const v of res.data) {
classState.studentOptions.push({
value: v.userName,
label: v.userName,
applyId: v.applyId,
applyStatus: v.applyStatus,
});
// 设为最新状态
const item = classState.studentOptionsDef.find(
s => s.value === v.userName
);
if (item) {
item.applyStatus = v.applyStatus;
}
}
if (!val) {
Object.assign(classState.studentOptionsDef, classState.studentOptions);
}
});
}
// 学生状态
const studentStatus = computed(() => {
const item = classState.studentOptionsDef.find(
s => s.value === classState.student
);
if (item) return item.applyStatus;
return '';
});
return {
ptConfigState,
ptConfigSave,
ptConfigReset,
ptConfigApply,
classState,
studentStatus,
studentSearch,
studentChange,
};
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,442 @@
<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 } 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 useDictStore from '@/store/modules/dict';
import { listAllNeInfo } from '@/api/ne/neInfo';
import { parseDateToStr } from '@/utils/date-utils';
const { getDict } = useDictStore();
const appStore = useAppStore();
const route = useRoute();
const { t } = useI18n();
echarts.use([
TooltipComponent,
GaugeChart,
TitleComponent,
LegendComponent,
PieChart,
CanvasRenderer,
LabelLayout,
]);
/**图DOM节点实例对象 */
const statusBar = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
const statusBarChart = ref<any>(null);
/**网元状态字典数据 */
let indexColor = ref<DictType[]>([
{ label: 'Normal', value: 'normal', tagType: '', tagClass: '#91cc75' },
{
label: 'Abnormal',
value: 'abnormal',
tagType: '',
tagClass: '#ee6666',
},
]);
/**表格字段列 */
//customRender(){} ----单元格处理
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);
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: object[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
data: [],
selectedRowKeys: [],
});
/**表格状态 */
let nfInfo: any = reactive({
obj: 'OMC',
version: appStore.version,
status: t('views.index.normal'),
outTimeDate: '',
serialNum: appStore.serialNum,
});
/**表格状态类型 */
type nfStateType = {
/**主机名 */
hostName: string;
/**操作系统信息 */
osInfo: string;
/**IP地址 */
ipAddress: string;
/**版本 */
version: string;
/**CPU利用率 */
cpuUse: string;
/**内存使用 */
memoryUse: string;
/**用户容量 */
capability: number;
/**序列号 */
serialNum: string;
/**许可证到期日期 */
/* selectedRowKeys: (string | number)[];*/
expiryDate: string;
};
/**网元详细信息 */
let pronInfo: nfStateType = reactive({
hostName: '5gc',
osInfo: 'Linux 5gc 4.15.0-112-generic 2020 x86_64 GNU/Linux',
ipAddress: '-',
version: '-',
cpuUse: '-',
memoryUse: '-',
capability: 0,
serialNum: '-',
expiryDate: '-',
});
/**查询网元状态列表 */
function fnGetList(one: boolean) {
if (tableState.loading) return;
one && (tableState.loading = true);
listAllNeInfo({ bandStatus: true }).then(res => {
tableState.data = res.data;
tableState.loading = false;
var rightNum = 0;
var errorNum = 0;
res.data.forEach((item: any) => {
if (item.serverState.online) {
rightNum++;
} else {
errorNum++;
}
});
const optionData: any = {
title: {
text: '',
subtext: '',
left: 'center',
},
tooltip: {
trigger: 'item',
},
legend: {
orient: 'vertical',
left: 'left',
},
color: indexColor.value.map(item => item.tagClass),
series: [
{
name: t('views.index.realNeStatus'),
type: 'pie',
radius: '70%',
center: ['50%', '50%'],
data: [
{ value: rightNum, name: t('views.index.normal') },
{ value: errorNum, name: t('views.index.abnormal') },
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
label: {},
},
],
};
fnDesign(statusBar.value, optionData);
});
}
function fnDesign(container: HTMLElement | undefined, option: any) {
if (!container) return;
if (!statusBarChart.value) {
statusBarChart.value = markRaw(echarts.init(container, 'light'));
}
option && statusBarChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (statusBarChart.value) {
statusBarChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
/**抽屉 网元详细信息 */
const open = ref(false);
const closeDrawer = () => {
open.value = false;
};
/**抽屉 网元详细信息 */
/**监听表格行事件*/
function rowClick(record: any, index: any) {
return {
onClick: (event: any) => {
let pronData = JSON.parse(JSON.stringify(record.serverState));
if (!pronData.online) {
message.error(t('views.index.neStatus'), 2);
return false;
} else {
const totalMemInKB = pronData.mem?.totalMem;
const nfUsedMemInKB = pronData.mem?.nfUsedMem;
const sysMemUsageInKB = pronData.mem?.sysMemUsage;
// 将KB转换为MB
const totalMemInMB = Math.round((totalMemInKB / 1024) * 100) / 100;
const nfUsedMemInMB = Math.round((nfUsedMemInKB / 1024) * 100) / 100;
const sysMemUsageInMB =
Math.round((sysMemUsageInKB / 1024) * 100) / 100;
//渲染详细信息
pronInfo = {
hostName: pronData.hostname,
osInfo: pronData.os,
ipAddress: pronData.neIP,
version: pronData.version,
cpuUse:
pronData.neName +
':' +
pronData.cpu?.nfCpuUsage / 100 +
'%; ' +
'SYS:' +
pronData.cpu?.sysCpuUsage / 100 +
'%',
memoryUse:
'Total:' +
totalMemInMB +
'MB; ' +
pronData.name +
':' +
nfUsedMemInMB +
'MB; SYS:' +
sysMemUsageInMB +
'MB',
capability: pronData.capability,
serialNum: pronData.sn,
expiryDate: pronData.expire,
};
}
open.value = true;
},
};
}
let timer: any;
/**
* 国际化翻译转换
*/
function fnLocale() {
let title = route.meta.title as string;
if (title.indexOf('router.') !== -1) {
title = t(title);
}
appStore.setTitle(title);
}
onMounted(() => {
getDict('index_status')
.then(res => {
if (res.length > 0) {
indexColor.value = res;
}
})
.finally(() => {
fnLocale();
fnGetList(true);
timer = setInterval(() => fnGetList(false), 10000); // 每隔10秒执行一次
});
});
// 在组件卸载之前清除定时器
onBeforeUnmount(() => {
clearInterval(timer);
});
</script>
<template>
<PageContainer :breadcrumb="{}">
<div>
<a-drawer :open="open" @close="closeDrawer" :width="700">
<a-descriptions bordered :column="1" :label-style="{ width: '160px' }">
<a-descriptions-item :label="t('views.index.hostName')">{{
pronInfo.hostName
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.osInfo')">{{
pronInfo.osInfo
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.ipAddress')">{{
pronInfo.ipAddress
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.version')">{{
pronInfo.version
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.capability')">{{
pronInfo.capability
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.cpuUse')">{{
pronInfo.cpuUse
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.memoryUse')">{{
pronInfo.memoryUse
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.serialNum')">{{
pronInfo.serialNum
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.expiryDate')">{{
pronInfo.expiryDate
}}</a-descriptions-item>
</a-descriptions>
</a-drawer>
</div>
<a-row :gutter="16">
<a-col :lg="14" :md="16" :xs="24">
<!-- 表格列表 -->
<a-table
class="table"
row-key="id"
size="small"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:pagination="false"
:scroll="{ x: true }"
:customRow="rowClick"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'status'">
<div v-if="record.serverState.online">
<a-tag color="blue">{{ t('views.index.normal') }}</a-tag>
</div>
<div v-else>
<a-tag color="pink">{{ t('views.index.abnormal') }}</a-tag>
</div>
</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
:title="t('views.index.mark')"
style="margin-top: 16px"
size="small"
>
<a-descriptions
bordered
:column="1"
:label-style="{ width: '160px' }"
>
<a-descriptions-item :label="t('views.index.object')">{{
nfInfo.obj
}}</a-descriptions-item>
<template v-if="nfInfo.obj === 'OMC'">
<a-descriptions-item :label="t('views.index.versionNum')">{{
nfInfo.version
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.systemStatus')">{{
nfInfo.status
}}</a-descriptions-item>
</template>
<template v-else>
<a-descriptions-item :label="t('views.index.serialNum')">{{
nfInfo.serialNum
}}</a-descriptions-item>
<a-descriptions-item :label="t('views.index.expiryDate')">{{
nfInfo.outTimeDate
}}</a-descriptions-item>
</template>
</a-descriptions>
</a-card>
</a-col>
</a-row>
</PageContainer>
</template>
<style lang="less" scoped></style>