基于Vue实现可以拖拽的树形表格实例详解

因业务需求,需要一个树形表格,并且支持拖拽排序,任意未知插入,github搜了下,真不到合适的,大部分树形表格都没有拖拽功能,所以决定自己实现一个。这里分享一下实现过程,项目源代码请看github,插件已打包封装好,发布到npm上 

本博文会分为两部分,第一部分为使用方式,第二部分为实现方式

安装方式

npm i drag-tree-table --save-dev

使用方式

import dragTreeTable from ‘drag-tree-table‘

 模版写法

?


1

<dragTreeTable :data="treeData" :onDrag="onTreeDataChange"></dragTreeTable>

data参数示例

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

{

 lists: [

 {

 "id":40,

 "parent_id":0,

 "order":0,

 "name":"动物类",

 "open":true,

 "lists":[]

 },{

 "id":5,

 "parent_id":0,

 "order":1,

 "name":"昆虫类",

 "open":true,

 "lists":[

  {

  "id":12,

  "parent_id":5,

  "open":true,

  "order":0,

  "name":"蚂蚁",

  "lists":[]

  }

 ]

 },

 {

 "id":19,

 "parent_id":0,

 "order":2,

 "name":"植物类",

 "open":true,

 "lists":[]

 }

 ],

 columns: [

 {

 type: ‘selection‘,

 title: ‘名称‘,

 field: ‘name‘,

 width: 200,

 align: ‘center‘,

 formatter: (item) => {

  return ‘<a>‘+item.name+‘</a>‘

 }

 },

 {

 title: ‘操作‘,

 type: ‘action‘,

 width: 350,

 align: ‘center‘,

 actions: [

  {

  text: ‘查看角色‘,

  onclick: this.onDetail,

  formatter: (item) => {

   return ‘<i>查看角色</i>‘

  }

  },

  {

  text: ‘编辑‘,

  onclick: this.onEdit,

  formatter: (item) => {

   return ‘<i>编辑</i>‘

  }

  }

 ]

 },

 ]

}

onDrag在表格拖拽时触发,返回新的list

?


1

2

3

onTreeDataChange(lists) {

 this.treeData.lists = lists

}

到这里组件的使用方式已经介绍完毕

实现

?递归生成树姓结构(非JSX方式实现)
?实现拖拽排序(借助H5的dragable属性)
?单元格内容自定义展示

组件拆分-共分为四个组件

  dragTreeTable.vue是入口组件,定义整体结构

  row是递归组件(核心组件)

  clolmn单元格,内容承载

  space控制缩进

看一下dragTreeTable的结构

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

<template>

 <div class="drag-tree-table">

  <div class="drag-tree-table-header">

   <column

   v-for="(item, index) in data.columns"

   :width="item.width"

   :key="index" >

   {{item.title}}

   </column>

  </div>

  <div class="drag-tree-table-body" @dragover="draging" @dragend="drop">

   <row depth="0" :columns="data.columns"

   :model="item" v-for="(item, index) in data.lists" :key="index">

  </row>

  </div>

 </div>

</template>

看起来分原生table很像,dragTreeTable主要定义了tree的框架,并实现拖拽逻辑

filter函数用来匹配当前鼠标悬浮在哪个行内,并分为三部分,上中下,并对当前匹配的行进行高亮

resetTreeData当drop触发时调用,该方法会重新生成一个新的排完序的数据,然后返回父组件

下面是所有实现代码

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

