This commit is contained in:
1378012178@qq.com 2025-03-20 17:21:16 +08:00
commit 16928bde50
11 changed files with 801 additions and 51 deletions

View File

@ -14,8 +14,17 @@ enum Api {
getDictItems = '/sys/dict/getDictItems/',
getTableList = '/sys/user/queryUserComponentData',
getCategoryData = '/sys/category/loadAllData',
getNuList = '/iot/cameraInfo/nuList',//后期调整
}
/**
*
* @param params
*/
export const getNuList = (params) => {
return defHttp.get({ url: Api.getNuList, params });
};
/**
*
*/

View File

@ -45,6 +45,7 @@ import JImageUpload from './jeecg/components/JImageUpload.vue';
import JDictSelectTag from './jeecg/components/JDictSelectTag.vue';
import JSelectDept from './jeecg/components/JSelectDept.vue';
import JAreaSelect from './jeecg/components/JAreaSelect.vue';
import JSelectNu from './jeecg/components/JSelectNu.vue';
import JEditor from './jeecg/components/JEditor.vue';
// import JMarkdownEditor from './jeecg/components/JMarkdownEditor.vue';
import JSelectInput from './jeecg/components/JSelectInput.vue';
@ -127,6 +128,7 @@ componentMap.set('JImageUpload', JImageUpload);
componentMap.set('JDictSelectTag', JDictSelectTag);
componentMap.set('JSelectDept', JSelectDept);
componentMap.set('JAreaSelect', JAreaSelect);
componentMap.set('JSelectNu', JSelectNu);
// componentMap.set(
// 'JEditor',
// createAsyncComponent(() => import('./jeecg/components/JEditor.vue'))

View File

@ -0,0 +1,167 @@
<!--职务选择组件-->
<template>
<div class="JSelectNu">
<JSelectBiz @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs"></JSelectBiz>
<!-- update-begin--author:liaozhiyang---date:20240515---forQQYUN-9260必填模式下会影响到弹窗内antd组件的样式 -->
<a-form-item>
<NuSelectModal @register="regModal" @getSelectResult="setValue" v-bind="getBindValue" :isRadioSelection="selectType"></NuSelectModal>
</a-form-item>
<!-- update-end--author:liaozhiyang---date:20240515---forQQYUN-9260必填模式下会影响到弹窗内antd组件的样式 -->
</div>
</template>
<script lang="ts">
import NuSelectModal from './modal/NuSelectModal.vue';
import JSelectBiz from './base/JSelectBiz.vue';
import { defineComponent, ref, reactive, watchEffect, watch, provide, computed, unref } from 'vue';
import { useModal } from '/@/components/Modal';
import { propTypes } from '/@/utils/propTypes';
import { useRuleFormItem } from '/@/hooks/component/useFormItem';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { SelectValue } from 'ant-design-vue/es/select';
export default defineComponent({
name: 'JSelectNu',
components: {
NuSelectModal,
JSelectBiz,
},
inheritAttrs: false,
props: {
value: propTypes.oneOfType([propTypes.string, propTypes.array]),
rowKey: {
type: String,
default: 'nuId',
},
labelKey: {
type: String,
default: 'nuName',
},
selectType: {
type: Boolean,
default: false,
},
params: {
type: Object,
default: () => {},
},
},
emits: ['options-change', 'change', 'update:value'],
setup(props, { emit, refs }) {
const emitData = ref<any[]>();
//model
const [regModal, { openModal }] = useModal();
//
const [state] = useRuleFormItem(props, 'value', 'change', emitData);
//
const selectOptions = ref<SelectValue>([]);
//
let selectValues = reactive<object>({
value: [],
change: false,
});
//
const loadingEcho = ref<boolean>(false);
// selectOptions,xxxBiz
provide('selectOptions', selectOptions);
// selectValues,xxxBiz
provide('selectValues', selectValues);
// loadingEcho,xxxBiz
provide('loadingEcho', loadingEcho);
const tag = ref(false);
const attrs = useAttrs();
/**
* 监听组件值
*/
watchEffect(() => {
props.value && initValue();
});
/**
* 监听selectValues变化
*/
watch(selectValues, () => {
if (selectValues) {
state.value = selectValues.value;
}
});
/**
* 打卡弹出框
*/
function handleOpen() {
tag.value = true;
openModal(true, {
isUpdate: false,
});
}
/**
* 将字符串值转化为数组
*/
function initValue() {
let value = props.value ? props.value : [];
if (value && typeof value === 'string' && value != 'null' && value != 'undefined') {
state.value = value.split(',');
selectValues.value = value.split(',');
}
}
/**
* 设置下拉框的值
*/
function setValue(options, values) {
selectOptions.value = options;
//emitData.value = values.join(",");
state.value = values;
selectValues.value = values;
//update-begin-author:liusq date:20230517 for:v-model
emit('update:value', values.join(','));
//update-begin-author:liusq date:20230517 for:v-model
}
const getBindValue = Object.assign({}, unref(props), unref(attrs));
return {
state,
getBindValue,
attrs,
selectOptions,
selectValues,
loadingEcho,
tag,
regModal,
setValue,
handleOpen,
};
},
});
</script>
<style lang="less" scoped>
// update-begin--author:liaozhiyang---date:20240515---forQQYUN-9260antd
.JSelectNu {
> .ant-form-item {
display: none;
}
}
// update-end--author:liaozhiyang---date:20240515---forQQYUN-9260antd
.j-select-row {
@width: 82px;
.left {
width: calc(100% - @width - 8px);
}
.right {
width: @width;
}
.full {
width: 100%;
}
:deep(.ant-select-search__field) {
display: none !important;
}
}
</style>

