一、前言
- 需要安装vuedraggable和element-plus (其他组件库同理)
- vuedraggable官网文档vue.draggable中文文档 - itxst.com
- 安装命令
NPM或yarn安装方式
csharp
yarn add vuedraggable
npm i -S vuedraggable
UMD浏览器直接引用JS方式
xml
<script src="https://www.itxst.com/package/vue/vue.min.js"></script>
<script src="https://www.itxst.com/package/sortable/Sortable.min.js"></script>
<script src="https://www.itxst.com/package/vuedraggable/vuedraggable.umd.min.js"></script>
- 页面引入
javascript
import draggable from "vuedraggable";
二、代码注释详解
-
动态列显示控制:
- 可以通过复选框控制哪些列显示/隐藏
- 支持全选/全不选功能
- 最少保留一项显示
-
列排序功能:
- 使用vuedraggable实现拖拽排序
- 通过长按拖动图标调整列顺序
-
配置持久化:
- 使用localStorage保存列配置
- 下次打开页面会记住上次的配置
-
用户友好提示:
- 操作指南提示
- 未保存修改确认提示
- 操作成功/失败反馈
xml
<template>
<!-- 设置按钮 -->
<div style="padding: 10px">
<el-icon size="24" style="margin-left:auto;display: block;" @click="drawer=true">
<Setting />
</el-icon>
</div>
<!-- 主表格 -->
<!-- 使用tableKey强制重新渲染解决ResizeObserver问题 -->
<el-table :data="tableData" border style="width: 100%" :key="tableKey">
<!-- 动态渲染列 -->
<el-table-column
v-for="item in citiesConfig"
:key="item.prop"
:prop="item.prop"
:label="item.label"
>
<template #default="scope">
{{ scope.row[item.prop] }}
<!--{{ item.isShow }}-->
</template>
</el-table-column>
</el-table>
<!-- 列配置抽屉 -->
<div class="drawer-box">
<el-drawer
v-model="drawer"
title="表格动态列配置"
direction="rtl"
:before-close="handleClose"
>
<el-alert type="warning" :closable="false">
<template #title>
<div class="el-alert-text">
<div>1、是否勾选字段将决定是否在列表中显示,最少保留一项。</div>
<div>2、长按某项图标上下拖动至要调整的位置,即可完成字段排序。</div>
<div>3、以上两项配置均为保存完成后生效。</div>
</div>
</template>
</el-alert>
<!-- 全选复选框 -->
<el-checkbox
v-model="checkAll"
:indeterminate="isIndeterminate"
@change="handleCheckAllChange"
style="margin-bottom: 10px"
>
全选
</el-checkbox>
<!-- 可拖拽的列配置列表 -->
<draggable
v-model="cities"
chosen-class="chosen"
forceFallback="true"
group="people"
animation="300"
item-key="prop"
>
<template #item="{index, element }">
<div class="draggable-item">
<!-- 单个列的复选框 -->
<el-checkbox-group
v-model="checkedCities"
@change="handleCheckedCitiesChange"
>
<el-checkbox :label="element.label" :value="element.prop">
<div class="checkbox-item">
{{ element.label }}
</div>
</el-checkbox>
</el-checkbox-group>
<!--右侧排序图标-->
<div>
<el-icon>
<DCaret />
</el-icon>
</div>
</div>
</template>
</draggable>
<!-- 底部按钮 -->
<div class="bottom-btns">
<el-button @click="drawer = false;">取消</el-button>
<el-button type="primary" @click="save">保存</el-button>
</div>
</el-drawer>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { Setting, DCaret } from '@element-plus/icons-vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import draggable from "vuedraggable";
// 抽屉显示状态
const drawer = ref(false);
// 全选状态
const checkAll = ref(false);
// 半选状态
const isIndeterminate = ref(false);
// 选中的列
const checkedCities = ref([]);
// 表格重新渲染的key
const tableKey = ref(0);
// 表格数据
const tableData = ref([
{ a: '姓名1', b: '性别1', c: '年龄1', d: '住址1', e: '手机号1', f: '身份证号1' },
{ a: '姓名2', b: '性别2', c: '年龄2', d: '住址2', e: '手机号2', f: '身份证号2' },
{ a: '姓名3', b: '性别3', c: '年龄3', d: '住址3', e: '手机号3', f: '身份证号3' },
{ a: '姓名4', b: '性别4', c: '年龄4', d: '住址4', e: '手机号4', f: '身份证号4' },
{ a: '姓名5', b: '性别5', c: '年龄5', d: '住址5', e: '手机号5', f: '身份证号5' }
]);
// 所有列配置(包括显示/隐藏状态)
const cities = ref([
{ label: '姓名', prop: 'a', isShow: true },
{ label: '身份证号', prop: 'f', isShow: true },
{ label: '性别', prop: 'b', isShow: false },
{ label: '年龄', prop: 'c', isShow: false },
{ label: '住址', prop: 'd', isShow: false },
{ label: '手机号', prop: 'e', isShow: true }
]);
// 实际显示的列配置(初始化时从cities中筛选)
const citiesConfig = ref([]);
/**
* 保存列配置
* 1. 检查至少选中一列
* 2. 保存到localStorage
* 3. 更新显示的列配置
* 4. 强制表格重新渲染
*/
function save() {
// 展示的列数据
let citiesFilterList = cities.value.filter(item => item.isShow);
if(citiesFilterList?.length > 0) {
// 保存到本地存储
localStorage.setItem('cities', JSON.stringify(cities.value));
// 更新显示的列配置(使用展开运算符创建新数组)
citiesConfig.value = [...cities.value.filter(item => item.isShow)];
// 强制表格重新渲染(解决ResizeObserver问题)
tableKey.value++;
ElMessage.success('保存成功!');
}
else {
ElMessage.warning('表格列最少需要保存一项!');
}
}
/**
* 关闭抽屉前的确认
* 检查是否有未保存的修改
*/
function handleClose() {
let citiesData = localStorage.getItem('cities');
if(citiesData) {
// 比较当前配置与保存的配置
if(citiesData !== JSON.stringify(cities.value)) {
ElMessageBox.confirm(`当前配置存在变动未保存,请确认是否要关闭?`, {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
drawer.value = false;
})
.catch(() => {
// 用户取消关闭
});
}
else {
drawer.value = false;
}
}
else {
drawer.value = false;
}
}
/**
* 全选/全不选
* @param {Boolean} val - 是否全选
*/
const handleCheckAllChange = (val) => {
// 全选全不选时修改选中的数据
checkedCities.value = val ? cities.value.map((item) => item.prop) : [];
// 选中的数据 修改列显示状态
cities.value.forEach((item) => item.isShow = val);
// 清除半选的样式
isIndeterminate.value = false;
};
/**
* 单个列选择变化
* @param {Array} val - 选中的列prop数组
*/
const handleCheckedCitiesChange = (val) => {
// 单选时选中的数据长度
const checkedCount = cities.value.filter((item) => val.includes(item.prop)).length;
// 选中的数据 修改列显示状态
cities.value.forEach((item) => item.isShow = val.includes(item.prop));
// 是不是全选
checkAll.value = checkedCount === cities.value.length;
// 有且不是全选 即半选样式
isIndeterminate.value = checkedCount > 0 && checkedCount < cities.value.length;
};
/**
* 初始化列配置
* 1. 从localStorage加载保存的配置
* 2. 初始化复选框状态
* 3. 设置实际显示的列
*/
function init() {
// 是否存在历史配置
let citiesData = localStorage.getItem('cities');
if(citiesData) {
cities.value = JSON.parse(citiesData);
}
// 设置全选状态
checkAll.value = cities.value.every((item) => item.isShow);
// 设置选中项
if(checkAll.value) {
checkedCities.value = cities.value.map((item) => item.prop);
}
else {
// 全选框样式
isIndeterminate.value = cities.value.some((item) => item.isShow);
checkedCities.value = isIndeterminate.value
? cities.value.filter((item) => item.isShow).map((item) => item.prop)
: [];
}
// 初始化显示的列
citiesConfig.value = [...cities.value.filter(item => item.isShow)];
}
// 初始化
init();
</script>
<style scoped lang="scss">
/* 底部按钮定位 */
.bottom-btns {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
}
/* 标题颜色 */
.title {
color: var(--text-color);
}
/* 复选框盒子样式 */
:deep(.el-checkbox) {
display: flex !important;
align-items: center;
padding: 10px;
}
/* 可拖拽项样式 */
.draggable-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
img {
width: 24px;
}
// &:hover {
// background-color: #eeeeee;
// }
}
//被拖拽的项的样式
.chosen {
background-color: rgba(238, 238, 238, 0.4);
border: 1px solid #eeeeee;
}
.el-alert-text {
font-size: 12px;
}
.drawer-box:deep(.el-drawer__header) {
margin-bottom: 0;
}
</style>