<script>

 import row from ‘./row.vue‘

 import column from ‘./column.vue‘

 import space from ‘./space.vue‘

 document.body.ondrop = function (event) {

 event.preventDefault();

 event.stopPropagation();

 }

 export default {

 name: "dragTreeTable",

 components: {

  row,

  column,

  space

 },

 props: {

  data: Object,

  onDrag: Function

 },

 data() {

  return {

  treeData: [],

  dragX: 0,

  dragY: 0,

  dragId: ‘‘,

  targetId: ‘‘,

  whereInsert: ‘‘

  }

 },

 methods: {

  getElementLeft(element) {

  var actualLeft = element.offsetLeft;

  var current = element.offsetParent;

  while (current !== null){

   actualLeft += current.offsetLeft;

   current = current.offsetParent;

  }

  return actualLeft

  },

  getElementTop(element) {

  var actualTop = element.offsetTop;

  var current = element.offsetParent;

  while (current !== null) {

   actualTop += current.offsetTop;

   current = current.offsetParent;

  }

  return actualTop

  },

  draging(e) {

  if (e.pageX == this.dragX && e.pageY == this.dragY) return

  this.dragX = e.pageX

  this.dragY = e.pageY

  this.filter(e.pageX, e.pageY)

  },

  drop(event) {

  this.clearHoverStatus()

  this.resetTreeData()

  },

  filter(x,y) {

  var rows = document.querySelectorAll(‘.tree-row‘)

  this.targetId = undefined

  for(let i=0; i < rows.length; i++) {

   const row = rows[i]

   const rx = this.getElementLeft(row);

   const ry = this.getElementTop(row);

   const rw = row.clientWidth;

   const rh = row.clientHeight;

   if (x > rx && x < (rx + rw) && y > ry && y < (ry + rh)) {

   const diffY = y - ry

   const hoverBlock = row.children[row.children.length - 1]

   hoverBlock.style.display = ‘block‘

   const targetId = row.getAttribute(‘tree-id‘)

   if (targetId == window.dragId){

    this.targetId = undefined

    return

   }

   this.targetId = targetId

   let whereInsert = ‘‘

   var rowHeight = document.getElementsByClassName(‘tree-row‘)[0].clientHeight

   if (diffY/rowHeight > 3/4) {

    console.log(111, hoverBlock.children[2].style)

    if (hoverBlock.children[2].style.opacity !== ‘0.5‘) {

    this.clearHoverStatus()

    hoverBlock.children[2].style.opacity = 0.5

    }

    whereInsert = ‘bottom‘

   } else if (diffY/rowHeight > 1/4) {

    if (hoverBlock.children[1].style.opacity !== ‘0.5‘) {

    this.clearHoverStatus()

    hoverBlock.children[1].style.opacity = 0.5

    }

    whereInsert = ‘center‘

   } else {

    if (hoverBlock.children[0].style.opacity !== ‘0.5‘) {

    this.clearHoverStatus()

    hoverBlock.children[0].style.opacity = 0.5

    }

    whereInsert = ‘top‘

   }

   this.whereInsert = whereInsert

   }

  }

  },

  clearHoverStatus() {

  var rows = document.querySelectorAll(‘.tree-row‘)

  for(let i=0; i < rows.length; i++) {

   const row = rows[i]

   const hoverBlock = row.children[row.children.length - 1]

   hoverBlock.style.display = ‘none‘

   hoverBlock.children[0].style.opacity = 0.1

   hoverBlock.children[1].style.opacity = 0.1

   hoverBlock.children[2].style.opacity = 0.1

  }

  },

  resetTreeData() {

  if (this.targetId === undefined) return

  const newList = []

  const curList = this.data.lists

  const _this = this

  function pushData(curList, needPushList) {

   for( let i = 0; i < curList.length; i++) {

   const item = curList[i]

   var obj = _this.deepClone(item)

   obj.lists = []

   if (_this.targetId == item.id) {

    const curDragItem = _this.getCurDragItem(_this.data.lists, window.dragId)

    if (_this.whereInsert === ‘top‘) {

    curDragItem.parent_id = item.parent_id

    needPushList.push(curDragItem)

    needPushList.push(obj)

    } else if (_this.whereInsert === ‘center‘){

    curDragItem.parent_id = item.id

    obj.lists.push(curDragItem)

    needPushList.push(obj)

    } else {

    curDragItem.parent_id = item.parent_id

    needPushList.push(obj)

    needPushList.push(curDragItem)

    }

   } else {

    if (window.dragId != item.id)

    needPushList.push(obj)

   }

   if (item.lists && item.lists.length) {

    pushData(item.lists, obj.lists)

   }

   }

  }

  pushData(curList, newList)

  this.onDrag(newList)

  },

  deepClone (aObject) {

  if (!aObject) {

   return aObject;

  }

  var bObject, v, k;

  bObject = Array.isArray(aObject) ? [] : {};

  for (k in aObject) {

   v = aObject[k];

   bObject[k] = (typeof v === "object") ? this.deepClone(v) : v;

  }

  return bObject;

  },

  getCurDragItem(lists, id) {

  var curItem = null

  var _this = this

  function getchild(curList) {

   for( let i = 0; i < curList.length; i++) {

   var item = curList[i]

   if (item.id == id) {

    curItem = JSON.parse(JSON.stringify(item))

    break

   } else if (item.lists && item.lists.length) {

    getchild(item.lists)

   }

   }

  }

  getchild(lists)

  return curItem;

  }

 }

 }

</script>

row组件核心在于递归,并注册拖拽事件,v-html支持传入函数,这样可以实现自定义展示,渲染数据时需要判断是否有子节点,有的画递归调用本身,并传入子节点数据

结构如下

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

