vue-draggable实现拖拽表单
draggable在项目中的实际应用
这个功能为公司做的项目中的一个功能,要求能拖拽配置表单,并且能实现实时计算,本文讲述的是拖拽表单组件,配置出对应的算法的实现
1. 介绍及配置项
1.1 特性
使用版本: vue.draggable.next
vue.draggable.next
是一款vue3
的拖拽插件,是vue.draggable
升级版本。Vue.Draggable
是一款基于Sortable.js
实现的vue
拖拽插件。
特性:
- 支持移动设备
- 拖拽和选择文本
- 智能滚动
- 可以在不同列表间拖拽
- 不依赖jQuery为基础
- vue 2过渡动画兼容
- 支持撤销操作
1.2 配置项
属性说明:
属性名称 | 说明 |
---|---|
modelValue | 传入数组到可拖动组件。通常与内部元素v-for指令引用的数组相同。 |
list | 与modelValue类似,主要的区别是list拖动组件是使用splice方法更新的,而modelValue是不可变的。 |
group | 如果一个页面有多个拖拽区域,通过设置group名称可以实现多个区域之间相互拖拽 或者 { name: "...", pull: [true, false, 'clone', array , function], put: [true, false, array , function] } |
sort | 是否开启排序,如果设置为false,它所在组无法排序 |
delay | 鼠标按下多少秒之后可以拖拽元素 |
touchStartThreshold | 鼠标按下移动多少px才能拖动元素 |
disabled | :disabled= "true",是否启用拖拽组件 |
animation | 拖动时的动画效果,如设置animation=1000表示1秒过渡动画效果 |
handle | :handle=".mover" 只有当鼠标在class为mover类的元素上才能触发拖到事件 |
filter | :filter=".unmover" 设置了unmover样式的元素不允许拖动 |
draggable | :draggable=".item" 样式类为item的元素才能被拖动 |
ghost-class | :ghost-class="ghostClass" 设置拖动元素的占位符类名,你的自定义样式可能需要加!important才能生效,并把forceFallback属性设置成true |
chosen-class | :ghost-class="hostClass" 被选中目标的样式,你的自定义样式可能需要加!important才能生效,并把forceFallback属性设置成true |
drag-class | :drag-class="dragClass"拖动元素的样式,你的自定义样式可能需要加!important才能生效,并把forceFallback属性设置成true |
force-fallback | 默认false,忽略HTML5的拖拽行为,因为h5里有个属性也是可以拖动,你要自定义ghostClass chosenClass dragClass样式时,建议forceFallback设置为true |
fallback-class | 默认false,克隆选中元素的样式到跟随鼠标的样式 |
fallback-on-body | 默认false,克隆的元素添加到文档的body中 |
fallback-tolerance | 按下鼠标移动多少个像素才能拖动元素,:fallback-tolerance="8" |
scroll | 默认true,有滚动区域是否允许拖拽 |
scroll-fn | 滚动回调函数 |
scroll-fensitivity | 距离滚动区域多远时,滚动滚动条 |
scroll-speed | 滚动速度 |
1.3 示例
安装
npm i -S vuedraggable@next
普通拖拽
<template>
<div class="itxst">
<div>
<draggable
:list="state.list"
ghost-class="ghost"
chosen-class="chosenClass"
animation="300"
@start="onStart"
@end="onEnd"
>
<template #item="{ element }">
<div class="item">
{{ element.name }}
</div>
</template>
</draggable>
</div>
<div>{{ state.list }}</div>
</div>
</template>
<script setup>
import { ref, reactive } from "vue";
import draggable from "vuedraggable";
/*
draggable 对CSS样式没有什么要求万物皆可拖拽
:list="state.list" //需要绑定的数组
ghost-class="ghost" //被替换元素的样式
chosen-class="chosenClass" //选中元素的样式
animation="300" //动画效果
@start="onStart" //拖拽开始的事件
@end="onEnd" //拖拽结束的事件
*/
const state = reactive({
//需要拖拽的数据,拖拽后数据的顺序也会变化
list: [
{ name: "www.itxst.com", id: 0 },
{ name: "www.baidu.com", id: 1 },
{ name: "www.google.com", id: 2 },
],
});
//拖拽开始的事件
const onStart = () => {
console.log("开始拖拽");
};
//拖拽结束的事件
const onEnd = () => {
console.log("结束拖拽");
};
</script>
<style scoped>
.itxst {
width: 600px;
display: flex;
}
.itxst > div:nth-of-type(1) {
flex: 1;
}
.itxst > div:nth-of-type(2) {
width: 270px;
padding-left: 20px;
}
.item {
border: solid 1px #eee;
padding: 6px 10px;
text-align: left;
}
.item:hover {
cursor: move;
}
.item + .item {
margin-top: 10px;
}
.ghost {
border: solid 1px rgb(19, 41, 239);
}
.chosenClass {
background-color: #f1f1f1;
}
</style>
在表格中的拖拽
<template>
<div>
<div class="title">鼠标放到ID列和行上试试 可以拖拽行和列</div>
<table class="tb">
<thead>
<draggable
v-model="state.headers"
animation="200"
tag="tr"
:item-key="(key) => key"
>
<template #item="{ element: header }">
<th class="move">
{{ header }}
</th>
</template>
</draggable>
</thead>
<draggable
:list="state.list"
handle=".move"
animation="300"
@start="onStart"
@end="onEnd"
tag="tbody"
item-key="name"
>
<template #item="{ element }">
<tr>
<td
class="move"
v-for="(header, index) in state.headers"
:key="header"
>
{{ element[header] }}
</td>
</tr>
</template>
</draggable>
</table>
</div>
</template>
<script setup>
import { ref, reactive } from "vue";
import draggable from "vuedraggable";
/*
draggable 对CSS样式没有什么要求万物皆可拖拽
:list="state.list" //需要绑定的数组
animation="300" //动画效果
@start="onStart" //拖拽开始的事件
@end="onEnd" //拖拽结束的事件
*/
const state = reactive({
//列的名称
headers: ["id", "name", "intro"],
//需要拖拽的数据,拖拽后数据的顺序也会变化
list: [
{ name: "www.itxst.com", id: 0, intro: "慢吞吞的蜗牛" },
{ name: "www.baidu.com", id: 1, intro: "中文搜索引擎" },
{ name: "www.google.com", id: 3, intro: "安卓操作系统" },
],
});
//拖拽开始的事件
const onStart = () => {
console.log("开始拖拽");
};
//拖拽结束的事件
const onEnd = () => {
console.log("结束拖拽");
};
</script>
<style scoped>
.title {
padding: 3px;
font-size: 13px;
}
.itxst {
width: 600px;
}
.move {
cursor: move;
}
table.tb {
color: #333;
border: solid 1px #999;
font-size: 13px;
border-collapse: collapse;
min-width: 500px;
user-select: none;
}
table.tb th {
background: rgb(168 173 217);
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #999;
text-align: left;
}
table.tb th:nth-of-type(1) {
text-align: center;
}
table.tb td {
background: #d6c8c8;
border-width: 1px;
padding: 8px;
border-style: solid;
border-color: #999;
}
table.tb td:nth-of-type(1) {
text-align: center;
}
</style>
2. Vue3项目应用
示例:
基础工具拖拽部分实现
<template>
<div class="list-title"><span>基础工具</span></div>
<draggable
:list="componentsList.base"
:group="{ name: 'people', pull: 'clone', put: false }"
:sort="false" // 不排序
:clone="cloneElement" // 克隆事件
:move="onMove" // 移动事件
item-key="id"
class="draggable-list"
:forceFallback="true"
>
<template #item="{ element }">
<div
class="list-group-item"
@click="onChooseComponent(element)"
:class="{ 'active-element': currentSelectElement.id === element.id }"
>
<i :class="['iconfont', element.icon]"></i>{{ element.name }}
</div>
</template>
</draggable>
</template>
<script setup>
/**
* 复制元素
* @param {Object} param0
*/
let cloneId = ''
const cloneElement = ({ id, type, name }) => {
let ele = createNewElement(type)
cloneId = ele.id
console.log(ele)
return ele
}
/**
* 组件移动时,判断移动位置是否为最外层
*/
const onMove = (evt, originalEvent) => {
if (
(evt.draggedContext.element.type === 1 || evt.draggedContext.element.type === 2) &&
props.computeUnitList.findIndex((item) => item.type === evt.draggedContext.element.type) === -1
) {
// 外部数据或公共数据拖向最外层
emit('handleListChange')
return true
}
if (
(evt.draggedContext.element.type === 1 || evt.draggedContext.element.type === 2) &&
evt.to.className.indexOf('dragUnitArea') === -1
) {
// 外部数据或公共数据不是拖向最外层
return false
}
console.log(evt)
if (
evt.to.className.indexOf('dragUnitArea') != -1 ||
(evt.to.className.indexOf('table-draggable') != -1 && evt.draggedContext.element.type === 6) ||
(evt.to.className.indexOf('table-draggable') != -1 &&
evt.draggedContext.element.type === 'energy_table')
) {
// 拖向最外层,类型为外部数据或者公共数据,或者将表格组件拖到表格内
return false
}
}
</script>
中间部分拖拽组件主要实现
<template>
<div>
<draggable
class="dragArea"
tag="div"
:list="components"
:group="{ name: 'g1', put: true }"
item-key="id"
:move="onMove"
:animation="200"
ghost-class="ghost"
:forceFallback="true"
>
<template #item="{ element }">
<div
:class="{ 'list-group-item': true, 'active-area': activeArea?.id === element.id }"
:style="{
width: !element.ratio
? element.type === 6
? '100% !important'
: '50% !important'
: element.ratio + '% !important'
}"
v-if="element.type != 0"
@click.stop="onChooseArea(element)"
>
<!-- <CSingleText
v-if="element.type === 'single_text'"
:activeArea="activeArea"
:elementData="element"
></CSingleText> -->
<component
:is="componentNameList[element.type]"
:activeArea="activeArea"
:elementData="element"
@onCopyElement="$emit('onCopyElement')"
@onDelElement="$emit('onDelElement')"
@onChooseArea="onChooseArea"
>
<div class="base-title">
<div class="mask"></div>
<el-icon
v-if="
element.defaultValueType === 1 &&
element.dataLinkages[0]?.associationRules &&
element.dataLinkages[0].associationRules[0]?.targetField
"
>
<Link />
</el-icon>
<i v-if="element.isRequired">*</i>
{{ element.name }}
</div>
</component>
<p class="operate" v-if="activeArea?.id === element.id">
<i class="iconfont iconcopy" @click.stop="$emit('onCopyElement')"></i>
<i class="iconfont icondel" @click.stop="$emit('onDelElement')"></i>
</p>
</div>
<template v-else>
<ComputeUnit
@onCopyElement="$emit('onCopyElement')"
@onDelElement="$emit('onDelElement')"
@addBrotherUnit="$emit('addBrotherUnit')"
@addChildUnit="$emit('addChildUnit')"
@onShowAlCenter="$emit('onShowAlCenter')"
class="drag-children"
:activeArea="activeArea"
:computeUnit="element"
@onChooseArea="onChooseArea"
></ComputeUnit>
</template>
</template>
<template #footer>
<slot></slot>
</template>
</draggable>
</div>
</template>
<script setup>
import draggable from 'vuedraggable'
import ComputeUnit from '../ComputeUnit/ComputeUnit.vue'
import { componentNameList } from '../../utils/element'
import { Link } from '@element-plus/icons-vue'
const props = defineProps({
components: {
required: true,
type: Array
},
activeArea: {
type: Object,
default() {
return {
id: '-1'
}
}
}
})
const emits = defineEmits([
'addBrotherUnit',
'addChildUnit',
'onChooseArea',
'onCopyElement',
'onDelElement',
'onShowAlCenter'
])
const onChooseArea = (ele) => {
emits('onChooseArea', ele)
}
/**
* 组件移动时,判断移动位置是否正确
*/
const onMove = (evt, originalEvent) => {
if (
(evt.to.className === 'dragUnitArea' && evt.draggedContext.element.type != 0) ||
evt.to.className.indexOf('table-draggable') != -1
) {
return false
}
}
</script>
转载自:https://juejin.cn/post/7234680624655941687