vue3: vuedraggable 的使用方法(正常数据的基本使用与树结构数据递归使用)

目录

效果展示

[第一章 准备](#第一章 准备)

[第二章 源代码](#第二章 源代码)

[2.1 基本使用](#2.1 基本使用)

[2.2 树结构数据递归使用](#2.2 树结构数据递归使用)


效果展示

  • 正常数据拖拽
  • 树结构数据拖拽 ------说明:自定义拖拽树,添加数据展开收起**,**拖拽时非同层级不可拖拽,使用灰色区分,跨层拖拽不生效,并使用递归组件实现。

第一章 准备

官方文档:vuedraggable - npm

安装:

javascript 复制代码
yarn add vuedraggable@4.1.0
yarn add vuedraggable-es@4.1.0

第二章 源代码

(为了方便大家,小编直接上源码,里面附带讲解,大家复制即可使用)

2.1 基本使用

  • 封装组件:

js------

javascript 复制代码
<script setup name="NormalDraggable">
import draggable from "vuedraggable";
import { reactive } from "vue";

const props = defineProps({
  originData: {
    type: Array,
    default: () => {
      return [];
    },
  },
});

const emit = defineEmits(["handleChange"]);

const state = reactive({
  list: props.originData.map((item) => {
    return {
      ...item,
    };
  }),
});

//拖拽开始的事件
const onStart = () => {
  console.log("开始拖拽");
};

//拖拽结束的事件
const onEnd = () => {
  // 拖拽结束后重新处理数据,参数:原数据,变化的数据
  emit("handleChange", state.list);
};
</script>

html------

html 复制代码
<template>
  <draggable
    :list="state.list"
    itemKey="id"
    animation="0"
    @start="onStart"
    @end="onEnd"
  >
    <template #item="{ element }">
      <div>
        <div class="draggable_item">
          <div class="draggable_item-left">
            <span>{{ element.fieldName }}</span>
          </div>
          <div class="draggable_item-right">
            <div class="example">
              <span>{{ element.exampleValue }}</span>
            </div>
          </div>
        </div>
      </div>
    </template>
  </draggable>
</template>

css------

javascript 复制代码
<style lang="less" scoped>
.draggable_item {
  width: 100%;
  padding: 0px 12px;
  min-height: 40px;
  background: #ffffff;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border: 1px solid #e0e6eb;
  box-shadow: 0px 2px 0px 0px rgba(0, 0, 0, 0.02);
  border-radius: 2px;
  margin-bottom: 4px;

  .draggable_item-left {
    display: flex;
    align-items: center;
    max-width: 260px;

    .icon {
      margin-right: 10px;
      width: 14px;
    }
  }

  .draggable_item-right {
    display: flex;
    align-items: center;
    .example {
      color: rgba(0, 0, 0, 0.25);
      margin-right: 10px;
      max-width: 184px;
    }
  }
}
</style>
  • 使用:
html 复制代码
<script setup name="NormalDraggable">
import NormalDraggable from "./NormalDraggable/index.vue";

const originDataList = ref([
  {
    exampleValue: "1",
    fieldName: "1层1号",
    id: "41cf48fb5c7546758b8ea679c7fc40b3",
    parentId: "",
  },
  {
    exampleValue: "123",
    fieldName: "1层2号",
    id: "57e6b0b1f0f14dff86deeb9ca2937772",
    parentId: "",
  },
  {
    exampleValue: '{a:"123"}',
    fieldName: "1层3号",
    id: "57e6b0b1f0f14dff86deeb9ca2937773",
    parentId: "",
  },
  {
    exampleValue: "1",
    fieldName: "1层4号",
    id: "b83e93dd599d40e6b58b24c0f3cf1cc8",
    parentId: "",
  },
  {
    exampleValue: "1",
    fieldName: "1层5号",
    id: "b83e93dd599d40e6b58b24c0f3cf1cd8",
    parentId: "",
  },
  {
    exampleValue: "1",
    fieldName: "1层6号",
    id: "b83e93dd599d40e6b58b24c0f3cf1cf8",
    parentId: "",
  },
]);

const handleChangeList = (data) => {
  console.log("data", data);
};
</script>

<NormalDraggable
   :originData="originDataList"
   @handleChange="handleChangeList"
/>

2.2 树结构数据递归使用

  • 封装拖拽组件:

js------

javascript 复制代码
<script> 
// 由于是递归组件,小编的demo中配置不是特别完全,所以需要将该组件名字向外暴露,但是需要区分,setup语法糖
export default {
  name: "TreeDraggable",
};
</script>
<script setup name="TreeDraggable">
import {
  CaretRightOutlined,
  CaretDownOutlined,
  DragOutlined,
} from "@ant-design/icons-vue"; // 小编使用的时antd小图标需要额外引入

import draggable from "vuedraggable"; // 引入拖拽组件

const props = defineProps({
  // 整个数据对象
  originData: {
    type: Array,
    default: () => {
      return [];
    },
  },
  // 数据对象中的子节点
  childrenData: {
    type: Array,
    default: () => {
      return [];
    },
  },
});

const emit = defineEmits([
  "handleChange",
  "startHandleChange",
  "initHandleChange",
]);

//拖拽开始的事件
const onStart = (event) => {
  const { element } = event.item.__draggable_context;
  // 拖拽开始时处理数据(向父组件发送消息)
  emit("startHandleChange", element.level);
};

// 递归组件中介函数
const startHandleChange = (level) => {
  emit("startHandleChange", level);
};

//拖拽结束的事件
const onEnd = () => {
  // 拖拽结束后重新处理数据,参数:原数据,变化的数据(向父组件发送消息)
  emit("handleChange", props.originData, props.childrenData);
};

// 递归组件中介函数
const handleChange = (data1, data2) => {
  emit("handleChange", data1, data2);
};

// 侧边小图标点击事件
const showTreeInfo = (ele) => {
  if (ele.children) {
    ele.open = !ele.open;
  }
};
</script>

html------

html 复制代码
<template>
  <draggable
    :list="childrenData" // 传入的拖拽数据
    itemKey="id" // 需要传入id防止会跨层拖拽
    animation="0"
    @start="onStart" // 拖拽开始事件
    @end="onEnd" // 拖拽结束事件
  >
    // 下面是拖拽组件的基本使用结构
    <template #item="{ element }"> // 单条拖拽元素数据,即draggable组件的插槽
      <div> //自定义每一条拖拽数据的展示结构
        <div
          class="draggable_item"
          :style="{
            backgroundColor: element.isdraggable // 通过isdraggable区分是否能拖拽的颜色
              ? '#ffffff'
              : 'rgba(0, 0, 0, 0.04)',
            color: element.isdraggable
              ? 'rgba(0, 0, 0, 0.88)'
              : 'rgba(0, 0, 0, 0.25)',
          }"
        >
          <div
            class="draggable_item-left"
            :style="{ marginLeft: `${(element.level - 1) * 16}px` }" // 构造一下树结构
          >
            <div class="icon" @click="showTreeInfo(element)"> // 通过点击小图标控制数据的展开与隐藏
              <div v-if="element.children.length"> // 如果没子级则不展示小图标
                <CaretRightOutlined v-if="!element.open" /> // open控制小图标
                <CaretDownOutlined v-else /> 
              </div>
            </div>
            <div class="name">
              <span>{{ element.fieldName }}</span>
            </div>
          </div>
          <div
            class="draggable_item-right"
            :style="{
              color: element.isdraggable
                ? 'rgba(0, 0, 0, 0.45)'
                : 'rgba(0, 0, 0, 0.25)',
            }"
          >
            <div class="example">
              <span>{{ element.exampleValue }}</span>
            </div>
            <div>
              <DragOutlined />
            </div>
          </div>
        </div>
        <TreeDraggable // 递归组件传入对应的方法以及参数
          :originData="originData"
          :childrenData="element.children"
          @handleChange="handleChange"
          @startHandleChange="startHandleChange"
          v-if="element.children && element.open"
        />
      </div>
    </template>
  </draggable>
</template>

css(与2.1基本使用的样式一致)

  • 使用
html 复制代码
<script name="App" setup>
import TreeDraggable from "./TreeDraggable/index.vue"; // 引入主键
import { onMounted, ref } from "vue";
let originData = ref([ // 数据格式
  {
    children: [],
    exampleValue: "1",
    fieldName: "1层1号",
    id: "41cf48fb5c7546758b8ea679c7fc40b3",
    parentId: "",
  },
  {
    children: [
      {
        children: [
          {
            children: [],
            exampleValue: "示例值",
            fieldName: "2号2层1号",
            id: "7647910dc6d94a51ac5311392ba290d3",
            parentId: "7647910dc6d94a51ac5311382ba990d3",
          },
          {
            children: [],
            exampleValue: "示例值",
            fieldName: "2号2层2号",
            id: "7647910dc6d94a51ac5311382ba290d4",
            parentId: "7647910dc6d94a51ac5311382ba990d3",
          },
        ],
        exampleValue: "示例值",
        fieldName: "2号1层1",
        id: "7647910dc6d94a51ac5311382ba990d3",
        parentId: "57e6b0b1f0f14dff86deeb9ca2937772",
      },
    ],
    exampleValue: "123",
    fieldName: "1层2号",
    id: "57e6b0b1f0f14dff86deeb9ca2937772",
    parentId: "",
  },
  {
    children: [
      {
        children: [
          {
            children: [],
            exampleValue: "示例值",
            fieldName: "3号2层1号",
            id: "7647910dc6d94a51ac5311382ba294d3",
            parentId: "7647910dc6d94a51ac5311382ba291d3",
          },
          {
            children: [],
            exampleValue: "示例值",
            fieldName: "3号2层2号",
            id: "7647910dc6d94a51ac5311382ba290d4",
            parentId: "7647910dc6d94a51ac5311382ba291d3",
          },
        ],
        exampleValue: "示例值",
        fieldName: "3号1层1",
        id: "7647910dc6d94a51ac5311382ba291d3",
        parentId: "57e6b0b1f0f14dff86deeb9ca2937773",
      },
      {
        children: [],
        exampleValue: "示例值",
        fieldName: "3号1层2",
        id: "7647910dc6d94a51ac5311382ba290d3",
        parentId: "57e6b0b1f0f14dff86deeb9ca2937773",
      },
    ],
    exampleValue: '{a:"123"}',
    fieldName: "1层3号",
    id: "57e6b0b1f0f14dff86deeb9ca2937773",
    parentId: "",
  },
  {
    children: [],
    exampleValue: "1",
    fieldName: "1层4号",
    id: "b83e93dd599d40e6b58b24c0f3cf1cc8",
    parentId: "",
  },
  {
    children: [],
    exampleValue: "1",
    fieldName: "1层5号",
    id: "b83e93dd599d40e6b58b24c0f3cf1cd8",
    parentId: "",
  },
  {
    children: [],
    exampleValue: "1",
    fieldName: "1层6号",
    id: "b83e93dd599d40e6b58b24c0f3cf1cf8",
    parentId: "",
  },
]);

//下面的方法基本上都是使用递归实现的
// 初始化数据,添加level(层级)、serial(序号)、isdraggable(是否可拖拽)、open(控制数据是否展开):要进行如下初始化的原因是正常开发时后端是不会这么给我们前端返回的,我们需要处理数据
const initTreeData = (nodes, level) => {
  for (let i = 0; i < nodes.length; i++) { // 遍历数据
    const node = nodes[i];
    node.serial = i + 1; // 额外添加以下字段
    node.level = level;
    node.isdraggable = true;
    node.open = false;
    // 如果节点有子节点,递归遍历子节点,并将层数加1
    if (node.children) {
      initTreeData(node.children, level + 1);
    }
  }
  return nodes;
};

onMounted(() => {
  originData.value = initTreeData(originData.value, 1);
});

// 部分数据重置(添加重置的方法原因是拖拽后已经展开的数据(open:true)不再收起设置为(open:false))
const restTreeData = (nodes, level) => {
  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i];
    node.serial = i + 1;
    node.level = level;
    node.isdraggable = true;
    // 如果节点有子节点,递归遍历子节点,并将层数加1
    if (node.children) {
      restTreeData(node.children, level + 1);
    }
  }
  return nodes;
};

