转自:https://blog.csdn.net/If_CannewBoyFriend/article/details/108072059
拖拽树主要是绑定 mousedown mouseup 事件 主要参考ztree官网 http://www.treejs.cn/v3/main.php#_zTreeInfo
树结构样式参考 https://github.com/tower1229/Vue-Giant-Tree
下载地址 https://download.csdn.net/download/If_CannewBoyFriend/12720563
存在问题:拖拽时候显示的信息不随鼠标移动,一直在页面底部,
解决方案:在bindMouseDown 方法中显示的信息添加绝对定位,父级相对定位
<span class='dom_tmp' style='position:absolute'>" +
this.targetNode.name +
</span>
table拖拽
bindDom () {
this.$nextTick(() => {
$('.dragBox').bind('mousedown', this.bindMouseDown)
})
},
bindMouseDown (e) {
const target = e.target
const id = $(target).parent('.dragbody').attr('dataID')
console.log(id)
if (target != null && id) {
const doc = $(document); const target = $(target)
const docScrollTop = doc.scrollTop()
const docScrollLeft = doc.scrollLeft()
const obj = this.tableData.filter(item => item.id === id)
if (obj && obj.length > 0) {
this.targetNode = obj[0]
}
console.log(this.targetNode)
const curDom = $("<span class='dom_tmp'>" + this.targetNode.name + '</span>')
if (this.curTmpTarget) this.curTmpTarget.remove()
curDom.appendTo('body')
curDom.css({
'top': (e.clientY + docScrollTop + 3) + 'px',
'left': (e.clientX + docScrollLeft + 3) + 'px'
})
this.curTarget = target
this.curTmpTarget = curDom
doc.bind('mousemove', this.bindMouseMove)
doc.bind('mouseup', this.bindMouseUp)
}
if (e.preventDefault) {
e.preventDefault()
}
},
bindMouseMove (e) {
this.noSel()
const doc = $(document)
const docScrollTop = doc.scrollTop()
const docScrollLeft = doc.scrollLeft()
const tmpTarget = this.curTmpTarget
if (tmpTarget) {
tmpTarget.css({
'top': (e.clientY + docScrollTop + 3) + 'px',
'left': (e.clientX + docScrollLeft + 3) + 'px'
})
}
return false
},
bindMouseUp (e) {
const doc = $(document)
doc.unbind('mousemove', this.bindMouseMove)
doc.unbind('mouseup', this.bindMouseUp)
// const target = this.curTarget
const tmpTarget = this.curTmpTarget
if (tmpTarget) tmpTarget.remove()
/* if ($(e.target).parents('#treeDemo').length === 0) {
console.log(target)
} */
},
noSel () {
/* try {
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
} catch(e){} */
},
// 拖动添加数据 用于捕获 zTree 上鼠标按键松开后的事件回调函数
onMouseUpFun (e, treeId, treeNode) {
try {
const target = this.curTarget
const tmpTarget = this.curTmpTarget
if (!target) return
const parentNode = treeNode
if (tmpTarget) tmpTarget.remove()
this.curTarget = null
this.curTmpTarget = null
if (parentNode) {
return new Promise((resolve, reject) => {
addMediaTree(this.targetNode).then(response => {
if (response && response.errorMsg === '成功') {
this.$message({
message: '添加成功!',
type: 'success'
})
// 添加成功之后 更新树
const dadd = response.data
// 树展示的属性用的name 我的接口返回的是title 处理数据
dadd['name'] = dadd.title
var nodes = this.ztreeObj.addNodes(parentNode, dadd, true)
console.log(nodes)
} else {
this.$message({
message: '添加失败!',
type: 'error'
})
}
resolve(response)
}).catch(error => {
reject(error)
})
})
} else {
console.log('数据错误')
}
} catch (e) {
console.log(e)
}
},
右键方法::
// 右键显示栏目 event, treeId, treeNode
openMenu (e, treeId, object) {
/* let hasAuthB = false
if (this.HasAuth('/assettree/add') || this.HasAuth('/assettree/edit') ||
this.HasAuth('/assettree/delete') || this.HasAuth('/assettree/poster')) {
hasAuthB = true
}
console.log(object.isAuthorized, hasAuthB)
if (!object.isAuthorized || !hasAuthB) {
this.$message({
message: '您暂无权限操作',
type: 'info'
})
return
} */
/* if (object.children && object.children.length > 0) {
this.isHasChildren = true
} else {
this.isHasChildren = false
} */
this.treeData = object
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 20// 15: margin right
document.addEventListener('click', (e) => {
this.visible = false
})
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
this.top = e.clientY - 80
this.visible = true
},
实现
<template>
<div id="app">
<!--<img src="./assets/logo.png">-->
<!--<HelloWorld/>-->
<el-row style="margin-top: 20px;">
<el-col :span="6" :offset="6">
<el-row>
<tree
:setting="setting"
:nodes="setTree"
@onCreated="handleCreated"
/>
</el-row>
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
<li @click="NodeAdd()">添加下级栏目</li>
<li @click="NodeEdit">编辑栏目</li>
<li @click="NodeDel">移除该栏目</li>
</ul>
</el-col>
<el-col :span="12">
<table class="el-table table table-striped">
<thead class="thead-dark">
<tr>
<th><div class="cell">ID</div></th>
<th><div class="cell">名称</div></th>
<th><div class="cell">描述</div></th>
</tr>
</thead>
<tbody class="dragBox">
<tr v-for="(item,index) in tableData" :key="index" class="dragbody" :dataID="item.id">
<td class="dragtr" style="text-align: center">{{ item.id }}</td>
<td style="text-align: center">{{ item.name }}</td>
<td style="text-align: center">{{ item.desc }}</td>
</tr>
</tbody>
</table>
</el-col>
</el-row>
<div style="margin-top: 100px;">实现功能拖拽右侧表格到左侧树,自动生成数据</div>
<!--左边节点的增删改查修改 -->
<el-dialog
:title="dialog.title"
:visible.sync="dialog.show"
:close-on-click-modal="false"
:close-on-press-escape="false"
:modal-append-to-body="false"
:before-close="handleClose"
custom-class="centerDialog"
>
<el-form
ref="menuData"
:model="menuData"
:rules="form_rules"
label-width="120px"
style="margin:10px;width:auto;"
>
<el-form-item prop="name" label="节点名称">
<el-input v-model="menuData.name" type="name" placeholder="请输入媒资树节点名称" />
</el-form-item>
<el-form-item prop="description" label="节点描述">
<el-input v-model="menuData.desc" type="textarea" placeholder="请输入节点描述" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose()">取 消</el-button>
<el-button type="primary" @click="onSubmitMenu('menuData')">提 交</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import * as $ from 'jquery'
import tree from '@/components/ztree'
import {addMediaTree, delMediaTree, editMediaTree} from '@/api/index'
if (!window.jQuery) {
window.jQuery = $
}
export default {
name: 'App',
components: {
tree
},
data () {
return {
setting: {
edit: {
enable: true,
showRemoveBtn: false,
showRenameBtn: false,
drag: {
prev: true,
next: true,
inner: true
}
},
check: {
enable: false
},
data: {
simpleData: {// 设置数据键值
enable: true,
idKey: 'treeID',
pIdKey: 'parentTreeID'
}
},
view: {
showIcon: false,
addDiyDom: this.addDiyDom // 自定义图标
},
callback: {
onMouseUp: this.onMouseUpFun,
onRightClick: this.openMenu
}
},
setTree: [
{treeID: '1', name: '少儿剧场', processStatusID: 'input'},
{treeID: '2', name: '经典怀旧', processStatusID: 'rejected'},
{treeID: '3', name: '少儿舞蹈', parentTreeID: '1', processStatusID: 'issued'},
{treeID: '4', name: '川剧', parentTreeID: '2', processStatusID: 'checked'},
{treeID: '5', name: '韩流剧场', processStatusID: 'input'}
],
ztreeObj: null,
iconName: {
'input': 'iconluru inputIcon',
'rejected': 'iconshibai rejectedIcon',
'issued': 'icontongguo issuedIcon',
'checked': 'iconyishen checkedIcon'
},
tableData: [
{id: '11', name: '少儿英语', desc: '少儿英语'},
{id: '22', name: '周星驰特辑', desc: '周星驰特辑'},
{id: '33', name: '邪恶之花', desc: '悬疑韩剧'},
{id: '44', name: '非遗扎染', desc: '非遗扎染'}
],
targetNode: null,
treeData: {},
curTmpTarget: null,
curTarget: null,
visible: false,
left: 0,
top: 0,
dialog: {
show: false,
option: '',
title: ''
},
menuData: {},
// 自定义验证
form_rules: {}
}
},
methods: {
// 自定义图标 使用icon font
addDiyDom (treeId, treeNode) {
const aObj = $('#' + treeNode.tId + '_a')
const className = this.iconName[treeNode.processStatusID]
const editStr = '<i class="iconfont ' + className + '" ></i>'
aObj.after(editStr)
},
// 拖动添加数据
onMouseUpFun (e, treeId, treeNode) {
try {
const target = this.curTarget
const tmpTarget = this.curTmpTarget
if (!target) return
const parentNode = treeNode
if (tmpTarget) tmpTarget.remove()
this.curTarget = null
this.curTmpTarget = null
if (parentNode) {
return new Promise((resolve, reject) => {
addMediaTree(this.targetNode).then(response => {
if (response && response.errorMsg === '成功') {
this.$message({
message: '添加成功!',
type: 'success'
})
// 添加成功之后 更新树
const dadd = response.data
// 树展示的属性用的name 我的接口返回的是title 处理数据
dadd['name'] = dadd.title
var nodes = this.ztreeObj.addNodes(parentNode, dadd, true)
console.log(nodes)
} else {
this.$message({
message: '添加失败!',
type: 'error'
})
}
resolve(response)
}).catch(error => {
reject(error)
})
})
} else {
console.log('数据错误')
}
} catch (e) {
console.log(e)
}
},
handleCreated: function (ztreeObj) {
this.ztreeObj = ztreeObj
ztreeObj.expandNode(ztreeObj.getNodes()[0], true)
//
this.bindDom()
},
bindDom () {
this.$nextTick(() => {
$('.dragBox').bind('mousedown', this.bindMouseDown)
})
},
bindMouseDown (e) {
const target = e.target
const id = $(target).parent('.dragbody').attr('dataID')
console.log(id)
if (target != null && id) {
const doc = $(document); const target = $(target)
const docScrollTop = doc.scrollTop()
const docScrollLeft = doc.scrollLeft()
const obj = this.tableData.filter(item => item.id === id)
if (obj && obj.length > 0) {
this.targetNode = obj[0]
}
console.log(this.targetNode)
const curDom = $("<span class='dom_tmp'>" + this.targetNode.name + '</span>')
if (this.curTmpTarget) this.curTmpTarget.remove()
curDom.appendTo('body')
curDom.css({
'top': (e.clientY + docScrollTop + 3) + 'px',
'left': (e.clientX + docScrollLeft + 3) + 'px'
})
this.curTarget = target
this.curTmpTarget = curDom
doc.bind('mousemove', this.bindMouseMove)
doc.bind('mouseup', this.bindMouseUp)
}
if (e.preventDefault) {
e.preventDefault()
}
},
bindMouseMove (e) {
this.noSel()
const doc = $(document)
const docScrollTop = doc.scrollTop()
const docScrollLeft = doc.scrollLeft()
const tmpTarget = this.curTmpTarget
if (tmpTarget) {
tmpTarget.css({
'top': (e.clientY + docScrollTop + 3) + 'px',
'left': (e.clientX + docScrollLeft + 3) + 'px'
})
}
return false
},
bindMouseUp (e) {
const doc = $(document)
doc.unbind('mousemove', this.bindMouseMove)
doc.unbind('mouseup', this.bindMouseUp)
// const target = this.curTarget
const tmpTarget = this.curTmpTarget
if (tmpTarget) tmpTarget.remove()
/* if ($(e.target).parents('#treeDemo').length === 0) {
console.log(target)
} */
},
noSel () {
/* try {
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
} catch(e){} */
},
// 右键显示栏目 event, treeId, treeNode
openMenu (e, treeId, object) {
/* let hasAuthB = false
if (this.HasAuth('/assettree/add') || this.HasAuth('/assettree/edit') ||
this.HasAuth('/assettree/delete') || this.HasAuth('/assettree/poster')) {
hasAuthB = true
}
console.log(object.isAuthorized, hasAuthB)
if (!object.isAuthorized || !hasAuthB) {
this.$message({
message: '您暂无权限操作',
type: 'info'
})
return
} */
/* if (object.children && object.children.length > 0) {
this.isHasChildren = true
} else {
this.isHasChildren = false
} */
this.treeData = object
const menuMinWidth = 105
const offsetLeft = this.$el.getBoundingClientRect().left // container margin left
const offsetWidth = this.$el.offsetWidth // container width
const maxLeft = offsetWidth - menuMinWidth // left boundary
const left = e.clientX - offsetLeft + 20// 15: margin right
document.addEventListener('click', (e) => {
this.visible = false
})
if (left > maxLeft) {
this.left = maxLeft
} else {
this.left = left
}
this.top = e.clientY - 80
this.visible = true
},
handleClose () {
// 关闭弹窗 清除之定义验证提示消息
this.dialog.show = false
this.$refs['menuData'].clearValidate()
},
// 删除节点
NodeDel () {
if (this.treeData.children && this.treeData.children.length !== 0) {
this.$message.error('此节点有子级,不可删除!')
return false
} else {
// 新增节点可直接删除,已存在的节点要二次确认
// 删除操作
const DelFun = () => {
return new Promise((resolve, reject) => {
delMediaTree(this.treeData.treeID).then(response => {
if (response && response.errorMsg === '成功') {
this.$message({
type: 'success',
message: '删除成功'
})
// 更新树
this.ztreeObj.removeNode(this.treeData)
resolve()
} else {
this.$message({
type: 'error',
message: '删除失败!'
})
resolve()
}
}).catch(error => {
reject(error)
})
})
}
// 二次确认
const ConfirmFun = () => {
this.$confirm('是否删除此节点?', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
center: true
}).then(() => {
DelFun()
}).catch(() => {
this.$message({
type: 'info',
message: '取消删除!'
})
})
}
ConfirmFun()
}
},
// 添加节点
NodeAdd () { // 新增节点,
this.dialog.show = true
this.dialog.title = '添加节点信息'
this.dialog.option = 'add'
this.dialog.isTop = false
this.menuData = {
treeID: 0
}
},
// 编辑节点
NodeEdit () { // 编辑节点
this.dialog.show = true
this.dialog.option = 'edit'
this.menuData = Object.assign({}, this.treeData)
this.dialog.title = '编辑该节点信息'
},
// 保存节点修改
onSubmitMenu (form) {
this.$refs[form].validate(valid => {
if (valid) {
// 此时this.menuData 保留了树结构的字段 直接提交后端报错 所以处理数据
const subData = {
title: this.menuData.name,
treeID: this.menuData.treeID,
parentTreeID: this.menuData.parentTreeID
}
if (this.dialog.option === 'edit') {
return new Promise((resolve, reject) => {
editMediaTree(subData).then(response => {
if (response && response.errorMsg === '成功') {
this.dialog.show = false
this.$message({
message: '编辑成功!',
type: 'success'
})
/**
* this.ztreeObj.updateNode(this.menuData)
* updateNode 更新树只能更新某些特定字段 具体参考 ztree 官网
*/
// 删除
this.ztreeObj.removeNode(this.menuData)
// 找到父节点
const filterData = (node) => {
return node.treeID === this.menuData.parentTreeID
}
const pid = this.ztreeObj.getNodesByFilter(filterData, true)
// 添加
var nodes = this.ztreeObj.addNodes(pid, this.menuData, true)
console.log(nodes)
resolve()
} else {
this.$message({
message: '编辑失败!',
type: 'error'
})
resolve(response)
}
}).catch(error => {
reject(error)
})
})
} else {
return new Promise((resolve, reject) => {
addMediaTree(subData).then(response => {
this.dialog.show = false
if (response && response.errorMsg === '成功') {
this.$message({
message: '添加成功!',
type: 'success'
})
let parentNode = null
const addData = response.data
// 处理属性不一致
addData.name = addData.title
if (addData.parentTreeID) {
parentNode = this.treeData
}
var nodes = this.ztreeObj.addNodes(parentNode, addData, true)
console.log(nodes)
} else {
this.$message({
message: '添加失败!',
type: 'error'
})
}
resolve(response)
}).catch(error => {
this.dialog.show = false
reject(error)
})
})
}
}
})
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.inputIcon{
color: #409eff;
font-size: 14px;
margin-left: 5px;
}
.rejectedIcon{
color: #f56c6c;
font-size: 17px;
margin-left: 5px;
}
.issuedIcon{
color: #67c23a;
font-size: 17px;
margin-left: 5px;
}
.checkedIcon{ color: #e6a23c;font-size: 17px;margin-left: 5px;}
.el-table th{
white-space: nowrap;
overflow: hidden;
user-select: none;
background-color: #fff;
}
th {
padding: 12px 0;
min-width: 0;
box-sizing: border-box;
text-overflow: ellipsis;
vertical-align: middle;
position: relative;
text-align: left;
}
.el-table .cell {
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
word-break: break-all;
line-height: 23px;
padding-left: 10px;
padding-right: 10px;
text-align: center;
}
.span-ellipsis {
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: block;
}
.dragbody{
cursor: pointer;
}
.dom_tmp{
position: absolute;
font-size: 16px;
color: #606266;
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 100;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
color: #333;
text-align: center;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
}
.contextmenu li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
}
.contextmenu li:hover {
background: #eee;
}
</style>