View File

@ -0,0 +1,190 @@
<!--职务选择框-->
<template>
<div>
<BasicModal
v-bind="$attrs"
@register="register"
:title="modalTitle"
width="1100px"
wrapClassName="j-user-select-modal"
@ok="handleOk"
destroyOnClose
@visible-change="visibleChange"
>
<a-row>
<a-col :span="showSelected ? 18 : 24">
<BasicTable
:columns="columns"
:bordered="true"
:useSearchForm="true"
:formConfig="formConfig"
:api="getNuList"
:searchInfo="searchInfo"
:rowSelection="rowSelection"
:indexColumnProps="indexColumnProps"
v-bind="getBindValue"
></BasicTable>
</a-col>
<a-col :span="showSelected ? 6 : 0">
<BasicTable
v-bind="selectedTable"
:dataSource="selectRows"
:useSearchForm="true"
:formConfig="{ showActionButtonGroup: false, baseRowStyle: { minHeight: '40px' } }"
>
<!--操作栏-->
<template #action="{ record }">
<a href="javascript:void(0)" @click="handleDeleteSelected(record)"><Icon icon="ant-design:delete-outlined"></Icon></a>
</template>
</BasicTable>
</a-col>
</a-row>
</BasicModal>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { getNuList } from '/@/api/common/api';
import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
import { useSelectBiz } from '/@/components/Form/src/jeecg/hooks/useSelectBiz';
import { useAttrs } from '/@/hooks/core/useAttrs';
import { selectProps } from '/@/components/Form/src/jeecg/props/props';
export default defineComponent({
name: 'NuSelectModal',
components: {
//BasicTable
BasicModal,
BasicTable: createAsyncComponent(() => import('/@/components/Table/src/BasicTable.vue'), {
loading: true,
}),
},
props: {
...selectProps,
//
modalTitle: {
type: String,
default: '护理单元选择',
},
},
emits: ['register', 'getSelectResult'],
setup(props, { emit, refs }) {
//
const [register, { closeModal }] = useModalInner();
const attrs = useAttrs();
//
const config = {
canResize: false,
bordered: true,
size: 'small',
//rowKey,
rowKey: props.rowKey,
};
const getBindValue = Object.assign({}, unref(props), unref(attrs), config);
const [{ rowSelection, visibleChange, indexColumnProps, getSelectResult, handleDeleteSelected, selectRows }] = useSelectBiz(
getNuList,
getBindValue
);
const searchInfo = ref(props.params);
//form
const formConfig = {
labelCol: {
span: 4,
},
baseColProps: {
xs: 24,
sm: 10,
md: 10,
lg: 10,
xl: 10,
xxl: 10,
},
//update-begin-author:liusq date:2023-10-30 for: [issues/5514]
actionColOptions: {
xs: 24,
sm: 8,
md: 8,
lg: 8,
xl: 8,
xxl: 8,
},
//update-end-author:liusq date:2023-10-30 for: [issues/5514]
schemas: [
{
label: '护理单元名称',
field: 'nuName',
component: 'JInput',
colProps: { span: 10 },
},
],
};
//
const columns = [
{
title: '护理单元ID',
dataIndex: 'nuId',
width: 180,
align: 'left',
},
{
title: '护理单元名称',
dataIndex: 'nuName',
// width: 180,
},
];
//table
const selectedTable = {
pagination: false,
showIndexColumn: false,
scroll: { y: 390 },
size: 'small',
canResize: false,
bordered: true,
rowKey: 'nuId',
columns: [
{
title: '护理单元名称',
dataIndex: 'nuName',
width: 40,
},
{
title: '操作',
dataIndex: 'action',
align: 'center',
width: 40,
slots: { customRender: 'action' },
},
],
};
/**
* 确定选择
*/
function handleOk() {
getSelectResult((options, values) => {
//
emit('getSelectResult', options, values);
//
closeModal();
});
}
return {
handleOk,
getNuList,
register,
visibleChange,
getBindValue,
formConfig,
indexColumnProps,
columns,
rowSelection,
selectedTable,
selectRows,
handleDeleteSelected,
searchInfo,
};
},
});
</script>

