用vue+element ui开发管理系统时,使用el-table做表格,当表格列过多的时候,想要做成可选表头的,实现表格列的筛选显示,效果如下:
代码文件结构:
废话不多说,直接上代码:
第一步:新建名为 TableHeaderRender.vue 的文件
placement="bottom"
width="200"
trigger="manual"
v-model="visible"
@show="showPopover"
popper-class="table-header-popover"
>
placeholder="请输入内容"
v-model="value"
size="small"
clearable
@keyup.enter.native="confirm"
ref="sInput"
>
slot="reference"
style="margin-left: 5px"
@click.stop="popClick"
v-click-outside="closeOver"
>
class="filter-icon el-icon-search"
:style="{ color: iconColor ? '#9a4b9b' : '#909399' }"
>
export default {
name: "tableHeaderRender",
data() {
return {
// input 绑定的值
value: "",
visible: false,
iconColor: false,
};
},
props: {
tableColumn: {
type: Object,
default: () => {},
},
columnProp: {
type: String,
default: "",
},
defaultValue: {
type: String,
default: "",
},
inputFilteredMap: {
type: Object,
default: () => {},
},
},
created() {
this.value = this.defaultValue;
this.iconColor = this.value.trim() ? true : false;
},
methods: {
showPopover() {
this.$nextTick(() => {
this.$refs.sInput.focus();
});
},
resetData() {
console.log("reset");
this.value = "";
this.visible = false;
this.iconColor = false;
const self = this;
if (this.inputFilteredMap[self.columnProp]) {
delete this.inputFilteredMap[self.columnProp];
}
self.$emit("resetChangeMethod", this.tableColumn, self.columnProp);
},
closeOver() {
this.visible = false;
},
popClick(e) {
// e.stopPropagation()
this.visible = !this.visible;
},
confirm() {
this.visible = false;
if (this.value.trim()) {
this.iconColor = true;
this.inputFilteredMap[this.columnProp] = this.value;
this.$emit(
"filterInputMethod",
this.tableColumn,
this.inputFilteredMap
);
} else {
// 如果搜索input输入为空,等同重置
this.resetData();
}
},
},
directives: {
clickOutside: {
bind(el, binding, vnode) {
function clickHandler(e) {
// 这里判断点击的元素是否是本身,是本身,则返回
if (el.contains(e.target)) {
return false;
}
// 判断指令中是否绑定了函数
if (binding.expression) {
// 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
binding.value(e);
}
}
// 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
el.__vueClickOutside__ = clickHandler;
document.addEventListener("click", clickHandler);
},
update() {},
unbind(el, binding) {
// 解除事件监听
document.removeEventListener("click", el.__vueClickOutside__);
delete el.__vueClickOutside__;
},
},
},
};
.filter-icon {
font-size: 14px;
color: #909399;
cursor: pointer;
font-weight: 400;
}
.table-header-popover {
padding: 0;
}
.table-header-popover .table-header-popover-template {
margin: 10px;
}
第二步:新建名为 operateTable.vue 的文件
size="small"
trigger="click"
v-if="options.columnsSelect || options.columnsTreeSelect"
class="column-dropdown"
>
v-if="isInit"
size="small"
type="primary"
plain
style="width:70px;"
@click="initColumns(true)"
>初始化
v-if="isSave"
size="small"
type="primary"
plain
style="width:70px;"
@click="$emit('saveSettingColumns',checkedList)"
>保存
Select All
v-if="!options.columnsTreeSelect"
v-model="checkedList"
style="max-height: 300px"
@change="handleCheckedCitiesChange"
>
-1">
:key="index"
class="checkbox"
:label="option"
>
:key="index"
class="checkbox"
:label="option"
>
ref="tree"
:check-on-click-node="true"
:data="treeColumn"
show-checkbox
node-key="label"
:default-expanded-keys="defaultExpanded"
:default-checked-keys="defaultChecked"
:props="{
children: 'children',
label: 'label',
}"
@check="checkChange"
style="max-height: 300px; overflow-y: auto"
>
id="iTable"
ref="operateTable"
border
:data="dataSource"
:stripe="options.stripe"
:highlight-current-row="options.highlightCurrentRow"
:max-height="options.maxHeight"
:size="options.size"
:fit="options.fit"
:show-header="options.showHeader"
:empty-text="options.emptyText"
:default-sort="options.defaultSort"
:row-key="getRowKeys"
:default-expand-all="options.defaultExpandAll"
:tree-props="options.treeProps"
:lazy="options.lazy"
:load="load"
@cell-mouse-enter="cellMouseEnter"
@cell-mouse-leave="cellMouseLeave"
@cell-click="cellClick"
@cell-dblclick="cellDblclick"
@row-click="rowClick"
@row-contextmenu="rowContextmenu"
@row-dblclick="rowDblclick"
@header-click="headerClick"
@header-contextmenu="headerContextmenu"
@sort-change="sortChange"
@select="select"
@select-all="selectAll"
@selection-change="checkboxSelected"
@filter-change="filterChange"
>
:reserve-selection="options.reserveSelection"
:key="0"
type="selection"
:selectable="selectable"
width="40"
align="left"
v-if="options.showCheckBox"
:resizable="false"
>
ref="fixedColumn"
label="操作"
align="left"
:width="operates.dropDown ? '50' : operates.width"
:fixed="operates.fixed"
:min-width="operates.minwidth"
:resizable="operates.resizable"
v-if="operates.list.length > 0"
>
class="operate-group"
v-if="!operates.dropDown && !operates.isText"
>
v-for="item in operates.list[0] instanceof Array
? operates.list[scope.$index]
: operates.list"
>
v-if="item.type === 'switch'"
v-model="scope.row[item.prop]"
active-color="#13ce66"
@change="item.method(scope.$index, scope.row)"
>
v-else-if="item.type === 'tooltipIcon'"
:enterable="false"
effect="light"
placement="bottom"
>
{{ item.tooltip }}
type="primary"
plain
:icon="item.icon"
size="mini"
:disabled="item.disabled"
@click="item.method(scope.$index, scope.row)"
>
v-else-if="item.type === 'icon'"
type="primary"
plain
:icon="item.icon"
size="mini"
:disabled="item.disabled"
@click="item.method(scope.$index, scope.row)"
>
class="operate-group"
v-if="!operates.dropDown && operates.isText"
>
v-for="item in operates.list[0] instanceof Array
? operates.list[scope.$index]
: operates.list"
>
size="small"
type="text"
:disabled="item.disabled"
@click.native.prevent="item.method(scope.$index, scope.row)"
>{{ item.label }}
>
@command="handleCommand"
trigger="hover"
placement="bottom-start"
>
v-for="(item, index) in operates.list[0] instanceof Array
? operates.list[scope.$index]
: operates.list"
:disabled="item.disabled"
:key="index"
:command="composeValue(item, scope.row, scope.$index)"
>{{ item.label }}
>
v-if="
options.columnsSelect || options.columnsTreeSelect
? checkedList.includes(column.label)
: true
"
:prop="column.prop"
:key="column.label"
:label="column.label"
align="left"
:width="column.width"
:min-width="column.minwidth"
:resizable="column.resizable"
:sortable="column.sortable"
:filters="column.filters"
:filter-method="column.filters ? column.filterMethod : undefined"
:filtered-value="column.filteredValue"
:fixed="column.fixed"
:column-key="column.prop"
>
>{{ column.label }}
v-if="column.filterInput"
:columnProp="column.prop"
:tableColumn="scope.column"
:defaultValue="column.defaultValue"
:inputFilteredMap="inputFilteredMap"
@filterInputMethod="filterInputMethod"
@resetChangeMethod="resetChangeMethod"
>
{{ scope.row[column.prop] }}
:column="column"
:row="scope.row"
:render="column.render"
:index="scope.$index"
>
import TableHeaderRender from "./TableHeaderRender.vue";
export default {
name: "OperateTable",
props: {
// 表格的数据源
dataSource: {
type: Array,
default: () => [],
},
// 需要展示的列
columns: {
type: Array,
default: () => [{}],
},
// table 表格的控制参数
options: {
type: Object,
default: () => {
return {
stripe: true, // 是否为斑马纹 table
};
},
},
// 操作按钮组 === label: 文本,show:是否显示,icon:按钮图标,disabled:是否禁用,method:回调方法, 等等
operates: {
type: Object,
default: () => {
return {
list: [],
};
},
},
defaultSelectedColumn: {
type: Array,
default: () => [],
},
defaultColumn: {
type: Array,
default: () => [],
},
totalColumn: {
type: Array,
default: () => [],
},
treeColumn: {
type: Array,
default: () => [],
},
defaultChecked: {
type: Array,
default: () => [],
},
defaultExpanded: {
type: Array,
default: () => [],
},
isInit: {
type: Boolean,
default: false
},
isSave: {
type: Boolean,
default: false
}
},
components: {
TableHeaderRender,
expandDom: {
functional: true,
props: {
row: Object,
render: Function,
index: Number,
column: {
type: Object,
default: null,
},
},
render: (h, ctx) => {
const params = {
row: ctx.props.row,
index: ctx.props.index,
};
if (ctx.props.column) params.column = ctx.props.column;
return ctx.props.render(h, params);
},
},
},
data() {
return {
cloumnKeyword:"",//字段关键字搜索
isIndeterminate:true,//全选状态
checkAll:false,//字段全选
checkBoxOptions: [], // 全部表头
checkedList: [], // 选中表头
count: 0, // 用于判断表格是否刚渲染
isCheckBoxSort: false, // 用于判断是否是由自定义数据列引发的排序
// 以下是之前放于vuex中用于记录状态
preCheckedList: [], // 前一次的checkbox
// 排序的状态
sort: {
prop: "",
order: "",
label: "",
},
// 筛选的状态
checkBoxFilteredMap: {},
// input 所有的筛选
inputFilteredMap: {},
// columns label及prop对应的键值对
columnsLabelMap: {}
};
},
watch: {
// 监听defaultColumn,若这个发生变化,代表传入的默认column变化,即数据表格发生了切换
defaultColumn() {
this.initColumns();
},
checkedList() {
// if(this.checkedList.length>0){
// this.$emit("selectedColumn",this.checkedList);
// }
// 处理当点击checkbox显示的是排序列时,恢复排序列的显示
let showLabelArray = this.checkboxShowLabel();
console.log("showLabelArray", showLabelArray);
// if (value.length !== 0) {
// value.map((item) => {
// this.handleStatusRevert(item);
// });
this.columns.map((column, index) => {
this.handleStatusRevert(column, index, showLabelArray);
});
},
},
created() {
this.normalizeColumnsLabelMap();
},
mounted() {
if (!this.options.columnsTreeSelect) {
this.checkedList = this.$props.defaultColumn;
this.checkBoxOptions = this.$props.totalColumn;
} else {
this.checkedList = this.$refs.tree
.getCheckedNodes()
.map((item) => item.label);
}
// 挂载时将defaultSort属性传给vuex
this.handleDefaultSort();
},
// 动态切换表头的时候闪烁是因为表头重新计算高度导致的,以下方法解决此bug
beforeUpdate() {
this.$nextTick(() => {
//在数据加载完,重新渲染表格
this.$refs["operateTable"].doLayout();
});
},
methods: {
//全选字段
handleCheckAllChange(val){
this.checkedList = val ? this.checkBoxOptions : [];
this.isIndeterminate = false;
},
//反选
handleCheckedCitiesChange(value){
let checkedCount = value.length;
this.checkAll = checkedCount === this.checkBoxOptions.length;
this.isIndeterminate = checkedCount > 0 && checkedCount < this.checkBoxOptions.length;
},
//初始化字段
initColumns(flag){
if(flag){
this.checkedList = this.$props.defaultSelectedColumn;
}else{
this.checkedList = this.$props.defaultColumn;
}
this.checkBoxOptions = this.$props.totalColumn;
},
// 处理判断checkbox 点击时,显示的是哪个字段名
checkboxShowLabel() {
// 判断显示的字段是哪个字段,如果是树形,可能有多个,故更改为数组接收
let value = [];
console.log("this.checkedList", this.checkedList);
if (this.count === 0) {
this.saveCheckedList(this.checkedList);
// this.$componentsStore.commit("table/saveCheckedList", this.checkedList);
this.count++;
} else {
if (this.checkedList.length > this.preCheckedList.length) {
for (let index = 0; index < this.checkedList.length; index++) {
if (!this.preCheckedList.includes(this.checkedList[index])) {
value.push(this.checkedList[index]);
}
// if (this.checkedList[index] !== this.preCheckedList[index]) {
// value = this.checkedList[index];
// }
}
}
this.saveCheckedList(this.checkedList);
// this.$componentsStore.commit("table/saveCheckedList", this.checkedList);
}
return value;
},
// 处理sort\filterd由隐藏变为显示状态的恢复
handleStatusRevert(column, index, showLabelArray) {
let compareLabel = column.label;
if (showLabelArray.includes(compareLabel)) {
// 如果是有checkbox 筛选的字段,恢复筛选
let filteredValue =
this.checkBoxFilteredMap[this.columnsLabelMap[compareLabel]];
// 如果是有input 筛选的字段,恢复筛选
let filteredInputValue =
this.inputFilteredMap[this.columnsLabelMap[compareLabel]];
this.columns[index].filteredValue = filteredValue;
this.columns[index].defaultValue = filteredInputValue;
this.$nextTick(() => {
this.$refs.operateTable.store.states.columns.map((column) => {
if (column.filteredValue && column.filteredValue.length) {
this.$refs.operateTable.store.commit("filterChange", {
column,
values: column.filteredValue,
silent: true,
});
}
});
});
} else {
this.columns[index].filteredValue = [];
this.columns[index].defaultValue = "";
}
// 如果是有排序的字段,恢复排序
if (showLabelArray.includes(this.sort.label)) {
this.$nextTick(() => {
//在数据加载完,重新渲染表格
this.isCheckBoxSort = true;
this.$refs.operateTable.sort(this.sort.prop, this.sort.order);
});
}
/**
// 如果是有checkbox 筛选的字段,恢复筛选
let filteredValue = this.checkBoxFilteredMap[this.columnsLabelMap[value]];
// 如果是有input 筛选的字段,恢复筛选
let filteredInputValue = this.inputFilteredMap[
this.columnsLabelMap[value]
];
for (let i = 0; i < this.columns.length; i++) {
if (this.columns[i].label === value) {
this.columns[i].filteredValue = filteredValue;
this.columns[i].defaultValue = filteredInputValue;
this.$nextTick(() => {
this.$refs.operateTable.store.states.columns.map((column) => {
if (column.filteredValue && column.filteredValue.length) {
console.log("!11");
this.$refs.operateTable.store.commit("filterChange", {
column,
values: column.filteredValue,
silent: true,
});
}
});
});
} else {
this.columns[i].filteredValue = [];
this.columns[i].defaultValue = "";
}
}
// for (let i = 0; i < this.columns.length; i++) {
// if (this.columns[i].label === value) {
// this.columns[i].defaultValue = filteredInputValue;
// } else {
// this.columns[i].defaultValue = "";
// }
// }
// 如果是有排序的字段,恢复排序
if (value === this.sort.label) {
this.$nextTick(() => {
//在数据加载完,重新渲染表格
this.isCheckBoxSort = true;
this.$refs.operateTable.sort(this.sort.prop, this.sort.order);
});
}
*/
},
// 处理生成columns 的label prop键值对
normalizeColumnsLabelMap() {
this.columns.map((column) => {
this.columnsLabelMap[column.label] = column.prop;
});
},
filterInputMethod(column, inputFilteredMap) {
console.log("column, inputFilteredMap", column, inputFilteredMap);
this.inputFilteredMap = inputFilteredMap;
this.$emit("filterInputMethod", column, inputFilteredMap);
},
resetChangeMethod(tableColumn, columnProp) {
this.$emit("resetChangeMethod", tableColumn, this.inputFilteredMap);
},
cellMouseEnter(row, column, cell, event) {
this.$emit("cell-mouse-enter", row, column, cell, event);
},
cellMouseLeave(row, column, cell, event) {
this.$emit("cell-mouse-leave", row, column, cell, event);
},
cellClick(row, column, cell, event) {
this.$emit("cell-click", row, column, cell, event);
},
cellDblclick(row, column, cell, event) {
this.$emit("cell-dblclick", row, column, cell, event);
},
rowClick(row, column, event) {
this.$emit("row-click", row, column, event);
},
rowContextmenu(row, column, event) {
this.$emit("row-contextmenu", row, column, event);
},
rowDblclick(row, column, event) {
this.$emit("row-dblclick", row, column, event);
},
headerClick(column, event) {
this.$emit("header-click", column, event);
},
headerContextmenu(column, event) {
this.$emit("header-contextmenu", column, event);
},
sortChange(sortObj) {
this.changeSort(sortObj);
// this.$componentsStore.commit("table/changeSort", sortObj);
if (!this.isCheckBoxSort) {
this.$emit("sort-change", sortObj);
}
// 还原isCheckBoxSort
this.isCheckBoxSort = false;
},
handleDefaultSort() {
if (this.options.defaultSort !== undefined) {
let column = { label: "" };
// for (let index = 0; index < this.columns.length; index++) {
// if (this.columns[index].prop === this.options.defaultSort.prop) {
// column.label = this.columns[index].label;
// break;
// }
// }
column.label = this.columnsLabelMap[this.options.defaultSort.prop];
this.changeSort({
...this.options.defaultSort,
column,
});
}
},
// 点击操作的下拉项目后触发事件
handleCommand(command) {
if (command.method) {
command.method(command.index, command.row, command.label);
}
},
// 点击dropDown传参方法
composeValue(item, row, index) {
return {
label: item.label,
row: row,
index: index,
method: item.method,
};
},
select(selection, row) {
this.$emit("select", selection, row);
},
selectAll(selection) {
this.$emit("select-all", selection);
},
checkboxSelected(selection) {
this.$emit("selection-change", selection);
},
selectable(row, index) {
let data = true;
this.$emit("selectable", row, index, (val) => {
data = val;
});
return data;
},
getRowKeys(row) {
let data;
for (let index = 0; index < this.dataSource.length; index++) {
if (row === this.dataSource[index]) {
data = index;
break;
}
}
// this.dataSource.map((item, index) => {
// if (row === item) {
// data = index;
// }
// });
this.$emit("row-key", row, (val) => {
data = val;
});
return data;
},
load(tree, treeNode, resolve) {
this.$emit("load", tree, treeNode, resolve);
},
// 记录表格总的筛选状态,用于列显示隐藏时保存checkbox状态
filterChange(filters) {
let currKey = Object.keys(filters)[0];
if (filters[currKey].length === 0) {
delete this.checkBoxFilteredMap[currKey];
} else {
this.checkBoxFilteredMap[currKey] = filters[currKey];
}
this.$emit("filter-change", filters);
},
checkChange(nodeObj, checkObj) {
this.checkedList = checkObj.checkedNodes.map((item) => {
return item.label;
});
},
// 之前写在vuex里的方法
changeSort(sort) {
this.sort.prop = sort.prop;
this.sort.order = sort.order;
this.sort.label = sort.column.label;
},
saveCheckedList(preCheckedList) {
this.preCheckedList = preCheckedList;
},
},
};
.operateTable{
position: relative;
width: 100%;
}
.operateTable .column-dropdown{
position: absolute;
right: 0px;
top: -42px;
z-index: 99;
}
.caoz_ft_warp{
text-align: center;
}
.caoz_ft_warp .el-input.is-active .el-input__inner, .caoz_ft_warp .el-input__inner:focus{
border-color: #9A4B9B;
outline: 0;
}
.el-checkbox__input.is-checked .el-checkbox__inner,.el-checkbox__input.is-indeterminate .el-checkbox__inner {
background-color: #9a4b9b;
border-color: #9a4b9b;
}
.el-checkbox__inner:hover {
border-color: #9A4B9B;
}
.el-checkbox__input.is-focus .el-checkbox__inner{
border-color: #9A4B9B;
}
.el-checkbox__input.is-checked+.el-checkbox__label {
color: #9A4B9B;
}
.checkboxScorll .el-checkbox__input.is-checked + .el-checkbox__label {
color: #666;
}
.column-dropdown .el-button{margin-right: 0 !important;min-width:0;}
.column-dropdown .el-button:focus, .el-button:hover{
color: #9A4B9B;
border-color: #e1c9e1;
background-color: #f5edf5;
}
.checkboxScorll {
max-height: 300px;
overflow-y: auto;
}
.checkboxScorll .checkbox {
display: block;
margin-top: 10px;
padding-left: 20px;
}
第三步:在页面中引入operateTable并使用
v-loading="loading"
:dataSource="operateTableData"
:columns="operateTableColumns"
:options="operateTableOption"
:defaultColumn="defaultColumns"
:totalColumn="totalColumns"
:defaultSelectedColumn="defaultSelectedColumn"
@sort-change="sortChange"
@saveSettingColumns="saveSettingColumns"
>
import operateTable from "./components/operateTable.vue";
export default {
name: "",
components: {
operateTable,
},
data() {
return {
loading: false,
operateTableData: [
{
date: "2016-05-02",
name: "王小虎",
address: "上海市普陀区金沙江路 1518 弄",
},
{
date: "2016-05-04",
name: "王小虎",
address: "上海市普陀区金沙江路 1517 弄",
},
{
date: "2016-05-01",
name: "王小虎",
address: "上海市普陀区金沙江路 1519 弄",
},
{
date: "2016-05-03",
name: "王小虎",
address: "上海市普陀区金沙江路 1516 弄",
},
],
operateTableColumns: [
{
prop: "action",
label: "操作",
width: 100,
fixed: true,
render: (h, params) => {
return h(
"div",
{
class: "operate-group",
},
[
h(
"el-tooltip",
{
props: {
content: "处理",
placement: "bottom",
enterable: false,
effect: "light",
},
class: "item",
},
[
h("i", {
props: {},
class: "el-icon-edit",
on: {
click: () => {
console.log(params.row);
},
},
}),
]
),
]
);
},
},
{
prop: "date",
label: "日期",
minwidth: 150,
sortable: "custom",
},
{
prop: "name",
label: "姓名",
minwidth: 150,
sortable: "custom",
},
{
prop: "address",
label: "地址",
minwidth: 150,
sortable: "custom",
},
],
operateTableOption: {
stripe: true, // 是否为斑马纹 table
highlightCurrentRow: true, // 是否要高亮当前行
columnsSelect: true,
maxHeight: 300,
},
defaultColumns: ["操作", "日期", "姓名", "地址"],
totalColumns: ["操作", "日期", "姓名", "地址"],
//所有用户默认勾选的列 用于初始化
defaultSelectedColumn: [],
};
},
methods: {
//表头排序
sortChange(column, prop, order) {
if (column.order === "ascending") {
this.orderfield = column.prop;
this.orderby = "ASC";
} else if (column.order === "descending") {
this.orderfield = column.prop;
this.orderby = "DESC";
} else {
this.orderfield = "";
this.orderby = "";
}
// this.getTabldHandle();
},
//保存自定义字段
saveSettingColumns(data) {
console.log(data);
},
},
mounted() {},
};
.tableView {
width: 100%;
height: 100%;
}
.content {
padding: 60px;
}
.disableIcon {
color: #c0c4cc;
cursor: not-allowed;
}