<template>

  <div class="tree-block" draggable="true" @dragstart="dragstart($event)"

   @dragend="dragend($event)">

   <div class="tree-row"

    @click="toggle"

    :tree-id="model.id"

    :tree-p-id="model.parent_id">

    <column

     v-for="(subItem, subIndex) in columns"

     v-bind:class="‘align-‘ + subItem.align"

     :field="subItem.field"

     :width="subItem.width"

     :key="subIndex">

     <span v-if="subItem.type === ‘selection‘">

      <space :depth="depth"/>

      <span v-if = "model.lists && model.lists.length" class="zip-icon" v-bind:class="[model.open ? ‘arrow-bottom‘ : ‘arrow-right‘]">

      </span>

      <span v-else class="zip-icon arrow-transparent">

      </span>

      <span v-if="subItem.formatter" v-html="subItem.formatter(model)"></span>

      <span v-else v-html="model[subItem.field]"></span>

     </span>

     <span v-else-if="subItem.type === ‘action‘">

      <a class="action-item"

       v-for="(acItem, acIndex) in subItem.actions"

       :key="acIndex"

       type="text" size="small"

       @click.stop.prevent="acItem.onclick(model)">

       <i :class="acItem.icon" v-html="acItem.formatter(model)"></i> 

      </a>

     </span>

     <span v-else-if="subItem.type === ‘icon‘">

       {{model[subItem.field]}}

     </span>

     <span v-else>

      {{model[subItem.field]}}

     </span>

    </column>

    <div class="hover-model" style="display: none">

     <div class="hover-block prev-block">

      <i class="el-icon-caret-top"></i>

     </div>

     <div class="hover-block center-block">

      <i class="el-icon-caret-right"></i>

     </div>

     <div class="hover-block next-block">

      <i class="el-icon-caret-bottom"></i>

     </div>

    </div>

   </div>

   <row

    v-show="model.open"

    v-for="(item, index) in model.lists"

    :model="item"

    :columns="columns"

    :key="index"

    :depth="depth * 1 + 1"

    v-if="isFolder">

   </row>

  </div>

  

 </template>

 <script>

 import column from ‘./column.vue‘

 import space from ‘./space.vue‘

 export default {

  name: ‘row‘,

  props: [‘model‘,‘depth‘,‘columns‘],

  data() {

   return {

    open: false,

    visibility: ‘visible‘

   }

  },

  components: {

   column,

   space

  },

  computed: {

   isFolder() {

    return this.model.lists && this.model.lists.length

   }

  },

  methods: {

   toggle() {

    if(this.isFolder) {

     this.model.open = !this.model.open

    }

   },

   dragstart(e) {

    e.dataTransfer.setData(‘Text‘, this.id);

    window.dragId = e.target.children[0].getAttribute(‘tree-id‘)

    e.target.style.opacity = 0.2

   },

   dragend(e) {

    e.target.style.opacity = 1;

    

   }

  }

 }

clolmn和space比较简单,这里就不过多阐述

上面就是整个实现过程,组件在chrome上运行稳定,因为用H5的dragable,所以兼容会有点问题,后续会修改拖拽的实现方式,手动实现拖拽

 
带你诱人的Ruby,手把手一起做Web app之 ruby on rails web开发学习实录
爱创课堂张容铭js高级课程backbone企业级实战教程基础+实战图片网项目视频
爱创课堂张容铭js高级课程Vue.js企业级实战教程基础+实战团购项目视频课程  ...2
爱创课堂张容铭js高级课程Angular企业级实战教程(基础+实战后台管理系统)课程
小码哥jQuery实战带你搞定旋转木马特效(赵延明老师)
web前后端全漏洞原理+攻击手段+测试方法+预防措施精讲视频教程
mock.js-无需等待,让前端独立于后端进行开发--NodeJS搭建服务端调试接口实战京东...  ...2
web前端高级全栈工程师高薪特训英班开完整版(价值381元)
潭州全栈web前端:从原生javescript到jQuery交互实战视频课程
T3 - 构建大型 Web 应用的 JavaScript 框架视频教程
高手之路!网页设计师应该知道的960网格系统的CSS架构
一套精美通用系统后台管理UI模板系统框架结构讲解演示地址、详细代码 
打造全栈工程师之前后端分离的网上商城实战及系统架构解析
利用seajs实现前端模块化开发提示框项目实战
利用requirejs实现前端模块化开发hao123项目实战
FIS3一个强大的前端工程构建工具的初级超级实用功能讲解视频教程
用Gulp构建你的前端项目_前端工程化工具--gulp视频教程
开启大数据时代最后一公里-精通D3.js 交互式数据可视化高级编程视频   ...2
开启大数据时代最后一公里-精通Highchars大数据可视化视频教程
AngularJS仿拉勾网WebApp 开发移动端单页应用

原文地址:https://www.cnblogs.com/zorasia/p/10012832.html

时间: 2024-11-05 22:45:03

基于Vue实现可以拖拽的树形表格实例详解的相关文章

WPF中元素拖拽的两个实例

