START
- 番茄我,又又又来写点啥啦。最近又遇到,一级菜单以及二级菜单的展示效果。我第一反应就是可以转换成一个树状图展示,或许展示效果挺好的。然后,还要要求,可以拖动菜单,对菜单进行排序?我在想element-ui 树状图不是刚好可以实现嘛???写个文章记录一下。
- 如果从来没有在 vue 中使用 element-ui中的 树状图组件,建议看一下,我之前一篇写的 vue 中 element-ui Tree树形控件的使用,熟悉一下这个组件,写个demo,玩一玩就好,上手很快的。
一.整理需求
-
其实就是模仿微信服务号中的 自定义菜单。
-
效果见下图
需求.png
-
-
为了简化,我就考虑使用element-ui的树状图组件。
-
效果见下图
效果.png
-
-
如果想百分百还原,微信服务号配置 给出以下两种方案
- jquery (但是我司的项目是vue,我并不想因为拖动,专门引入一个jquery,所以暂时不考虑这个。)
- 用vue插件- vuedraggable-实现元素拖动 去实现
二.写个demo玩一玩?
- 写这个案例,请先自己创建vue项目,安装elememt-ui
1.我的树状图数据 (其实就是微信官方给的测试数据)
{
menu: {
button: [
{
type: "click",
name: "今日歌曲",
key: "V1001_TODAY_MUSIC",
sub_button: [
{
type: "view",
name: "难忘今宵",
url: "http://www.baidu.com/",
sub_button: [],
},
],
},
{
type: "click",
name: "歌手简介",
key: "V1001_TODAY_SINGER",
sub_button: [
{
type: "view",
name: "张宇",
url: "http://www.soso.com/",
sub_button: [],
},
{
type: "view",
name: "林俊杰",
url: "http://v.qq.com/",
sub_button: [],
},
],
},
{
name: "菜单",
sub_button: [
{
type: "view",
name: "搜索",
url: "http://www.soso.com/",
sub_button: [],
},
{
type: "view",
name: "视频",
url: "http://v.qq.com/",
sub_button: [],
},
{
type: "click",
name: "赞一下我们",
key: "V1001_GOOD",
sub_button: [],
},
{
type: "miniprogram",
name: "wxa",
url: "http://mp.weixin.qq.com",
appid: "wx286b93c14bbf93aa",
pagepath: "pages/lunar/index",
},
],
},
],
},
};
2.开始写树状图 (官网拖动的案例)
<template>
<div>
<el-tree
:data="data"
node-key="id"
default-expand-all
@node-drag-start="handleDragStart"
@node-drag-enter="handleDragEnter"
@node-drag-leave="handleDragLeave"
@node-drag-over="handleDragOver"
@node-drag-end="handleDragEnd"
@node-drop="handleDrop"
draggable
:allow-drop="allowDrop"
:allow-drag="allowDrag"
></el-tree>
</div>
</template>
<script>
export default {
data() {
return {
data: [
{
id: 1,
label: "一级 1",
children: [
{
id: 4,
label: "二级 1-1",
children: [
{
id: 9,
label: "三级 1-1-1",
},
{
id: 10,
label: "三级 1-1-2",
},
],
},
],
},
{
id: 2,
label: "一级 2",
children: [
{
id: 5,
label: "二级 2-1",
},
{
id: 6,
label: "二级 2-2",
},
],
},
{
id: 3,
label: "一级 3",
children: [
{
id: 7,
label: "二级 3-1",
},
{
id: 8,
label: "二级 3-2",
children: [
{
id: 11,
label: "三级 3-2-1",
},
{
id: 12,
label: "三级 3-2-2",
},
{
id: 13,
label: "三级 3-2-3",
},
],
},
],
},
],
defaultProps: {
children: "children",
label: "label",
},
};
},
methods: {
handleDragStart(node, ev) {
console.log("drag start", node);
},
handleDragEnter(draggingNode, dropNode, ev) {
console.log("tree drag enter: ", dropNode.label);
},
handleDragLeave(draggingNode, dropNode, ev) {
console.log("tree drag leave: ", dropNode.label);
},
handleDragOver(draggingNode, dropNode, ev) {
console.log("tree drag over: ", dropNode.label);
},
handleDragEnd(draggingNode, dropNode, dropType, ev) {
console.log("tree drag end: ", dropNode && dropNode.label, dropType);
},
handleDrop(draggingNode, dropNode, dropType, ev) {
console.log("tree drop: ", dropNode.label, dropType);
},
allowDrop(draggingNode, dropNode, type) {
if (dropNode.data.label === "二级 3-1") {
return type !== "inner";
} else {
return true;
}
},
allowDrag(draggingNode) {
return draggingNode.data.label.indexOf("三级 3-2-2") === -1;
},
},
};
</script>
3.因为官网的数据结构和我们的数据接口有些不一样,所以我们稍加修改一下
<template>
<div>
<el-tree
:data="data.menu.button"
:props="defaultProps"
node-key="id"
default-expand-all
@node-drag-start="handleDragStart"
@node-drag-enter="handleDragEnter"
@node-drag-leave="handleDragLeave"
@node-drag-over="handleDragOver"
@node-drag-end="handleDragEnd"
@node-drop="handleDrop"
draggable
></el-tree>
<!-- 上面注释掉的属性
:allow-drop="allowDrop"
:allow-drag="allowDrag"
-->
</div>
</template>
<script>
export default {
data() {
return {
// 修改一下数据,让数据符合我们的要求
data: {
menu: {
button: [
{
type: "click",
name: "今日歌曲",
key: "V1001_TODAY_MUSIC",
sub_button: [
{
type: "view",
name: "难忘今宵",
url: "http://www.baidu.com/",
sub_button: [],
},
],
},
{
type: "click",
name: "歌手简介",
key: "V1001_TODAY_SINGER",
sub_button: [
{
type: "view",
name: "张宇",
url: "http://www.soso.com/",
sub_button: [],
},
{
type: "view",
name: "林俊杰",
url: "http://v.qq.com/",
sub_button: [],
},
],
},
{
name: "菜单",
sub_button: [
{
type: "view",
name: "搜索",
url: "http://www.soso.com/",
sub_button: [],
},
{
type: "view",
name: "视频",
url: "http://v.qq.com/",
sub_button: [],
},
{
type: "click",
name: "赞一下我们",
key: "V1001_GOOD",
sub_button: [],
},
{
type: "miniprogram",
name: "wxa",
url: "http://mp.weixin.qq.com",
appid: "wx286b93c14bbf93aa",
pagepath: "pages/lunar/index",
},
],
},
],
},
},
// 修改一下啊显示规则,
// 1.label(显示的文字)对应 我们数据中的name
// 2.children(下级菜单名)对应 我们数据中的sub_button
defaultProps: {
children: "sub_button",
label: "name",
},
};
},
methods: {
handleDragStart(node, ev) {
console.log("drag start", node);
},
handleDragEnter(draggingNode, dropNode, ev) {
console.log("tree drag enter: ", dropNode.label);
},
handleDragLeave(draggingNode, dropNode, ev) {
console.log("tree drag leave: ", dropNode.label);
},
handleDragOver(draggingNode, dropNode, ev) {
console.log("tree drag over: ", dropNode.label);
},
handleDragEnd(draggingNode, dropNode, dropType, ev) {
console.log("tree drag end: ", dropNode && dropNode.label, dropType);
},
handleDrop(draggingNode, dropNode, dropType, ev) {
console.log("tree drop: ", dropNode.label, dropType);
},
// 这个地方就是拖动的限制条件 ,暂时注释掉
// allowDrop(draggingNode, dropNode, type) {
// if (dropNode.data.label === "二级 3-1") {
// return type !== "inner";
// } else {
// return true;
// }
// },
// allowDrag(draggingNode) {
// return draggingNode.data.label.indexOf("") === -1;
// },
},
};
</script>
- 上面的代码其实就是修改了一下要展示的数据,其实到这里,基本的功能都是实现了.
- 数据的展示
- 数据拖拽排序
- 但是这样肯定是不够好的,用户随意拖动,肯定不行呀!所以我在给他加点限制条件 o(╥﹏╥)o.
三.给拖动的树状图,进行限制
-
我的要求就是,
- 一级菜单,拖动排序,只能 一级菜单相互调换顺序.
- 二级菜单,拖动排序,只能在同一个一级菜单下面,二级菜单相互调换顺序.
-
经过几次修改,基本的最终的代码就是下面的代码了
-
演示的 视频
-
以下代码注释的非常详细,想理解代码的话,可以多看看,就可以理解了。或者直接复制也是可以的,最好自己本地跑一下,就可以看到效果了。
-
-
上代码
<template> <div class="end"> <h1 style="text-align: center;">树状图end</h1> <el-button :disabled="isdraggable" type="success" @click="openOne">一级菜单排序</el-button> <el-button :disabled="isdraggable" type="success" @click="openTwo">二级菜单排序</el-button> <div class="all"> <div class="left"> <!-- 属性解释: data 绑定的数据 node-key 节点唯一标识 props 数据匹配规则 default-expand-all 是否默认展开所有节点 expand-on-click-node 是否在点击节点的时候展开或者收缩节点, 默认值为 true,如果为 false,则只有点箭头图标的时候才会展开或者收缩节点。 node-drop 拖拽成功完成时触发的事件 draggable 是否开启拖拽节点功能 allow-drop 拖拽时判定目标节点能否被放置 allow-drag 判断节点能否被拖拽 node-click 节点点击事件 --> <el-tree :data="data.menu.button" node-key="name" :props="defaultProps" default-expand-all :expand-on-click-node="false" @node-drop="handleDrop" :draggable="isdraggable" :allow-drop="allowDrop" :allow-drag="allowDrag" @node-click="nodeClick" > <span class="custom-tree-node" slot-scope="{ node, data }"> <span>{{ node.label }} </span> <span v-if="node.childNodes.length !== 0"> <el-button type="text" size="mini" @click="() => append(node,data)">添加子菜单</el-button> </span> </span> </el-tree> <el-button type="warning" @click="open_set">保存修改</el-button> </div> <div class="right"> <el-form ref="form" :model="form" label-width="150px"> <el-form-item label="菜单名称"> <el-input v-model="form.name"></el-input> </el-form-item> <el-radio-group v-if="form.type==='click'||form.type==='view'||form.type==='miniprogram'" v-model="form.type" style="margin-left:70px" > <el-radio label="click">发送消息</el-radio> <el-radio label="view">跳转网页</el-radio> <el-radio label="miniprogram">跳转小程序</el-radio> </el-radio-group> <hr /> <div class="right_center"> <!-- 发送消息 --> <div v-if="form.type==='click'"> <el-form-item label="key值"> <el-input v-model="form.key"></el-input> </el-form-item> </div> <!--跳转网页 --> <div v-if="form.type==='view'"> <el-form-item label="跳转地址"> <el-input v-model="form.url"></el-input> </el-form-item> </div> <!-- 跳转小程序 --> <div v-if="form.type==='miniprogram'"> <el-form-item label="跳转地址"> <el-input v-model="form.url"></el-input> </el-form-item> <el-form-item label="小程序appid"> <el-input v-model="form.appid"></el-input> </el-form-item> <el-form-item label="小程序页面路径"> <el-input v-model="form.pagepath"></el-input> </el-form-item> </div> </div> <hr /> <el-button type="success" size="mini" @click="sub">保存</el-button> </el-form> </div> </div> </div> </template> <script> export default { name: "end", data() { const data = { menu: { button: [ { type: "click", name: "今日歌曲", key: "V1001_TODAY_MUSIC", sub_button: [ { type: "view", name: "难忘今宵", url: "http://www.baidu.com/", sub_button: [], }, ], }, { type: "click", name: "歌手简介", key: "V1001_TODAY_SINGER", sub_button: [ { type: "view", name: "张宇", url: "http://www.soso.com/", sub_button: [], }, { type: "view", name: "林俊杰", url: "http://v.qq.com/", sub_button: [], }, ], }, { name: "菜单", sub_button: [ { type: "view", name: "搜索", url: "http://www.soso.com/", sub_button: [], }, { type: "view", name: "视频", url: "http://v.qq.com/", sub_button: [], }, { type: "click", name: "赞一下我们", key: "V1001_GOOD", sub_button: [], }, { type: "miniprogram", name: "wxa", url: "http://mp.weixin.qq.com", appid: "wx286b93c14bbf93aa", pagepath: "pages/lunar/index", }, ], }, ], }, }; return { //这里其实就是一个简单的深拷贝 data: JSON.parse(JSON.stringify(data)), // 数据匹配规则 defaultProps: { children: "sub_button", label: "name", }, // 开启拖拽的控制变量 isdraggable: false, // 一级菜单修改one 二级菜单修改two isOpen: "", // 右侧表单 form: { name: "", }, }; }, methods: { openOne() { console.log("开启一级菜单修改"); this.isdraggable = true; this.isOpen = "one"; }, openTwo() { console.log("开启二级菜单修改"); this.isdraggable = true; this.isOpen = "two"; }, open_set() { console.log("保存修改", this.data.menu.button); this.isdraggable = false; this.isOpen = ""; }, append() { console.log("添加子菜单事件"); }, // 拖拽成功完成时触发的事件 handleDrop(draggingNode, dropNode, dropType, ev) { console.log(this.data); }, // 限制规则 // 拖动放下的限制 allowDrop(draggingNode, dropNode, type) { // draggingNode 拖动的元素 // dropNode 放下的元素 if (this.isOpen === "one") { // 当放下的节点 等级也是1 我们才能拖动放下 if (dropNode.level == 1) { return type !== "inner"; } else { return false; } } else if (this.isOpen === "two") { // 当放下的节点 等级也是2 而且 他们到属于同层级 我们才能拖动放下 if (dropNode.level == 2 && dropNode.parent === draggingNode.parent) { return type !== "inner"; } else { return false; } } else { console.log("不是一级也不是二级拖拽事件"); return false; } }, // 拖动 选中的限制 // 用来区分是 要拖动一级菜单,还是二级菜单,还是都不是 当 return true 才可以拖动 allowDrag(draggingNode) { if (this.isOpen === "one") { console.log("一级拖拽事件Drag", draggingNode.level); return draggingNode.level === 1; } else if (this.isOpen === "two") { console.log("二级拖拽事件allowDrag", draggingNode.level); return draggingNode.level === 2; } else { console.log("不是一级也不是二级拖拽事件Drag"); return false; } }, // 节点点击事件 nodeClick(a, b, c) { // 依次为:传递给 data 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 console.log(a, b, c); this.form = a; }, // 保存按钮事件 sub() { console.log("保存按钮事件", this.form); }, }, }; </script> <style scoped> /* a{ text-align: center; } */ .hello { width: 100%; height: 100%; } .all { display: flex; justify-content: space-around; } .left { margin-top: 20px; padding: 40px 10px; width: 400px; border: 3px solid #000; } .right { margin-top: 20px; width: 800px; height: 700px; border: 3px solid #000; } .right_center { width: 100%; height: 500px; } </style>
END
- 表达能力有限,可能叙述的,还是会有很多不足,欢迎指出.
- 由于只是写demo,所以命名规范啊,样式,都并没有太刻意去编写,只是记录实现的思路,看的不得劲,请见谅.
- 后期有新的需求,我再补充,比心 ,o(╥﹏╥)o.
网友评论