// 开始拖动时处理节点是否可拖拽,需要样式区分
const dealAddData = (nodes, level) => {
  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i];
    if (node.level === level) {
      node.isdraggable = true;
    } else {
      node.isdraggable = false;
    }
    // 如果节点有子节点,递归遍历子节点,并将层数加1
    if (node.children) {
      dealAddData(node.children, level);
    }
  }
  return nodes;
};

// 拖动后处理变化的数据(拖拽后需要对拖拽的chilren节点进行初始化,这里有一点优化,由于只能同层级拖拽,所以该节点下的子节点数据是没有变化的,只需要处理当前层级的即可)
const dealChangeData = (nodes, childrenNode, parentId) => {
  if (parentId === "") {
    nodes = childrenNode;
  } else {
    for (let i = 0; i < nodes.length; i++) {
      const node = nodes[i];
      if (node.parentId === parentId) {
        node.children = childrenNode;
      }
      // 如果节点有子节点,递归遍历子节点,并将层数加1
      if (node.children) {
        dealAddData(node.children, childrenNode, parentId);
      }
    }
  }
  return nodes;
};

// 结束拖动时处理数据
const handleChange = (data1, data2) => {
  const parentId = data2[0].parentId;
  const data = dealChangeData(data1, data2, parentId); // 处理数据
  originData.value = restTreeData(data, 1); // 初始化数据
};

