美文网首页
vue 中 element-ui Tree树形控件的拖动排序

vue 中 element-ui Tree树形控件的拖动排序

作者: lazy_tomato | 来源:发表于2020-09-06 16:21 被阅读0次

START

  • 番茄我,又又又来写点啥啦。最近又遇到,一级菜单以及二级菜单的展示效果。我第一反应就是可以转换成一个树状图展示,或许展示效果挺好的。然后,还要要求,可以拖动菜单,对菜单进行排序?我在想element-ui 树状图不是刚好可以实现嘛???写个文章记录一下。
  • 如果从来没有在 vue 中使用 element-ui中的 树状图组件,建议看一下,我之前一篇写的 vue 中 element-ui Tree树形控件的使用,熟悉一下这个组件,写个demo,玩一玩就好,上手很快的。

一.整理需求

  • 其实就是模仿微信服务号中的 自定义菜单。

    • 效果见下图


      需求.png
  • 为了简化,我就考虑使用element-ui的树状图组件。

    • 效果见下图


      效果.png
  • 如果想百分百还原,微信服务号配置 给出以下两种方案

二.写个demo玩一玩?

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 }}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</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.

相关文章

网友评论

      本文标题:vue 中 element-ui Tree树形控件的拖动排序

      本文链接:https://www.haomeiwen.com/subject/ffmcektx.html