原文:WPF中元素拖拽的两个实例 今天结合之前做过的一些拖拽的例子来对这个方面进行一些总结,这里主要用两个例子来说明在WPF中如何使用拖拽进行操作,元素拖拽是一个常见的操作,第一个拖拽的例子是将ListBox中的子元素拖拽到ListView的某一个节点,从而将该子元素作为当前节点的子节点.第二个例子就是将ListView的某一项拖拽到另外一项上从而使两个子项位置互换,这两个例子的原理类似,实现细节上有所差别,下面就具体分析一下这些细节. DEMO1 一 示例截图 图一 示例一截图 二 重点原理分

Spring基于事件驱动模型的订阅发布模式代码实例详解

代码下载地址:http://www.zuidaima.com/share/1791499571923968.htm 原文:Spring基于事件驱动模型的订阅发布模式代码实例详解 事件驱动模型简介 事件驱动模型也就是我们常说的观察者,或者发布-订阅模型:理解它的几个关键点: 首先是一种对象间的一对多的关系:最简单的如交通信号灯,信号灯是目标(一方),行人注视着信号灯(多方): 当目标发送改变(发布),观察者(订阅者)就可以接收到改变: 观察者如何处理(如行人如何走,是快走/慢走/不走,目标不会管的

Android中实现java与PHP服务器(基于新浪云免费云平台)http通信详解

Android中实现java与PHP服务器(基于新浪云免费云平台)http通信详解 (本文转自: http://blog.csdn.net/yinhaide/article/details/44756989) 前言:现在很多APP都需要云的功能,也就是通过网络与服务器交换数据.有的采用tcp/ip协议,但是你必须拥有一个固定ip的服务器,可以购买阿里云服务器之类的,就是贵了点.如果只是个人的小应用的的话可以采用新浪云平台这种免费的服务器,采用的协议是http协议,具体实现方式如下: 方式一.在线

Vue 实例详解与生命周期

Vue 实例详解与生命周期 Vue 的实例是 Vue 框架的入口,其实也就是前端的 ViewModel,它包含了页面中的业务逻辑处理.数据模型等,当然它也有自己的一系列的生命周期的事件钩子,辅助我们进行对整个 Vue 实例生成.编译.挂着.销毁等过程进行 js 控制. Vue 实例初始化的选项配置对象详解 前面我们已经用了很多次 new Vue({...})的代码,而且 Vue 初始化的选项都已经用了data.methods.el.computedd等,估计您看到这里时,应该已经都明白了他们的作

vue draggable 火狐拖拽搜索问题

最近在使用vuedraggable做导航时候,谷歌拖拽是没问题的,但是在火狐测试时候,拖拽时候是可以成功,但是火狐还是打开了一个新的tab,并且搜索了,一开始想着是阻止默认行为,但是在@end时间中阻止了默认行为,使用vue的.prevent.stop也是不行, 后来各种搜索后在这里才找到答案 传送 我是在created中添加了 created() { document.body.ondrop = function (event) { event.preventDefault(); event.

【05】Vue 之 实例详解与生命周期

Vue的实例是Vue框架的入口,其实也就是前端的ViewModel,它包含了页面中的业务逻辑处理.数据模型等,当然它也有自己的一系列的生命周期的事件钩子,辅助我们进行对整个Vue实例生成.编译.挂着.销毁等过程进行js控制. 5.1. Vue实例初始化的选项配置对象详解 前面我们已经用了很多次 new Vue({...})的代码,而且Vue初始化的选项都已经用了data.methods.el.computedd等,估计您看到这里时,应该已经都明白了他们的作用,我们就详细讲解一下他们的使用情况.更

Vue入门系列(五)Vue实例详解与生命周期

[入门系列] [本文转自] http://www.cnblogs.com/fly_dragon Vue的实例是Vue框架的入口,其实也就是前端的ViewModel,它包含了页面中的业务逻辑处理.数据模型等,当然它也有自己的一系列的生命周期的事件钩子,辅助我们进行对整个Vue实例生成.编译.挂着.销毁等过程进行js控制. 5.1. Vue实例初始化的选项配置对象详解 前面我们已经用了很多次 new Vue({...})的代码,而且Vue初始化的选项都已经用了data.methods.el.comp

基于Nginx搭建Web服务器及虚拟主机相关配置详解

随着计算机与Internet技术的高速发展,各种各样的Web站点也就成为面向用户的中坚力量.在各种网站服务器软件中.除了Apache外,还有一款轻量级的HTTP服务器软件--Nginx. 基于Apache搭建Web服务器可以参考博文:基于Apache搭建Web服务器详解 一.Nginx服务简介 Nginx由俄罗斯的lgor Sysoev开发,专为性能优化而开发,其最知名的优点就是它的稳定性和低系统资源消耗.以及对HTTP并发连接的高处立能力(单台物理服务器可支持30000~50000个并发请求)

Vue中实现拖拽

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" conte