// 开始拖动时修改数据:添加字段控制样式
const startHandleChange = (level) => {
  originData.value = dealAddData(originData.value, level);
};
</script>

<template>
  <div class="row">
    <div style="min-height: 500px">
      <TreeDraggable // 使用组件
        :originData="originData"
        :childrenData="originData"
        @handleChange="handleChange"
        @startHandleChange="startHandleChange"
      />
    </div>
  </div>
</template>

<style scoped></style>

如果数据格式中所有children都是\[\],也可以与基本使用的样式一样:

javascript 复制代码
let originData = ref([
  {
    children: [],
    exampleValue: "1",
    fieldName: "1层1号",
    id: "41cf48fb5c7546758b8ea679c7fc40b3",
    parentId: "",
  },
  {
    children: [],
    exampleValue: "123",
    fieldName: "1层2号",
    id: "57e6b0b1f0f14dff86deeb9ca2937772",
    parentId: "",
  },
  {
    children: [],
    exampleValue: '{a:"123"}',
    fieldName: "1层3号",
    id: "57e6b0b1f0f14dff86deeb9ca2937773",
    parentId: "",
  },
  {
    children: [],
    exampleValue: "1",
    fieldName: "1层4号",
    id: "b83e93dd599d40e6b58b24c0f3cf1cc8",
    parentId: "",
  },
  {
    children: [],
    exampleValue: "1",
    fieldName: "1层5号",
    id: "b83e93dd599d40e6b58b24c0f3cf1cd8",
    parentId: "",
  },
  {
    children: [],
    exampleValue: "1",
    fieldName: "1层6号",
    id: "b83e93dd599d40e6b58b24c0f3cf1cf8",
    parentId: "",
  },
]);
相关推荐
陈随易10 分钟前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
Dream-Y.ocean2 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
Csvn3 小时前
性能监控
前端
Highcharts.js3 小时前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn4 小时前
构建工具
前端
FRing034 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷5 小时前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen6 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒7 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫7 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库