在 HTML5 中,拖放是标准的一部分,任何元素都能够拖放
链接和图片默认是可拖动的,不需要 draggable 属性
MDN drag文档
https://www.cnblogs.com/moqiutao/p/6365113.html
https://www.cnblogs.com/weiqinl/p/7886049.html
在拖动目标上触发事件 (源元素)
- ondragstart - 用户开始拖动元素时触发
- ondrag - 元素正在拖动时触发
- ondragend - 用户完成元素拖动后触发
释放目标时触发的事件:
- ondragenter - 当被鼠标拖动的对象进入其容器范围内时触发此事件
- ondragover - 当某被拖动的对象在另一对象容器范围内拖动时触发此事件
- ondragleave - 当被鼠标拖动的对象离开其容器范围内时触发此事件
- ondrop - 在一个拖动过程中,释放鼠标键时触发此事件
DragEvent使用场景
- 文件拖拽上传
- 拖拽排序
DragEvent 继承 MouseEvent 和Event属性
https://developer.mozilla.org/zh-CN/docs/Web/API/DragEvent
https://developer.mozilla.org/zh-CN/docs/Web/API/Document/drag_event
lynda drag&win drop
https://blog.csdn.net/jariwsz/article/details/7865426
https://blog.csdn.net/jariwsz/article/details/7868242
拖拽详解
https://blog.csdn.net/baidu_25343343/article/details/53215193
https://blog.csdn.net/qq_40882724/article/details/85237030
DragEvent
DragEvent
Object.keys(DragEvent)
e.hasOwnProperty(key) // 'isTrusted'
DragEvent property
[
"isTrusted",
"dataTransfer",
"screenX",
"screenY",
"clientX",
"clientY",
"ctrlKey",
"shiftKey",
"altKey",
"metaKey",
"button",
"buttons",
"relatedTarget",
"pageX",
"pageY",
"x",
"y",
"offsetX",
"offsetY",
"movementX",
"movementY",
"fromElement",
"toElement",
"layerX",
"layerY",
"getModifierState",
"initMouseEvent",
"view",
"detail",
"sourceCapabilities",
"which",
"initUIEvent",
"type",
"target",
"currentTarget",
"eventPhase",
"bubbles",
"cancelable",
"defaultPrevented",
"composed",
"timeStamp",
"srcElement",
"returnValue",
"cancelBubble",
"path",
"NONE",
"CAPTURING_PHASE",
"AT_TARGET",
"BUBBLING_PHASE",
"composedPath",
"initEvent",
"preventDefault",
"stopImmediatePropagation",
"stopPropagation"
]
darg.md
https://blog.csdn.net/qq_42301358/article/details/108372100
https://blog.csdn.net/zy1281539626/article/details/112743588
- Event BaseAPI
2. 注册 drag事件 element.addEventListener(‘dragstart’, onDragStart, false)
3. Events maybe fired repeatedly
4. optional dropzone
element.addEventListener('dragstart', onDragStart, false);
function onDragStart(e) {}
dataTransfer
<template>
<el-row>
<el-col :span="12">
<el-form ref="form" class="b-a" label-width="80px">
<draggable :clone="cloneData" :list="form_list" :options="dragOptions1">
<transition-group class="form-list-group" type="transition" :name="'flip-list'" tag="div">
<renders v-for="(items, index) in form_list" :key="index" :ele="items.ele" :obj="items.obj"></renders>
</transition-group>
</draggable>
</el-form>
</el-col>
<el-col :span="12">
<el-form ref="form" :model="formData" class="b-a" label-width="80px" >
<draggable :clone="cloneData" :list="list2" :options="dragOptions1">
<transition-group class="form-list-group" type="transition" :name="'flip-list'" tag="div">
<renders v-for="(items, index) in list2" :key="index" :ele="items.ele" :obj="items.obj" @handleChangeVal="val => handleChangeVal(val,items)"></renders>
</transition-group>
</draggable>
<el-form-item>
<el-button @click="handleSubmit('form')">保存</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>
<script>
import draggable from "vuedraggable";
import form_list from "@/components/custom/FormList";
import renders from "@/components/custom/Render";
export default {
data() {
return {
form_list,
list2: [],
formData: {}
};
},
components: {
draggable,
renders
},
methods: {
handleSubmit(name) { //保存
this.$refs[name].validate((valid) => {
if (valid) {
localStorage.setItem('template_form', JSON.stringify(this.sortable_item));
console.log(this.sortable_item) //表单中的内容
} else {
console.log('验证不通过')
}
})
},
// https://github.com/SortableJS/Vue.Draggable#clone
// 克隆,深拷贝对象
cloneData(original) {
// 深拷贝对象,防止默认空对象被更改
return JSON.parse(JSON.stringify(original));
},
// 控件回填数据
handleChangeVal(val, element) {
this.$set(this.formData, element.obj.name, val);
}
},
computed: {
// 拖拽表单1
dragOptions1() {
return {
animation: 0,
ghostClass: "ghost",
// 分组
group: {
name: "shared",
pull: "clone",
revertClone: false
},
// 禁止拖动排序
sort: false
};
},
// 拖拽表单2
dragOptions2() {
return {
animation: 0,
ghostClass: "ghost",
group: {
// 只允许放置shared的控件,禁止pull
put: ["shared"]
}
};
}
}
};
</script>
<style>
.form-list-group {
min-height: 200px;
padding: 20px !important;
}
.b-a {
border: 1px solid #ccc;
}
.ghost {
opacity: 0.5;
background: #c8ebfb;
}
</style>
formlist
import { inputConf } from "./control/Input";
import { radioConf } from "./control/Radio";
import { checkBoxConf } from "./control/CheckBox";
const formList = {
input: inputConf,
radio: radioConf,
checkBox: checkBoxConf
};
let list_arr = [];
for (let i in formList) {
list_arr.push({
ele: i,
obj: formList[i]
});
}
export default list_arr;
render
import input from './control/Input';
import radio from './control/Radio';
import checkBox from './control/CheckBox';
const form_item = {
input,
radio,
checkBox
};
export default {
name: 'renders',
render(h) {
return h('el-form-item', {
props: {
label: this.obj.label + ":"
}
}, form_item[this.ele](this, h));
},
props: {
ele: {
type: String,
default: "input"
},
obj: {
type: Object,
default: {}
}
}
}
checkbox
export default (_self, h) => {
return [
h("el-checkbox-group", {
props: {
value: _self.obj.value || []
},
on: {
'input'(arr) {
_self.obj.value = arr;
_self.$emit('handleChangeVal', arr)
}
}
},
_self.obj.items.map(v => {
return h("el-checkbox", {
props: {
label: v.label_value
}
}, v.label_name)
}))
];
};
export let checkBoxConf = {
// 对应数据库内类型
type: 'checkbox',
// 是否可配置
config: true,
// 控件左侧label内容
label: '多选框',
// 是否显示行内元素
inlineBlock: false,
// 是否必填
require: true,
// 绑定的值
value: ["1", "2"],
// 选项内数据
items: [{ "label_value": "1", "label_name": "单选框1" }, { "label_value": "2", "label_name": "单选框2" }],
// 表单name
name: '',
// 验证错误提示信息
ruleError: '该选项不能为空',
// 是否关联字段
relation: false,
// 关联字段name
relation_name: '',
// 关联字段value
relation_value: '',
// 是否被渲染
visibility: true
}
input
export default (_self, h) => {
return [
h("el-input", {
props: {
value: _self.obj.value || ""
},
attrs: {
maxlength: parseInt(_self.obj.maxlength) || 20,
placeholder: _self.obj.placeholder || "这是一个输入框",
},
on: {
"change": function(val) {
// if (!_self.obj.name) {
// return false;
// }
_self.obj.value = event.currentTarget.value;
_self.$emit('handleChangeVal', event.currentTarget.value)
}
}
})
];
};
export let inputConf = {
// 对应数据库内类型
type: 'input',
// 是否可配置
config: true,
// 控件左侧label内容
label: '输入框',
name: '',
placeholder: '',
// 最大长度
maxlength: 20,
value: '',
}
radio
export default (_self, h) => {
return [
h("el-radio-group", {
props: {
value: _self.obj.value || "1"
},
on: {
'input' (value) {
// if (!_self.obj.name) {
// return false;
// }
_self.obj = Object.assign(_self.obj, {
value
});
_self.$emit('handleChangeVal', value)
}
}
}, _self.obj.items.map(v => {
return h("el-radio", {
props: {
label: v.label_value
},
}, v.label_name)
}))
];
};
export let radioConf = {
// 对应数据库内类型
type: 'radio',
// 是否可配置
config: true,
// 控件左侧label内容
label: '单选框',
// 绑定的值
value: '',
// 选项内数据
items: [{ "label_value": "1", "label_name": "单选框1" }, { "label_value": "2", "label_name": "单选框2" }],
// 表单name
name: ''
}