feat(菜单管理): 父级菜单改为树形结构

This commit is contained in:
sheng
2023-07-31 17:58:52 +08:00
parent 2c9d8766e3
commit a524137e18
6 changed files with 63 additions and 24 deletions

View File

@@ -35,9 +35,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, ref, onMounted } from 'vue'; import { reactive, ref, onMounted } from 'vue';
import { ElForm, FormRules } from 'element-plus'; import { ElForm, FormRules } from 'element-plus';
import { lazyLoadDept, AddObj, UpdateObj } from '../api'; import { lazyLoadDept, AddObj, UpdateObj } from '../../api';
import { successNotification } from '../../../../utils/message'; import { successNotification } from '/@/utils/message';
import { DeptFormDataType, TreeItemType, APIResponseData } from '../types'; import { DeptFormDataType, TreeItemType, APIResponseData } from '../../types';
import type Node from 'element-plus/es/components/tree/src/model/node'; import type Node from 'element-plus/es/components/tree/src/model/node';
interface IProps { interface IProps {

View File

@@ -73,9 +73,9 @@ import { ref, watch, toRaw, h } from 'vue';
import { ElTree } from 'element-plus'; import { ElTree } from 'element-plus';
import { getElementLabelLine } from 'element-tree-line'; import { getElementLabelLine } from 'element-tree-line';
import { Search } from '@element-plus/icons-vue'; import { Search } from '@element-plus/icons-vue';
import { lazyLoadDept, deptMoveUp, deptMoveDown } from '../api'; import { lazyLoadDept, deptMoveUp, deptMoveDown } from '../../api';
import { warningNotification } from '../../../../utils/message'; import { warningNotification } from '/@/utils/message';
import { TreeItemType, APIResponseData } from '../types'; import { TreeItemType, APIResponseData } from '../../types';
import type Node from 'element-plus/es/components/tree/src/model/node'; import type Node from 'element-plus/es/components/tree/src/model/node';
interface IProps { interface IProps {
@@ -276,7 +276,7 @@ const handleSort = async (type: string) => {
} }
.el-tree .el-tree-node__expand-icon:before { .el-tree .el-tree-node__expand-icon:before {
background: url('../../../../assets/img/menu-tree-show-icon.png') no-repeat center / 100%; background: url('../../../../../assets/img/menu-tree-show-icon.png') no-repeat center / 100%;
content: ''; content: '';
display: block; display: block;
width: 24px; width: 24px;
@@ -284,7 +284,7 @@ const handleSort = async (type: string) => {
} }
.el-tree .el-tree-node__expand-icon.expanded:before { .el-tree .el-tree-node__expand-icon.expanded:before {
background: url('../../../../assets/img/menu-tree-hidden-icon.png') no-repeat center / 100%; background: url('../../../../../assets/img/menu-tree-hidden-icon.png') no-repeat center / 100%;
content: ''; content: '';
display: block; display: block;
width: 24px; width: 24px;

View File

@@ -3,7 +3,7 @@
<el-row class="dept-el-row"> <el-row class="dept-el-row">
<el-col :span="6"> <el-col :span="6">
<div class="dept-box dept-left"> <div class="dept-box dept-left">
<TreeCom :treeData="deptTreeData" @treeClick="handleTreeClick" @updateDept="handleUpdateMenu" @deleteDept="handleDeleteMenu" /> <DeptTreeCom :treeData="deptTreeData" @treeClick="handleTreeClick" @updateDept="handleUpdateMenu" @deleteDept="handleDeleteMenu" />
</div> </div>
</el-col> </el-col>
@@ -24,8 +24,8 @@
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import XEUtils from 'xe-utils'; import XEUtils from 'xe-utils';
import { ElMessageBox } from 'element-plus'; import { ElMessageBox } from 'element-plus';
import TreeCom from './components/TreeCom.vue'; import DeptTreeCom from './components/DeptTreeCom/index.vue';
import DeptFormCom from './components/DeptFormCom.vue'; import DeptFormCom from './components/DeptFormCom/index.vue';
import DeptUserCom from './components/DeptUserCom/index.vue'; import DeptUserCom from './components/DeptUserCom/index.vue';
import { GetList, DelObj } from './api'; import { GetList, DelObj } from './api';
import { successNotification } from '../../../utils/message'; import { successNotification } from '../../../utils/message';

View File

@@ -8,10 +8,19 @@
</div> </div>
<el-form ref="formRef" :rules="rules" :model="menuFormData" label-width="80px" label-position="right"> <el-form ref="formRef" :rules="rules" :model="menuFormData" label-width="80px" label-position="right">
<el-form-item label="菜单名称" prop="name"> <el-form-item label="菜单名称" prop="name">
<el-input v-model="menuFormData.name" /> <el-input v-model="menuFormData.name" placeholder="菜单名称" />
</el-form-item> </el-form-item>
<el-form-item label="父级菜单" prop="parent"> <el-form-item label="父级菜单" prop="parent">
<el-input v-model="menuFormData.parent" /> <!-- <el-input v-model="menuFormData.parent" /> -->
<el-tree-select
v-model="menuFormData.parent"
:props="defaultTreeProps"
:data="deptDefaultList"
lazy
check-strictly
:load="handleTreeLoad"
style="width: 100%"
/>
</el-form-item> </el-form-item>
<el-form-item label="图标" prop="icon"> <el-form-item label="图标" prop="icon">
<IconSelector clearable v-model="menuFormData.icon" /> <IconSelector clearable v-model="menuFormData.icon" />
@@ -39,22 +48,22 @@
</el-form-item> </el-form-item>
<el-form-item label="备注"> <el-form-item label="备注">
<el-input v-model="menuFormData.description" maxlength="200" show-word-limit type="textarea" /> <el-input v-model="menuFormData.description" maxlength="200" show-word-limit type="textarea" placeholder="备注" />
</el-form-item> </el-form-item>
<el-divider></el-divider> <el-divider></el-divider>
<div style="min-height: 184px"> <div style="min-height: 184px">
<el-form-item v-if="menuFormData.menu_status === '3'" label="Url"> <el-form-item v-if="menuFormData.menu_status === '3'" label="Url">
<el-input v-model="menuFormData.web_path" /> <el-input v-model="menuFormData.web_path" placeholder="Url" />
</el-form-item> </el-form-item>
<el-form-item v-if="menuFormData.menu_status === '1'" label="路由地址"> <el-form-item v-if="menuFormData.menu_status === '1'" label="路由地址">
<el-input v-model="menuFormData.web_path" /> <el-input v-model="menuFormData.web_path" placeholder="路由地址" />
</el-form-item> </el-form-item>
<el-form-item v-if="menuFormData.menu_status === '1'" label="组件名称"> <el-form-item v-if="menuFormData.menu_status === '1'" label="组件名称">
<el-input v-model="menuFormData.component_name" /> <el-input v-model="menuFormData.component_name" placeholder="组件名称" />
</el-form-item> </el-form-item>
<el-form-item v-if="menuFormData.menu_status === '1'" label="组件地址"> <el-form-item v-if="menuFormData.menu_status === '1'" label="组件地址">
@@ -88,12 +97,27 @@
import { ref, onMounted, reactive } from 'vue'; import { ref, onMounted, reactive } from 'vue';
import { ElForm, FormRules } from 'element-plus'; import { ElForm, FormRules } from 'element-plus';
import IconSelector from '/@/components/iconSelector/index.vue'; import IconSelector from '/@/components/iconSelector/index.vue';
import { MenuFormDataType, MenuTreeItemType, ComponentFileItem } from '../../types'; import { lazyLoadMenu } from '../../api';
import { MenuFormDataType, MenuTreeItemType, ComponentFileItem, APIResponseData } from '../../types';
import type Node from 'element-plus/es/components/tree/src/model/node';
interface IProps { interface IProps {
initFormData: Partial<MenuTreeItemType> | null; initFormData: Partial<MenuTreeItemType> | null;
treeData: MenuTreeItemType[];
} }
const defaultTreeProps: any = {
children: 'children',
label: 'name',
value: 'id',
isLeaf: (data: MenuTreeItemType[], node: Node) => {
if (node?.data.hasChild) {
return false;
} else {
return true;
}
},
};
const validateWebPath = (rule: any, value: string, callback: Function) => { const validateWebPath = (rule: any, value: string, callback: Function) => {
let pattern = /^\/.*?/; let pattern = /^\/.*?/;
if (!pattern.test(value)) { if (!pattern.test(value)) {
@@ -105,6 +129,7 @@ const validateWebPath = (rule: any, value: string, callback: Function) => {
const props = withDefaults(defineProps<IProps>(), { const props = withDefaults(defineProps<IProps>(), {
initFormData: () => null, initFormData: () => null,
treeData: () => [],
}); });
const emit = defineEmits(['drawerClose']); const emit = defineEmits(['drawerClose']);
@@ -116,6 +141,7 @@ const rules = reactive<FormRules>({
parent: [{ required: true, message: '父级菜单必选', trigger: ['blur', 'change'] }], parent: [{ required: true, message: '父级菜单必选', trigger: ['blur', 'change'] }],
}); });
let deptDefaultList = ref<MenuTreeItemType[]>([]);
let menuFormData = reactive<MenuFormDataType>({ let menuFormData = reactive<MenuFormDataType>({
parent: '', parent: '',
name: '', name: '',
@@ -170,6 +196,17 @@ const createFilter = (queryString: string) => {
}; };
}; };
/**
* 树的懒加载
*/
const handleTreeLoad = (node: Node, resolve: Function) => {
if (node.level !== 0) {
lazyLoadMenu({ parent: node.data.id }).then((res: APIResponseData) => {
resolve(res.data);
});
}
};
const handleSubmit = () => {}; const handleSubmit = () => {};
const handleCancel = (type: string = '') => { const handleCancel = (type: string = '') => {
@@ -178,13 +215,15 @@ const handleCancel = (type: string = '') => {
}; };
onMounted(async () => { onMounted(async () => {
props.treeData.map((item) => {
deptDefaultList.value.push(item);
});
setMenuFormData(); setMenuFormData();
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.menu-form-com { .menu-form-com {
border-radius: 8px;
margin: 10px; margin: 10px;
overflow-y: auto; overflow-y: auto;
.menu-form-alert { .menu-form-alert {

View File

@@ -112,7 +112,7 @@ const emit = defineEmits(['treeClick', 'deleteDept', 'updateDept']);
let filterVal = ref(''); let filterVal = ref('');
let sortDisable = ref(false); let sortDisable = ref(false);
let treeSelectMenu = ref<TreeTypes>({}); let treeSelectMenu = ref<Partial<MenuTreeItemType>>({});
let treeSelectNode = ref<Node | null>(null); let treeSelectNode = ref<Node | null>(null);
watch(filterVal, (val) => { watch(filterVal, (val) => {

View File

@@ -15,7 +15,7 @@
</el-row> </el-row>
<el-drawer v-model="drawerVisible" title="菜单配置" direction="rtl" size="500px" :close-on-click-modal="false" :before-close="handleDrawerClose"> <el-drawer v-model="drawerVisible" title="菜单配置" direction="rtl" size="500px" :close-on-click-modal="false" :before-close="handleDrawerClose">
<MenuFormCom v-if="drawerVisible" :initFormData="drawerFormData" @drawerClose="handleDrawerClose" /> <MenuFormCom v-if="drawerVisible" :initFormData="drawerFormData" :treeData="menuTreeData" @drawerClose="handleDrawerClose" />
</el-drawer> </el-drawer>
</fs-page> </fs-page>
</template> </template>
@@ -27,7 +27,7 @@ import { ElMessageBox } from 'element-plus';
import MenuTreeCom from './components/MenuTreeCom/index.vue'; import MenuTreeCom from './components/MenuTreeCom/index.vue';
import MenuButtonCom from './components/MenuButtonCom/index.vue'; import MenuButtonCom from './components/MenuButtonCom/index.vue';
import MenuFormCom from './components/MenuFormCom/index.vue'; import MenuFormCom from './components/MenuFormCom/index.vue';
import * as api from './api'; import { GetList, DelObj } from './api';
import { successNotification } from '/@/utils/message'; import { successNotification } from '/@/utils/message';
import { APIResponseData, MenuTreeItemType } from './types'; import { APIResponseData, MenuTreeItemType } from './types';
@@ -37,7 +37,7 @@ let drawerVisible = ref(false);
let drawerFormData = ref<Partial<MenuTreeItemType>>({}); let drawerFormData = ref<Partial<MenuTreeItemType>>({});
const getData = () => { const getData = () => {
api.GetList({}).then((ret: APIResponseData) => { GetList({}).then((ret: APIResponseData) => {
const responseData = ret.data; const responseData = ret.data;
const result = XEUtils.toArrayTree(responseData, { const result = XEUtils.toArrayTree(responseData, {
parentKey: 'parent', parentKey: 'parent',
@@ -81,7 +81,7 @@ const handleDeleteMenu = (id: string, callback: Function) => {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning', type: 'warning',
}).then(async () => { }).then(async () => {
const res: APIResponseData = await api.DelObj(id); const res: APIResponseData = await DelObj(id);
callback(); callback();
if (res?.code === 2000) { if (res?.code === 2000) {
successNotification(res.msg as string); successNotification(res.msg as string);