SortableJS 是一个JavaScript库,用于创建具有拖放排序功能的交互式列表和表格。它允许您通过拖放方式重新排列HTML元素,例如列表或表格行,以自定义其顺序。SortableJS支持多种拖放手势和交互,包括触摸屏操作和鼠标拖放。除此之外,SortableJS还提供了许多选项和事件供您自定义和扩展其功能。它的主要作用是使网页更加交互和用户友好。
一、安装
npm i sortablejs -S
二、常用配置
const config = {
//一个网页存在多个分组时设置,组名相同的组之间元素可以相互拖拽
group: "name",
//2种group写法选一种就可以了
group: {
name: 'name',
pull: 'clone', //克隆元素
},
//是否允许元素内部排序,如果为false当有多个排序组时,多个组之间可以拖拽,本身不能拖拽(默认true)
sort: true,
//是否禁用拖拽和排序
disabled: false,
//动画效果持续时间(不设置或0都没有过渡效果)
animation: 150,
//点击指定class类的元素才能拖拽(比如点击元素内的图标才能拖拽元素,可以给图标设置my-handle class)
//class可以定义在元素本身上,也可以定义在子元素上
handle: ".my-handle",
// class为ignore的元素不能拖动
filter: ".ignore",
//含有item 类的元素可以被拖拽(class只能定义在元素本身上)
draggable: ".item",
//指定获取拖动后排序的属性
dataIdAttr: 'data-id',
//给停靠位置添加的class(可以给这个class定义样式)
ghostClass: "ghost",
//选中元素添加的类(包括悬浮的元素和停靠位置的元素)
chosenClass: "chosen",
//拖拽对象移动时添加的类
dragClass: "drag",
//禁用html5原生拖拽
forceFallback: false,
...
//克隆事件
onClone: function (evt) {
//被克隆的对象(被移到另外地方的那个元素)
var origEl = evt.item;
//克隆后的对象(还是在原来位置的元素)
var cloneEl = evt.clone;
cloneEl.innerHTML = "clone出的元素";
},
...
}
三、案例
1. 通过 blur 和 focus 事件,对类名进行添加和删除。使用 Sortable 里的 handle 参数对拖拽功能进行使用和取消。
2. 使用 uuid 设置唯一值,保证key不会重复...
3. 页面效果 ↓↓↓↓↓
<template>
<div>
<!-- 拖拽排序 -->
<!--
1. element ui + sortablejs + vue2 + uuid
2. 实现拖拽排序
3. 在el-input 内实现,可复制
-->
<div class="p-10">
<div class="title-btn-wrapper flex-end-center">
<div class="add-delete-warp">
<el-button type="primary" size="small" @click="addTable('repairSteps')">新 增</el-button>
<el-button type="danger" size="small" @click.stop="deleteSelection('repairSteps')">删 除</el-button>
</div>
</div>
<!-- 表单 -->
<div class="draggable">
<el-table :data="repairList" max-height="600" style="width: 100%"
border :header-cell-class-name="formHeadStarClass" row-key="uniqueId"
:cell-class-name="notSort"
@selection-change="handleSelectionChange($event,'repairSteps')">
<el-table-column type="selection" width="50" align="center"></el-table-column>
<el-table-column type="index" label="序号" align="center" width="50"></el-table-column>
<el-table-column prop="step" align="center" label="步骤顺序" width="140">
<template slot-scope="scope">
<el-input type="text" :disabled="false" placeholder="请输入步骤顺序" size="mini"
v-model="scope.row.step"
@blur="handleInputBlur" @focus="handleInputFocus"></el-input>
</template>
</el-table-column>
<el-table-column prop="content" align="center" label="步骤内容">
<template slot-scope="scope">
<el-input type="text" :disabled="false" placeholder="请输入步骤内容" size="mini"
v-model="scope.row.content"
@blur="handleInputBlur" @focus="handleInputFocus"></el-input>
</template>
</el-table-column>
<el-table-column prop="num" align="center" label="数量" width="140">
<template slot-scope="scope">
<el-input type="number" placeholder="请输入数量" :min="0"
size="mini" v-model="scope.row.num" @input="shipmentQuantityInput($event, scope.row, 'num')"
></el-input>
</template>
</el-table-column>
<el-table-column prop="remark" align="center" label="备注" width="260">
<template slot-scope="scope">
<el-input type="text" :disabled="false" placeholder="请输入备注" size="mini"
v-model="scope.row.remark"
@blur="handleInputBlur" @focus="handleInputFocus"></el-input>
</template>
</el-table-column>
</el-table>
</div>
</div>
</div>
</template>
<script>
import { v4 as uuid4 } from 'uuid'; // 通用唯一识别码
import Sortable from 'sortablejs';
export default {
components: {},
props: {},
data() {
return {
contentUnit: [],
notSort: 'my-handle',
repairList: [
{
step: '',
content: '',
unit: '',
num: '',
id: '',
remark: '',
uniqueId: '',
repairId: '',
ShipId: '',
},
],
}
},
computed: {},
watch: {},
created() {
this.repairList.forEach((item) => {
item.uniqueId = this.getUniqueId();
});
},
mounted(){
this.rowDrop() // 初始化拖拽
},
methods: {
getUniqueId() { // 获取唯一值
const reg = new RegExp('-', 'g'); // 正则表达式 /-/g
return uuid4().replace(reg, ''); // 获取唯一标识,并去除 -
},
handleInputFocus() { // 失去焦点时,进行清除
this.notSort = '';
},
handleInputBlur() { // 获取焦点时,进行生成
this.notSort = 'my-handle';
},
rowDrop() { // 拖拽功能
const tbody = document.querySelector('.draggable tbody');
const that = this;
Sortable.create(tbody, {
animation: 150,
handle: '.my-handle',
filter: '.not-sort',
draggable: '.draggable .el-table__row',
onEnd({ newIndex, oldIndex }) {
// 1.将要排序的数据 拷贝一份
const list = that.repairList.slice();
// 2. 通过拷贝的数据 进行排序
const currRow = list.splice(oldIndex, 1)[0];
list.splice(newIndex, 0, currRow);
list.forEach((item, index) => {
item.sequence = index + 1;
});
// 3.将源数据清空
that.repairList = [];
// 4.通过 $nextTick 的方式从新更新视图
that.$nextTick(() => {
that.repairList = list;
});
},
});
},
addTable(action) { // 新增
if (action === 'repairSteps') { // 步骤顺序
this.repairList.push({
repairId: '',
ShipId: '',
uniqueId: this.getUniqueId(),
id: '',
});
}
},
deleteSelection(action) { // 删除
const newShowArr = [];
if (action === 'repairSteps') { // 步骤顺序
const deleteList = this[action].map((i) => i.uniqueId);
this.repairList.forEach((item) => {
if (!deleteList.includes(item.uniqueId)) {
newShowArr.push(item);
}
});
}
this.repairList = newShowArr;
},
formHeadStarClass({ column }) { // 添加 * 必填标识
const requiredLabels = ['数量', '步骤内容', '步骤顺序', '单位'];
return requiredLabels.includes(column.label) ? 'star-row' : '';
},
shipmentQuantityInput(event, row, field) { // 步骤的数量
row.num = parseInt(event.replace(/[^0-9]/g, 0), 10);
},
handleSelectionChange(val, action) { // 选中的数据
if (val.length) {
this[action] = val;
} else {
this[action] = [];
}
},
},
}
</script>
<style lang="scss" scoped>
.p-10 {
// padding: 10px 10px 100px;
width: 900px;
margin-left: 20px;
}
.title-btn-wrapper {
margin: 10px 0;
}
.flex-end-center {
display: flex;
justify-content: flex-end;
align-items: center;
}
.add-delete-warp {
margin-bottom: 10px;
}
</style>