组件核心功能
-
拖拽排序(使用
vuedraggable
) -
显示/隐藏控制
-
列宽调整
-
列固定状态记忆
-
搜索过滤列
-
本地存储(localStorage)可改成接口保存
-
默认配置恢复
-
通过
searchText
动态过滤列。
安装拖拽依赖
npm install vuedraggable
-
如果需要支持后端存储,可以在
savePreferences
方法中添加 API 调用。 -
确保
columns
配置中包含prop
和label
字段。
创建组件ColumnVisibilityControl.vue
<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-inputv-model="searchText"placeholder="搜索列"clearablestyle="margin-bottom: 16px"/><draggable v-model="currentColumns" item-key="prop" @end="handleDragEnd"><template #item="{ element: col }"><divv-if="col.label.toLowerCase().includes(searchText.toLowerCase())"class="column-item"><el-checkbox v-model="col.visible">{{ col.label }}</el-checkbox><el-input-numberv-if="col.visible"v-model="col.width":min="50":max="500"style="margin-left: 16px"placeholder="列宽"/><el-checkboxv-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>
组件的使用
<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-columnv-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>
效果图展示