组件核心功能
-
拖拽排序(使用
vuedraggable
) -
显示/隐藏控制
-
列宽调整
-
列固定状态记忆
-
搜索过滤列
-
本地存储(localStorage)可改成接口保存
-
默认配置恢复
-
通过
searchText
动态过滤列。
安装拖拽依赖
npm install vuedraggable
-
如果需要支持后端存储,可以在
savePreferences
方法中添加 API 调用。 -
确保
columns
配置中包含prop
和label
字段。
创建组件ColumnVisibilityControl.vue
javascript
<template>
<div class="column-control">
<el-button @click="showControl = true"
><el-icon><Grid /></el-icon
></el-button>
<el-dialog v-model="showControl" title="列配置" width="600px">
<el-input
v-model="searchText"
placeholder="搜索列"
clearable
style="margin-bottom: 16px"
/>
<draggable v-model="currentColumns" item-key="prop" @end="handleDragEnd">
<template #item="{ element: col }">
<div
v-if="col.label.toLowerCase().includes(searchText.toLowerCase())"
class="column-item"
>
<el-checkbox v-model="col.visible">{{ col.label }}</el-checkbox>
<el-input-number
v-if="col.visible"
v-model="col.width"
:min="50"
:max="500"
style="margin-left: 16px"
placeholder="列宽"
/>
<el-checkbox
v-if="col.visible"
v-model="col.fixed"
style="margin-left: 16px"
>
固定
</el-checkbox>
</div>
</template>
</draggable>
<template #footer>
<el-button @click="resetToDefault">恢复默认</el-button>
<el-button type="primary" @click="savePreferences">保存</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, watch, onMounted } from "vue";
import { ElMessage } from "element-plus";
import draggable from "vuedraggable";
import {
Check,
Delete,
Edit,
Message,
Search,
Grid,
} from "@element-plus/icons-vue";
const props = defineProps({
columns: {
type: Array,
default: () => [],
validator: (value) => value.every((col) => "prop" in col && "label" in col),
},
storageKey: {
type: String,
default: "table-columns-preference",
},
});
const emit = defineEmits(["columns-change"]);
const showControl = ref(false);
const searchText = ref("");
const currentColumns = ref([]);
const defaultColumns = ref([]);
const initializeColumns = () => {
defaultColumns.value = JSON.parse(JSON.stringify(props.columns));
currentColumns.value = JSON.parse(JSON.stringify(props.columns));
const savedData = localStorage.getItem(props.storageKey);
if (savedData) {
try {
const parsed = JSON.parse(savedData);
applySavedPreferences(parsed);
} catch {
localStorage.removeItem(props.storageKey);
}
}
};
const applySavedPreferences = (savedData) => {
currentColumns.value = props.columns
.map((col) => {
const savedCol = savedData.find((c) => c.prop === col.prop);
return savedCol ? { ...col, ...savedCol } : col;
})
.sort((a, b) => a.order - b.order);
};
const updateVisibleColumns = () => {
const visibleColumns = currentColumns.value
.filter((col) => col.visible)
.map((col) => ({
prop: col.prop,
label: col.label,
width: col.width,
fixed: col.fixed,
}));
emit("columns-change", visibleColumns);
};
const handleDragEnd = () => {
currentColumns.value = currentColumns.value.map((col, index) => ({
...col,
order: index,
}));
};
const savePreferences = () => {
const dataToSave = currentColumns.value.map((col) => ({
prop: col.prop,
visible: col.visible,
order: col.order,
width: col.width,
fixed: col.fixed,
}));
localStorage.setItem(props.storageKey, JSON.stringify(dataToSave));
showControl.value = false;
ElMessage.success("配置已保存");
};
const resetToDefault = () => {
currentColumns.value = JSON.parse(JSON.stringify(defaultColumns.value));
localStorage.removeItem(props.storageKey);
ElMessage.success("已恢复默认配置");
};
watch(
currentColumns,
() => {
updateVisibleColumns();
},
{ deep: true }
);
onMounted(() => {
initializeColumns();
});
</script>
<style scoped>
.column-item {
padding: 8px 12px;
margin: 4px 0;
background: #f5f7fa;
border-radius: 4px;
cursor: move;
display: flex;
align-items: center;
}
</style>
组件的使用
javascript
<template>
<div class="about">
<div>
<column-control
:columns="tableColumns"
storage-key="user-table-preferences"
@columns-change="handleColumnsChange"
/>
<el-table :data="tableData" :key="tableKey">
<el-table-column
v-for="col in visibleColumns"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:fixed="col.fixed"
/>
</el-table>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import ColumnControl from "./ColumnVisibilityControl.vue";
// 表格数据
const tableData = ref([
{
name: "张三",
age: 25,
address: "北京",
sex: "男",
type: "A",
phone: "1234567890",
},
{
name: "李四",
age: 30,
address: "上海",
sex: "女",
type: "B",
phone: "1234567890",
},
{
name: "王五",
age: 28,
address: "重庆",
sex: "女",
type: "C",
phone: "1234567890",
},
{
name: "王刘",
age: 33,
address: "广州",
sex: "女",
type: "C",
phone: "1234567890",
},
{
name: "王气",
age: 88,
address: "深圳",
sex: "男",
type: "D",
phone: "1234567890",
},
]);
// 列配置
const tableColumns = [
{ prop: "name", label: "姓名", visible: true, width: 120, fixed: false },
{ prop: "age", label: "年龄", visible: true, width: 100, fixed: false },
{ prop: "address", label: "地址", visible: true, width: 120, fixed: false },
{ prop: "sex", label: "性别", visible: true, width: 120, fixed: false },
{ prop: "type", label: "类别", visible: true, width: 120, fixed: false },
{ prop: "phone", label: "电话", visible: true, width: 120, fixed: false },
];
// 可见列
const visibleColumns = ref([]);
const tableKey = ref(0);
// 列配置变化回调
const handleColumnsChange = (visibleCols) => {
visibleColumns.value = visibleCols;
tableKey.value += 1; // 强制刷新表格
};
</script>
<style>
@media (min-width: 1024px) {
.about {
min-height: 100vh;
display: flex;
align-items: center;
}
}
</style>
效果图展示


