查找一下,现有的拖拽实现,是基于Sortable.js 实现,例如vuedraggable 就是基于此实现的。 但vuedraggable这个实现有一个坑点就是要求,要拖动项必须是其标签的第一个子集。
但element-ui的表格,里面嵌套了很多div,如果用vuedraggable这个就无法达到其效果了。
基于此,我参考vuedraggable,写了一个专门针对element-ui表格的拖拽封装。 today-vue-plug-in
[另外也有针对vue-easytable 的表格拖拽封装]
git: https://github.com/wfwfwf/vue-plug-in
代码如下:(如果有需要优化的地方,可以联系我)
var Sortable = require('sortablejs')
if (!Array.from) {
Array.from = function (object) {
return [].slice.call(object)
}
}
function buildAttribute (object, propName, value) {
if (value === undefined || value === 'undefined') {
return object
}
object = (object === null) ? {} : object
object[propName] = value
return object
}
function removeNode (node) {
node.parentElement.removeChild(node)
}
function insertNodeAt (fatherNode, node, position) {
const refNode = (position === 0) ? fatherNode.children[0] : fatherNode.children[position - 1].nextSibling
fatherNode.insertBefore(node, refNode)
}
function computeVmIndex (vnodes, element) {
return vnodes.map(elt => elt.elm).indexOf(element)
}
function computeIndexes (slots, children, isTransition) {
if (!slots) {
return []
}
const elmFromNodes = slots.map(elt => elt.elm)
const rawIndexes = [...children].map(elt => elmFromNodes.indexOf(elt))
return isTransition ? rawIndexes.filter(ind => ind !== -1) : rawIndexes
}
function emit (evtName, evtData) {
this.$nextTick(() => this.$emit(evtName.toLowerCase(), evtData))
}
function delegateAndEmit (evtName) {
return (evtData) => {
if (this.realList !== null) {
this['onDrag' + evtName](evtData)
}
emit.call(this, evtName, evtData)
}
}
const eventsListened = ['Start', 'Add', 'Remove', 'Update', 'End', 'RowClick']
const eventsToEmit = ['Choose', 'Sort', 'Filter', 'Clone', 'RowClick', 'selectionChange']
const readonlyProperties = ['Move', ...eventsListened, ...eventsToEmit].map(evt => 'on' + evt)
const props = {
options: Object,
list: {
type: Array,
required: false,
default: null
},
value: {
type: Array,
required: false,
default: null
},
noTransitionOnDrag: {
type: Boolean,
default: false
},
clone: {
type: Function,
default: (original) => { return original }
},
element: {
type: String,
default: 'div'
},
move: {
type: Function,
default: null
},
dragSelector: {
type: String,
default: null
}
}
export default {
name: 'td-draggable',
props,
data () {
return {
transitionMode: false,
noneFunctionalComponentMode: false,
init: false
}
},
render (h, context) {
const slots = this.$slots.default
if (slots && slots.length === 1) {
const child = slots[0]
if (child.componentOptions && child.componentOptions.tag === 'transition-group') {
this.transitionMode = true
}
}
let children = slots
const { footer } = this.$slots
if (footer) {
children = slots ? [...slots, ...footer] : [...footer]
}
var attributes = null
const update = (name, value) => { attributes = buildAttribute(attributes, name, value) }
// 对element tabel行点击事件进行兼容
if (JSON.stringify(this.$attrs) !== '{}') {
update('attrs', {data: this.list})
} else {
update('attrs', this.$attrs)
}
// 继承element table 的所有方法
attributes.on = this.$listeners
// {
// 'selection-change': (selection) => {
// console.log('params: ', selection)
// console.log('this: ', this)
// this.$emit('selection-change', selection)
// },
// 'row-click': (row, event, column) => {
// console.log('params: ', row)
// this.$emit('row-click', row)
// }
// }
return h(this.element, attributes, children)
},
mounted () {
this.noneFunctionalComponentMode = this.element.toLowerCase() !== this.$el.nodeName.toLowerCase()
if (this.noneFunctionalComponentMode && this.transitionMode) {
throw new Error(`Transition-group inside component is not supported. Please alter element value or remove transition-group. Current element value: ${this.element}`)
}
var optionsAdded = {}
eventsListened.forEach(elt => {
optionsAdded['on' + elt] = delegateAndEmit.call(this, elt)
})
eventsToEmit.forEach(elt => {
optionsAdded['on' + elt] = emit.bind(this, elt)
})
const options = Object.assign({}, this.options, optionsAdded, { onMove: (evt, originalEvent) => { return this.onDragMove(evt, originalEvent) } })
!('draggable' in options) && (options.draggable = '>*')
this._sortable = new Sortable(this.rootContainer, options)
// this.computeIndexes()
},
beforeDestroy () {
this._sortable.destroy()
},
computed: {
rootContainer () {
return this.transitionMode ? this.$el.children[0] : (this.dragSelector ? this.$el.querySelector(this.dragSelector) : this.$el)
},
realList () {
return (this.list) ? this.list : this.value
}
},
watch: {
options: {
handler (newOptionValue) {
for (var property in newOptionValue) {
if (readonlyProperties.indexOf(property) === -1) {
this._sortable.option(property, newOptionValue[property])
}
}
},
deep: true
}
},
methods: {
getUnderlyingVm (evt) {
if (!evt) {
return null
}
let index = evt.oldIndex
if (typeof (index) === 'undefined' || index === -1) {
return null
}
const element = this.realList[index]
return { index, element }
},
emitChanges (evt) {
this.$nextTick(() => {
this.$emit('change', evt)
})
},
alterList (onList) {
if (this.list) {
onList(this.list)
} else {
const newList = [...this.value]
onList(newList)
this.$emit('input', newList)
}
},
spliceList () {
const spliceList = list => list.splice(...arguments)
this.alterList(spliceList)
},
updatePosition (oldIndex, newIndex) {
const updatePosition = list => list.splice(newIndex, 0, list.splice(oldIndex, 1)[0])
this.alterList(updatePosition)
},
onDragStart (evt) {
this.context = this.getUnderlyingVm(evt)
const element = this.realList[evt.oldIndex]
if (this.context) {
evt.item._underlying_vm_ = this.clone(this.context.element)
}
},
onDragMove (evt) {
// console.log("onDragMove: ", evt)
},
onDragUpdate (evt) {
removeNode(evt.item)
insertNodeAt(evt.from, evt.item, evt.oldIndex)
const oldIndex = this.context.index
const newIndex = evt.newIndex
this.updatePosition(evt.oldIndex, evt.newIndex)
const moved = { element: this.context.element, oldIndex, newIndex }
this.emitChanges({ moved })
},
onDragEnd (evt) {
}
}
}