vue使用拖拽方式创建结构树
时间:2023-01-24 10:49:01|栏目:vue|点击: 次
本文实例为大家分享了vue使用拖拽方式创建结构树的具体代码,供大家参考,具体内容如下
在页面中拖拽虚线框中的节点,创建向右的结构树,如下图
记录实现思路:
vueTree.vue
<template> <div class="container"> <div class="node-container"> <div v-for="(item, index) in nodeList" :key="index" class="source-node" draggable="true" @dragstart="dragStart(item)"> {{ item }} </div> </div> <div class="tree-container" @dragover="allowDrop" @drop="handleDrop"> <tree-node v-if="nodeData" ref="node" :nodeData="nodeData" @delete-node="deleteTree" /> </div> </div> </template> <script> import TreeNode from './treeNode.vue' import { Node } from './config.js' export default { name: 'vue-tree', components: { TreeNode }, // 后代节点无法获取节点数据,即无法独立创建节点,所以将祖先节点的创建节点方法暴露给后代节点 provide () { return { createNode: this.createNode } }, data () { return { nodeList: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], currNode: null, nodeData: null } }, methods: { // 开始拖拽,获取节点数据 dragStart (item) { this.currNode = item }, // 若未生成跟节点,则允许拖拽 allowDrop (event) { if (!this.nodeData) { event.preventDefault() } }, // 拖拽结束,生成节点 handleDrop () { if (!this.nodeData) { this.nodeData = this.createNode() } }, createNode () { let node = new Node(this.currNode) return node }, // 根节点删除,删除整个树 deleteTree () { this.nodeData = null } } } </script> <style lang="scss" scoped> .container { padding: 20px; width: calc(100% - 40px); height: calc(100% - 40px); .node-container { height: 100px; border: 1px dashed red; display: flex; .source-node { width: 50px; height: 30px; background: #fff; border: 1px solid blue; text-align: center; line-height: 30px; margin: 10px; cursor: pointer; } } .tree-container { height: calc(100% - 122px); margin-top: 20px; } } </style>
config,js
export class Node{ constructor(name){ this.name = name, this.children = [] } }
treeNode.vue
<template> <!-- 结构:最外层是node-inner,每个node-inner中有一个node与node-box,node存放当前节点,node-box存放当前节点的全部子节点,当前节点有几个子节点则node-box中就会有几个node-inner,以此循环 <node-inner> <node></node> <node-box> <node-inner> <node></node> <node-box>...</node-box> </node-inner> <node-inner> <node></node> <node-box>...</node-box> </node-inner> ... </node-box> </node-inner> --> <div class="node-inner"> <div class="node" :class="{ 'drag-over-node': isDragover }" @dragover="dragOver" @dragleave="dragLeave" @drop="nodeDrop"> <span class="name">{{nodeData.name}}</span> <span class="del" @click="deleteNode">删除</span> </div> <div v-show="nodeData.children.length > 0" class="node-box"> <tree-node v-for="(item,index) in nodeData.children" :key="index" :nodeData="item" @delete-node="deleteChild(index)" /> </div> </div> </template> <script> export default { name: 'tree-node', props: { nodeData: { type: Object, default: () => { } } }, // 获取祖先节点传递的数据 inject: ['createNode'], data () { return { isDragover: false } }, methods: { // 节点允许拖拽添加子节点 dragOver (event) { event.preventDefault() if (!this.isDragover) { this.isDragover = true } }, dragLeave () { if (this.isDragover) { this.isDragover = false } }, // 为节点添加子节点 nodeDrop () { let node = this.createNode() this.nodeData.children.push(node) this.isDragover = false }, // 删除当前节点,本质是交给父级删除子节点 deleteNode () { this.$emit("delete-node") }, // 接收删除子节点的指令并执行删除功能 deleteChild (index) { this.nodeData.children.splice(index, 1) } } } </script> <style lang="scss" scoped> .node { border: 1px solid orange; border-radius: 4px; position: relative; display: inline-flex; align-items: center; justify-content: space-between; background-color: #fff; height: 36px; padding: 0 12px 0 16px; line-height: 36px; margin-bottom: 10px; .name { font-size: 16px; margin-right: 12px; } .del { color: red; font-size: 12px; cursor: pointer; } &.drag-over-node { box-shadow: 6px 6px 12px rgba(106, 20, 134, 0.15); } } .node-box { display: inline-flex; flex-direction: column; .node-inner { margin-left: 80px; // 连接竖条 &:not(:last-child):before { position: absolute; left: -70px; top: 22px; border: 1px solid orange; content: ""; width: 8px; background-color: #fff; border-bottom-color: #fff; height: 100%; border-top-color: #fff; z-index: 3; } // 连接横条 &:after { left: -61px; width: 60px; content: ""; position: absolute; top: 14px; height: 8px; border: 1px solid orange; content: ""; background-color: #fff; border-right-color: #fff; border-left-color: #fff; z-index: 3; } // 最后一个竖条圆滑拐角 &:nth-last-child(2):before { border-bottom-left-radius: 6px; border-bottom-color: orange; } // 第一个横条拉长 &:first-child:after { left: -81px; width: 80px; z-index: 2; } } } .node-inner { position: relative; } </style>