View File

@ -156,5 +156,6 @@ export type ComponentType =
| 'linkRecordSelect'
| 'RangeTime'
| 'JRangeNumber'
| 'JInputSelect';
| 'JInputSelect'
| 'JSelectNu';

View File

@ -1,31 +1,33 @@
<template>
<a-card :bordered="false" style="height: 100%">
<div class="j-table-operator" style="width: 100%">
<!-- <a-button preIcon="ant-design:sync-outlined" @click="loadRootTreeData">刷新</a-button>-->
<a-button type="primary" preIcon="ant-design:sync-outlined" @click="syncProjectInfo">同步项目</a-button>
<template v-if="currentRegion !=null">
<a-button preIcon="ant-design:sync-outlined" @click="syncRegionInfo">同步区域</a-button>
</template>
</div>
<a-spin :spinning="loading">
<!--区域树-->
<template v-if="treeData.length > 0">
<a-tree
v-if="!treeReloading"
:clickRowToExpand="false"
:treeData="treeData"
:selectedKeys="selectedKeys"
:checkStrictly="checkStrictly"
:load-data="loadChildrenTreeData"
:checkedKeys="checkedKeys"
v-model:expandedKeys="expandedKeys"
@select="onSelect"
>
</a-tree>
</template>
<a-empty v-else description="暂无数据" />
</a-spin>
</a-card>
<a-card :bordered="false" style="height: 100%">
<a-spin :spinning="syncoading">
<div class="j-table-operator" style="width: 100%">
<a-button preIcon="ant-design:sync-outlined" @click="loadRootTreeData">刷新</a-button>
<!-- <a-button type="primary" preIcon="ant-design:sync-outlined" @click="syncProjectInfo">同步项目</a-button>-->
<!-- <template v-if="currentRegion !=null">-->
<!-- <a-button preIcon="ant-design:sync-outlined" @click="syncRegionInfo">同步区域</a-button>-->
<!-- </template>-->
</div>
<a-spin :spinning="loading">
<!--区域树-->
<template v-if="treeData.length > 0">
<a-tree
v-if="!treeReloading"
:clickRowToExpand="false"
:treeData="treeData"
:selectedKeys="selectedKeys"
:checkStrictly="checkStrictly"
:load-data="loadChildrenTreeData"
:checkedKeys="checkedKeys"
v-model:expandedKeys="expandedKeys"
@select="onSelect"
>
</a-tree>
</template>
<a-empty v-else description="暂无数据" />
</a-spin>
</a-spin>
</a-card>
</template>
<script lang="ts" setup>
@ -37,6 +39,7 @@
import { queryProjectTreeSync, queryRegionTreeSync, syncProject, syncRegion } from '@/views/iot/tplink/camera/camera.api';
const emit = defineEmits(['select', 'rootTreeData']);
const syncoading = ref<boolean>(false);
const loading = ref<boolean>(false);
//
const treeData = ref<any[]>([]);
@ -78,6 +81,7 @@
emit('rootTreeData', treeData.value);
} finally {
loading.value = false;
syncoading.value = false;
}
}
@ -172,24 +176,29 @@
/**
* 同步项目
*/
async function syncProjectInfo(){
await syncProject();
await loadRootTreeData();
}
// async function syncProjectInfo(){
// syncoading.value = true;
// await syncProject();
// await loadRootTreeData();
// }
/**
* 同步区域
*/
async function syncRegionInfo(){
let data = currentRegion.value;
if (data == null) {
createMessage.warning('请先选择一个区域');
return;
}
const record = { projectId: data.projectId };
await syncRegion(record);
await loadRootTreeData();
}
// async function syncRegionInfo(){
// let data = currentRegion.value;
// if (data == null) {
// createMessage.warning('');
// return;
// }
// const record = {
// projectId: data.projectId,
// regionId: data.regionId
// };
// syncoading.value = true;
// await syncRegion(record);
// await loadRootTreeData();
// }
defineExpose({
loadRootTreeData,

View File

@ -128,3 +128,7 @@ export const formSchema: FormSchema[] = [
},
];
// 项目基础表单
export const projectFormSchema: FormSchema[] = [
];

View File

@ -0,0 +1,92 @@
<template>
<a-spin :spinning="loading">
<BasicForm @register="registerForm" />
<div class="j-box-bottom-button offset-20" style="margin-top: 30px">
<div class="j-box-bottom-button-float">
<a-button preIcon="ant-design:sync-outlined" @click="onReset">重置</a-button>
<a-button type="primary" preIcon="ant-design:save-filled" @click="onSubmit">保存</a-button>
</div>
</div>
</a-spin>
</template>
<script lang="ts" setup>
import { watch, computed, inject, ref, unref, onMounted } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index';
import { saveOrUpdateRegion } from '@/views/iot/tplink/region/RegionInfo.api';
import { formSchema } from '@/views/iot/tplink/region/RegionInfo.data';
import { useDesign } from '/@/hooks/web/useDesign';
const emit = defineEmits(['success']);
const props = defineProps({
data: { type: Object, default: () => ({}) },
rootTreeData: { type: Array, default: () => [] },
});
const loading = ref<boolean>(false);
//
const isUpdate = ref<boolean>(true);
//
const model = ref<object>({});
//
const [registerForm, { resetFields, setFieldsValue, validate, updateSchema }] = useForm({
schemas: formSchema,
showActionButtonGroup: false
});
// const categoryOptions = computed(() => {
// if (!!props?.data?.parentId) {
// return orgCategoryOptions.child;
// } else {
// return orgCategoryOptions.root;
// }
// });
onMounted(() => {
//
updateSchema([
{ field: 'parentId', componentProps: { disabled: true } },
{ field: 'orgCode', componentProps: { disabled: true } },
]);
// data
watch(
() => props.data,
async () => {
let record = unref(props.data);
if (typeof record !== 'object') {
record = {};
}
model.value = record;
await resetFields();
await setFieldsValue({ ...record });
},
{ deep: true, immediate: true }
);
});
//
async function onReset() {
await resetFields();
await setFieldsValue({ ...model.value });
}
//
async function onSubmit() {
try {
loading.value = true;
let values = await validate();
values = Object.assign({}, model.value, values);
//
await saveOrUpdateRegion(values, isUpdate.value);
//
emit('success');
Object.assign(model.value, values);
} finally {
loading.value = false;
}
}
</script>
<style lang="less">
</style>

View File

@ -6,7 +6,7 @@
<!--插槽:table标题-->
<template #tableTitle>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="handleCreate"> 新增</a-button>
<a-button preIcon="ant-design:sync-outlined" @click="handleSync"> 同步</a-button>
<a-button preIcon="ant-design:sync-outlined" @click="handleSync"> 同步下级</a-button>
</template>
<!--操作栏-->
<template #action="{ record }">
@ -25,14 +25,16 @@
import {ref, reactive, createVNode, h} from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { useListPage } from '/@/hooks/system/useListPage';
import { columns,searchFormSchema } from './RegionInfo.data';
import { list, sync, deleteRegion } from './RegionInfo.api';
import { columns,searchFormSchema } from '@/views/iot/tplink/region/RegionInfo.data';
import { list, sync, deleteRegion } from '@/views/iot/tplink/region/RegionInfo.api';
import { useUserStore } from '/@/store/modules/user';
import { useRouter } from 'vue-router';
import { useDrawer } from "@/components/Drawer";
import RegionInfoDrawer from './components/RegionInfoDrawer.vue';
import RegionInfoDrawer from './RegionInfoDrawer.vue';
import {Modal} from "ant-design-vue";
import {ExclamationCircleOutlined} from "@ant-design/icons-vue";
import {useMessage} from "@/hooks/web/useMessage";
const { createMessage } = useMessage();
//drawer
const [registerDrawer, { openDrawer }] = useDrawer();
let router = useRouter();
@ -47,10 +49,10 @@
api: list,
columns,
canResize:false,
formConfig: {
// labelWidth: 200,
schemas: searchFormSchema,
},
// formConfig: {
// // labelWidth: 200,
// schemas: searchFormSchema,
// },
actionColumn: {
width: 160,
fixed: 'right',

View File

@ -0,0 +1,214 @@
<template>
<a-card :bordered="false" style="height: 100%">
<a-spin :spinning="syncoading">
<div class="j-table-operator" style="width: 100%">
<!-- <a-button preIcon="ant-design:sync-outlined" @click="loadRootTreeData">刷新</a-button>-->
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="syncProjectInfo">新增</a-button>
<a-button preIcon="ant-design:sync-outlined" @click="syncProjectInfo">同步</a-button>
<a-button type="primary" preIcon="ant-design:plus-outlined" @click="syncProjectInfo">新增下级</a-button>
<template v-if="currentRegion !=null">
<a-button preIcon="ant-design:sync-outlined" @click="syncRegionInfo">同步下级</a-button>
</template>
</div>
<a-spin :spinning="loading">
<a-input-search placeholder="按名称搜索…" style="margin-bottom: 10px" @search="onSearch" />
<!--区域树-->
<template v-if="treeData.length > 0">
<a-tree
v-if="!treeReloading"
:clickRowToExpand="false"
:treeData="treeData"
:selectedKeys="selectedKeys"
:checkStrictly="checkStrictly"
:load-data="loadChildrenTreeData"
:checkedKeys="checkedKeys"
v-model:expandedKeys="expandedKeys"
@select="onSelect"
>
</a-tree>
</template>
<a-empty v-else description="暂无数据" />
</a-spin>
</a-spin>
</a-card>
</template>
<script lang="ts" setup>
import { nextTick, ref } from 'vue';
import { useModal } from '/@/components/Modal';
import { useMessage } from '/@/hooks/web/useMessage';
import { useMethods } from '/@/hooks/system/useMethods';
const { createMessage } = useMessage();
import { queryProjectTreeSync, queryRegionTreeSync, syncProject, syncRegion } from '@/views/iot/tplink/camera/camera.api';
const emit = defineEmits(['select', 'rootTreeData']);
const syncoading = ref<boolean>(false);
const loading = ref<boolean>(false);
//
const treeData = ref<any[]>([]);
//
const checkedKeys = ref<any[]>([]);
//
const expandedKeys = ref<any[]>([]);
//
const selectedKeys = ref<any[]>([]);
//
const treeReloading = ref<boolean>(false);
//
const checkStrictly = ref<boolean>(true);
//
const currentRegion = ref<any>(null);
//
async function loadRootTreeData() {
try {
loading.value = true;
treeData.value = [];
const result = await queryProjectTreeSync();
if (Array.isArray(result)) {
treeData.value = result;
}
if (expandedKeys.value.length === 0) {
autoExpandParentNode();
} else {
if (selectedKeys.value.length === 0) {
let item = treeData.value[0];
if (item) {
//
setSelectedKey(item.id, item);
}
} else {
emit('select', currentRegion.value);
}
}
emit('rootTreeData', treeData.value);
} finally {
loading.value = false;
syncoading.value = false;
}
}
loadRootTreeData();
/**
* 加载子级区域信息
*/
async function loadChildrenTreeData(treeNode) {
try {
const result = await queryRegionTreeSync({
parentId: treeNode.dataRef.id,
projectId: treeNode.dataRef.projectId,
});
if (result.length == 0) {
treeNode.dataRef.isLeaf = true;
} else {
treeNode.dataRef.children = result;
if (expandedKeys.value.length > 0) {
//
let subKeys: any[] = [];
for (let key of expandedKeys.value) {
if (result.findIndex((item) => item.id === key) !== -1) {
subKeys.push(key);
}
}
if (subKeys.length > 0) {
expandedKeys.value = [...expandedKeys.value];
}
}
}
treeData.value = [...treeData.value];
emit('rootTreeData', treeData.value);
} catch (e) {
console.error(e);
}
return Promise.resolve();
}
/**
* 自动展开父节点只展开一级
*/
function autoExpandParentNode() {
let item = treeData.value[0];
if (item) {
if (!item.isLeaf) {
expandedKeys.value = [item.key];
}
//
setSelectedKey(item.id, item);
reloadTree();
} else {
emit('select', null);
}
}
/**
* 重新加载树组件防止无法默认展开数据
*/
async function reloadTree() {
await nextTick();
treeReloading.value = true;
await nextTick();
treeReloading.value = false;
}
/**
* 设置当前选中的行
*/
function setSelectedKey(key: string, data?: object) {
console.log('setSelectedKey: ', key);
selectedKeys.value = [key];
if (data) {
currentRegion.value = data;
emit('select', data);
}
}
/**
* 树选择事件
*/
function onSelect(selKeys, event) {
console.log('onSelect: ', selKeys, event);
if (selKeys.length > 0 && selectedKeys.value[0] !== selKeys[0]) {
setSelectedKey(selKeys[0], event.selectedNodes[0]);
} else {
//
setSelectedKey(selectedKeys.value[0]);
}
}
/**
* 同步项目
*/
// async function syncProjectInfo(){
// syncoading.value = true;
// await syncProject();
// await loadRootTreeData();
// }
/**
* 同步区域
*/
async function syncRegionInfo(){
let data = currentRegion.value;
if (data == null) {
createMessage.warning('请先选择一个节点');
return;
}
const record = {
projectId: data.projectId,
regionId: data.regionId
};
syncoading.value = true;
await syncRegion(record);
await loadRootTreeData();
}
defineExpose({
loadRootTreeData,
});
</script>
<style lang="less" scoped>
</style>

View File

@ -0,0 +1,60 @@
<template>
<a-row class="p-2" type="flex" :gutter="10">
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px">
<RegionLeftTree ref="leftTree" @select="onTreeSelect" @rootTreeData="onRootTreeData" />
</a-col>
<a-col :xl="12" :lg="24" :md="24" style="margin-bottom: 10px">
<div style="height: 100%;" class="form-content">
<a-tabs v-show="regionData != null" defaultActiveKey="base-info">
<a-tab-pane tab="基本信息" key="base-info" forceRender style="position: relative">
<div style="padding: 20px">
<RegionForm :data="regionData" :rootTreeData="rootTreeData" @success="onSuccess" />
</div>
</a-tab-pane>
</a-tabs>
<div v-show="regionData == null" style="padding-top: 40px">
<a-empty description="请选择区域" />
</div>
</div>
</a-col>
</a-row>
</template>
<script lang="ts" name="iot-nuIotRegionInfo" setup>
import { ref } from 'vue';
import { useDesign } from '/@/hooks/web/useDesign';
import RegionLeftTree from './components/RegionLeftTree.vue'
import RegionForm from "@/views/iot/tplink/region/components/RegionForm.vue";
// ref
const leftTree = ref();
//
const regionData = ref({});
const rootTreeData = ref<any[]>([]);
//
function onTreeSelect(data) {
console.log('onTreeSelect: ', data);
regionData.value = data;
}
// rootTreeData
function onRootTreeData(data) {
rootTreeData.value = data;
}
</script>
<style lang="less" scoped>
.p-2{
height: calc(100vh - 120px);
}
.form-content{
background-color: #ffffff;
height: 100%;
}
:deep(.ant-tabs-nav ){
padding: 0 20px;
}
</style>