后端:
实体类中添加字段
@JsonInclude(JsonInclude.Include.NON_NULL) //属性值为null不进行序列化操作
@TableField(exist = false)
private List<Permission> children = new ArrayList<Permission>();
/**
* 用于前端判断是菜单、目录或按钮
*/
@TableField(exist = false)
private String value;
/**
* 是否展开
*/
@TableField(exist = false)
private Boolean open;
需要工具类,生成树形列表
public static List<Permission> makeMenuTree(List<Permission> menuList,Long pid){
//创建集合保存菜单数据
List<Permission> permissionList = new ArrayList<>();
//判断菜单列表是否为空,如果不为空则使用菜单列表,否则创建集合对象
Optional.ofNullable(menuList).orElse(new ArrayList<Permission>())
.stream().filter(item -> item!=null && item.getParentId() == pid)
.forEach(item ->{
//创建权限菜单对象
Permission permission = new Permission();
//将原有的属性赋值给菜单对象
BeanUtils.copyProperties(item,permission);
//获取每一个item对象的子菜单,递归生成菜单树
List<Permission> children = makeMenuTree(menuList, item.getId());
//设置子菜单
permission.setChildren(children);
//将菜单对象添加到集合
permissionList.add(permission);
});
//返回菜单信息
return permissionList;
}
service中,需要获取最顶级的菜单。
@Override
public List<Permission> findParentPermissionList() {
QueryWrapper<Permission> wrapper = new QueryWrapper<>();
//只查询0目录 1 菜单 的数据
wrapper.in("type", Arrays.asList(0,1));
//排序
wrapper.orderByAsc("order_num");
//调用查询菜单列表的方法
List<Permission> permissionList = permissionMapper.selectList(wrapper);
//创建部门对象
Permission permission = new Permission();
permission.setId(0L);
permission.setParentId(-1L);
permission.setLabel("顶级列表");
permissionList.add(permission);
//生成树列表
List<Permission> menuTree = MenuTree.makeMenuTree(permissionList, -1L);
return menuTree;
}
controller层:
@GetMapping("/parent/list")
public Result findParentPermissionList() {
//查询菜单列表
List<Permission> permissionList = permissionService.findParentPermissionList();
return Result.ok(permissionList);
}
前端:
打开对话框的添加按钮
//新增按钮
openAddWindow(){
//每次打开表单时,都要清空数据 ref 和 model
this.$resetForm("menuForm",this.menu)
//清空图标选择器
this.$nextTick(() => {
//拿到儿子组件里的属性值,赋值为空。
this.$refs.child.userChooseIcon = "";
})
//点击新增按钮,打开弹出窗口 并添加标题
this.menuDialog.visible = true;
this.menuDialog.title = "新增表单";
},
这里使用了v-if控制表单内容的显隐,使用v-show是不能去掉表单验证的。
<!--添加的弹出表单 使用自定义组件-->
<system-dialog
:title="menuDialog.title"
:width="menuDialog.width"
:height="menuDialog.height"
:visible="menuDialog.visible"
@onClose="onClose()"
@onConfirm="onConfirm()"
>
<!--引入插槽-->
<div slot="content">
<!--添加form表单-->
<el-form
:model="menu"
ref="menuForm"
:rules="rules"
label-width="80px"
:inline="true"
size="small"
>
<el-row>
<el-col :span="24">
<el-form-item prop="type" label="菜单类型">
<el-radio-group v-model="menu.type">
<el-radio :label="0">目录</el-radio>
<el-radio :label="1">菜单</el-radio>
<el-radio :label="2">按钮</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item prop="parentName" size="small" label="所属菜单">
<el-input @click.native="selectParentMenu()" v-model="menu.parentName" :readonly="true" ></el-input>
</el-form-item>
<el-form-item prop="label" size="small" label="菜单名称">
<el-input v-model="menu.label"></el-input>
</el-form-item>
<el-form-item prop="name" v-if="menu.type == 1" size="small" label="路由名称">
<el-input v-model="menu.name"></el-input>
</el-form-item>
<el-form-item prop="path" v-if="menu.type != 2" size="small" label="路由地址">
<el-input v-model="menu.path"></el-input>
</el-form-item>
<el-form-item prop="url" v-if="menu.type == 1" size="small" label="组件路径">
<el-input v-model="menu.url"></el-input>
</el-form-item>
<el-form-item prop="code" size="small" label="权限字段">
<el-input v-model="menu.code"></el-input>
</el-form-item>
<el-form-item size="small" label="菜单图标">
<my-icon @selecticon="setIcon" ref="child"></my-icon>
</el-form-item>
<el-form-item size="small" label="菜单序号">
<el-input v-model="menu.orderNum"></el-input>
</el-form-item>
</el-form>
</div>
</system-dialog>
当点击所属菜单输入框时,会弹出一个新的对话框
再次定义一个对话框:用于显示树形结构的数据:
返回值中定义好属性:
menuDialog:{
title: "",
width: 630,
height: 270,
visible: false,
},
//菜单属性
menu: {
id: "",
type: "",
parentId: "",
parentName: "",
label: "",
icon: "",
name: "",
path: "",
url: "",
code: "",
orderNum: "",
},
其中使用了插槽插入了一个需要选择的树形结构:
<!--点击输入框时触发的弹窗-->
<system-dialog
:title="parentDialog.title"
:width="parentDialog.width"
:height="parentDialog.height"
:visible="parentDialog.visible"
@onClose="onParentClose()"
@onConfirm="onParentConfirm()"
>
<div slot="content">
<el-tree
style="font-size: 14px"
ref="parentTrees"
:data="treeList"
node-key="id"
:props="defaultProps"
empty-text="暂无数据"
:show-checkbox="false"
default-expand-all
:highlight-current="true"
:expand-on-click-node="false"
@node-click="handleNodeClick"
>
<!---->
<div class="customer-tree-node" slot-scope="{ node, data }">
<!--判断当前节点的子节点是否为0-->
<span v-if="data.children.length === 0">
<i class="el-icon-document"></i>
</span>
<!--绑定的这个方法,就是展开后显示-,没展开显示+ -->
<span v-else @click="openBtn(data)">
<!--判断当前节点是否是展开的-->
<!--icon-class=添加自定义的图标的名字-->
<!--图片添加在src/icons/svg下,后缀名.svg-->
<svg-icon v-if="data.open" icon-class="添加+" />
<svg-icon v-else icon-class="删除-" />
</span>
<!--显示的文本信息 选择完成之后数据的回显-->
<span style="margin-left: 5px">{{ node.label }}</span>
</div>
</el-tree>
</div>
</system-dialog>
:data:展示数据
node-key:每个树节点用来作为唯一标识的属性
props:配置选项
empty-text:内容为空时展示的文本
show-checkbox:节点是否可以被选择
default-expand-all:是否默认展开所有节点
:highlight-current:是否高亮当前选中的节点。默认false
:expand-on-click-node:是否在点击节点时展开或收缩节点,默认true.
@node-click: 节点被点击时的回调
定义属性:
//选择属性下拉菜单的弹框
parentDialog: {
title: '选择所属菜单',
width: 280,
height: 450,
visible: false
},
//树属性定义 绑定的props。其中的label的属性值是显示的名称
defaultProps: {
children: 'children',
label: 'label'
},
treeList: [], //所属菜单列表
给点击事件进行赋值(给表单属性赋值)
//点击树结构时触发
handleNodeClick(data){
this.menu.parentId = data.id
this.menu.parentName = data.label
},
点击图标进行树结构的折叠
/**
* 点击树节点+-号折叠展开事件
*/
openBtn(data) {
//修改折叠展开状态
data.open = !data.open;
this.$refs.parentTrees.store.nodesMap[data.id].expanded = !data.open;
},
给输入框的方法进行定义:就是给显示的数组进行赋值。
//选择所属菜单的方法 (点击输入框时触发 发送查询请求
async selectParentMenu(){
this.parentDialog.title="选择菜单信息"
this.parentDialog.visible = true;
//调用API连接后端的方法。
let res = await menuApi.getParentList();
if(res.success){
this.treeList = res.data;
}
},