使用 Sortable.js、Dragula 和 Gridstack.js 实现可拖拽表格
当涉及到实现可拖拽的表格时,Sortable.js、Dragula 和 Gridstack.js 是三个常用的 JavaScript 库。
下面是它们的详细介绍:
Sortable.js:
- Sortable.js 是一个简单易用的库,用于实现拖拽排序和重新排列表格行。
- 它支持在同一表格内进行行的拖拽排序,也可以实现不同表格之间的拖拽操作。
- Sortable.js 具有许多配置选项,例如设置拖拽手柄、限制拖拽的方向、自定义排序规则等。
- 它还提供了一组事件回调函数,使你可以在拖拽过程中处理自定义逻辑。
- Sortable.js 的文档清晰明了,提供了大量的示例代码和演示效果,可在其官方文档(https://sortablejs.github.io/Sortable/)中找到。
Dragula:
- Dragula 是一个轻量级的库,专门用于在多个容器之间实现拖拽操作。
- 它可以很容易地应用于表格中的行拖拽,可以将行从一个表格拖动到另一个表格,并在不同表格之间重新排序。
- Dragula 支持多个容器之间的复杂拖拽配置,并提供了丰富的事件回调函数,使你可以处理拖拽的各个阶段。
- 与 Sortable.js 不同,Dragula 不提供内置的排序功能,而是专注于拖拽操作。
- 更多关于 Dragula 的信息和示例可以在其 GitHub 页面(https://github.com/bevacqua/dragula)中找到。
Gridstack.js:
- Gridstack.js 是一个功能强大的库,用于创建可拖拽和可调整大小的网格布局。
- 它适用于构建仪表盘、网格系统和栅格布局,非常适合用于表格布局。
- Gridstack.js 具有灵活的配置选项,可自定义网格大小、调整大小的方式和限制等。
- 它还支持拖拽的可视化布局管理,允许用户自由调整和重新排列表格中的单元格。
- Gridstack.js 提供了丰富的 API 和事件,使你可以与表格中的元素进行交互。
- Gridstack.js 的官方文档(https://gridstackjs.com/)提供了全面的介绍和示例。
这些库各自适用于不同的应用场景,根据你的具体需求选择最适合的库。它们都有良好的文档和示例,可以帮助你更好地理解和使用它们。
Sortable.js 集合 Vue3 element-plus el-table 的使用
1. 效果图
2. 代码实现
- (1) 安装 sortablejs
bash
npm i sortablejs --save
- (2) 在组件中引入并使用
vue
<template>
<el-table
ref="tableRef"
:data="tableData"
:row-key="(row) => row.id"
@row-contextmenu="onRowClick"
v-bind="$attrs"
v-on="$attrs"
height="calc(100vh - 160px - 40px)"
style="width: 100%">
<el-table-column type="selection" reserve-selection width="55" align="center" />
<el-table-column type="index" :index="calculateIndex" label="序号" width="55" align="center" />
<el-table-column prop="startTime" label="日期" width="114px">
<template #default="scope">
{{ scope.row.startTime ? dayjs(scope.row.startTime).format('YYYY/MM/DD HH:mm:ss') : '' }}
</template>
</el-table-column>
<el-table-column prop="id" label="任务ID" width="70" align="center" />
<el-table-column prop="name" label="文件名称">
<template #default="scope">
<span class="file-name" @click="handleShowRowFolder(scope.row.path)" :title="scope.row.path">{{
scope.row.name
}}</span>
</template>
</el-table-column>
<el-table-column prop="size" label="大小" width="100px">
<template #default="scope">
{{ bytes(scope.row.size) }}
</template>
</el-table-column>
<el-table-column prop="status" label="状态" align="center" width="100px">
<template #default="scope">
<el-tag class="disabled-transitions" :type="MAP_STATUS.get(scope.row.status).tag">{{
MAP_STATUS.get(scope.row.status).text
}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="progress" label="完成进度" align="left">
<template #default="scope">
<div class="progress-wrap">
<el-progress :percentage="scope.row.progress" :format="(percentage) => percentage.toFixed(1) + '%'" />
</div>
</template>
</el-table-column>
<el-table-column prop="" label="操作" align="center" width="100px">
<template #default="scope">
<el-button
@click="handleResultFolder(scope.row)"
:disabled="scope.row.status !== 2"
text
:title="scope.row.path"
><el-icon><Folder /></el-icon
></el-button>
</template>
</el-table-column>
</el-table>
<p>
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[5, 10, 20, 30]"
:total="total"
background
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</p>
</template>
<script setup>
import {onMounted, defineExpose, computed, ref} from 'vue';
import dayjs from 'dayjs';
import useElectron from '@/renderer/index/composables/useElectron.js';
import {MAP_STATUS} from '@/renderer/index/utils/constant.js';
import bytes from 'bytes';
import Sortable from 'sortablejs';
const {handleResultFolder, handleShowRowFolder} = useElectron();
const props = defineProps({
data: {type: Array, required: true, default: () => []},
total: {type: Number, required: true, default: 0}
});
const defaultCurrentPage = 1;
const defaultPageSize = Number(localStorage.getItem('pageSize') || 10);
const currentPage = ref(Number.isNaN(defaultCurrentPage) ? 1 : defaultCurrentPage);
const pageSize = ref(Number.isNaN(defaultPageSize) ? 10 : defaultPageSize);
const tableData = computed(() => {
const startIndex = (currentPage.value - 1) * pageSize.value;
const endIndex = startIndex + pageSize.value;
return props.data.slice(startIndex, endIndex);
});
const calculateIndex = (index) => {
const startIndex = (currentPage.value - 1) * pageSize.value;
return startIndex + index + 1;
};
const handleSizeChange = (val) => {
localStorage.setItem('pageSize', pageSize.value);
};
const handleCurrentChange = (val) => {
localStorage.setItem('pageSize', pageSize.value);
};
const onRowClick = (row) => {
console.log('[onRowClick]', JSON.parse(JSON.stringify(row)));
};
const tableRef = ref();
onMounted(() => {
Sortable.create(tableRef.value.$el.querySelector('.el-table__body-wrapper tbody'), {
animation: 150,
onEnd: ({newIndex, oldIndex}) => {
const currRow = tableData.value.splice(oldIndex, 1)[0];
tableData.value.splice(newIndex, 0, currRow);
}
});
});
defineExpose({
getTableRef: () => tableRef.value
});
</script>
<style lang="scss" scoped>
.disabled-transitions {
transition: none !important;
}
.file-name {
cursor: pointer;
}
.progress-wrap ::v-deep(.el-progress__text) {
width: 60px;
font-size: 12px;
}
</style>
实现拖拽列排序代码
js
onMounted(() => {
Sortable.create(
tableRef.value.$el.querySelector(".el-table__header-wrapper thead tr"),
{
animation: 150,
onMove: () => {},
onUpdate: () => {},
onSort: () => {},
onEnd: ({ newIndex, oldIndex }) => {
// 获取表格列定义
const table = tableRef.value;
const oldColumns = table.store.states.columns;
// 重新排列列定义的顺序
const newColumns = [...oldColumns.value];
const movedColumn = newColumns.splice(oldIndex, 1)[0];
newColumns.splice(newIndex, 0, movedColumn);
oldColumns.value = newColumns;
},
}
);
});
最后加上销毁 Sortablejs 创建的实例
js
const tableRef = ref();
const sortableInstanceRow = ref(null);
const sortableInstanceColumn = ref(null);
onMounted(() => {
sortableInstanceRow.value = Sortable.create(
tableRef.value.$el.querySelector(".el-table__body-wrapper tbody"),
{
animation: 150,
onEnd: ({ newIndex, oldIndex }) => {
const currRow = tableData.value.splice(oldIndex, 1)[0];
tableData.value.splice(newIndex, 0, currRow);
},
}
);
sortableInstanceRow.value = Sortable.create(
tableRef.value.$el.querySelector(".el-table__header-wrapper thead tr"),
{
animation: 150,
onMove: () => {},
onUpdate: () => {},
onSort: () => {},
onEnd: ({ newIndex, oldIndex }) => {
// 获取表格列定义
const table = tableRef.value;
const oldColumns = table.store.states.columns;
// 重新排列列定义的顺序
const newColumns = [...oldColumns.value];
const movedColumn = newColumns.splice(oldIndex, 1)[0];
newColumns.splice(newIndex, 0, movedColumn);
oldColumns.value = newColumns;
},
}
);
});
onBeforeUnmount(() => {
if (sortableInstanceRow.value) {
sortableInstanceRow.value.destroy();
sortableInstanceRow.value = null;
}
if (sortableInstanceColumn.value) {
sortableInstanceColumn.value.destroy();
sortableInstanceColumn.value = null;
}
});