前段时间在el-table 源码加了个列显隐功能,后面老大说用户自定义列表也是一个特点,我就想了下,之前看过别人写的都是动态去渲染el-table-column标签,但是在我们项目中功能页面都是饱和状态,去做动态渲染,改动成本太大,而且标签中大多都是使用formatter,不是理想,就想着在源码上去动手脚。
- 使用 sortablejs
- 支持本地存储列和列宽
- 对 el-table 源码进行改动
改动文件目录
-
el-table
- table.vue
- table-header.js
- store
- index.js
具体效果如下,屏蔽了一些拖拽的列【selection、expand、index,操作,二级菜单】
以上列出都不支持拖拽也不支持拖拽到这里,保证表格基本布局信息。
改动这个和上次一样,需要将==el-table== 源码拉出来,不使用extends方式,这是因为改不到子文件上去。==前面步骤==: 给el-table实现列显隐
覆盖注册组件
javascript
import ElementUI from "element-ui"
import "element-ui/lib/theme-chalk/index.css";
Vue.use(ElementUI);
import ElTable from '@/components/element-ui/ElTable';
import ElTableColumn from "@/components/element-ui/el-table-column";
Vue.component(ElTable.name, ElTable); // 这个步骤同等于对象重新赋值的操作
Vue.component(ElTableColumn.name, ElTableColumn);
项目中将抛出3个关联钩子
钩子 | 备注 | 描述 |
---|---|---|
@header-cell-dragend="headerCellDragend" | 列被拖拽的钩子 | column被拖拽 |
@table-ready="tableReady" | 初始化完成 | 可以对ElTable进行操作 |
@header-dragend="headerDragend" | ElTable原生事件钩子 | 由于是扩展,原参数基础加了一个参数 |
项目中使用方式
javascript
<template>
<el-row class="mt-4">
<el-col :span="12" :push="2">
<el-table size="mini" :data="table.data" border stripe :height="300" :header-cell-style="{ background: '#e9d5ff' }" @header-cell-dragend="headerCellDragend" @table-ready="tableReady" @header-dragend="headerDragend">
<el-table-column type="selection" />
<el-table-column type="index" label="#" />
<el-table-column align="center" prop="ip" label="IP" />
<el-table-column align="center" prop="type" label="操作类型" />
<el-table-column align="center" label="动作">
<el-table-column align="center" prop="count" label="总访问次数" />
<el-table-column align="center" prop="visitCount" label="访问次数" />
</el-table-column>
<el-table-column align="center" label="操作">
<template slot-scope="scope">
<el-button type="text" size="mini">查看</el-button>
<el-button type="text" size="mini">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-col>
</el-row>
</template>
<script>
function localstorageSet(key, obj = {}) {
if (!key) return;
localStorage.setItem(key, JSON.stringify(obj));
}
function localstorageGet(key) {
if (!key) return;
return JSON.parse(localStorage.getItem(key) || "{}");
}
export default {
name: "Demo",
data() {
return {
table: {
data: Array.from({ length: 10 }, (v, k) => ({
ip: "192.178.2.12",
count: Math.floor(Math.random() * 100),
type: "登录",
visitCount: "访问次数",
})),
},
};
},
methods: {
headerDragend(newWidth, _oldWidth, column, _event, columns) {
const name = this.$options.name;
const tableMap = localstorageGet("ElTable");
const BBQ = `${name}`;
if (!tableMap[BBQ]) tableMap[BBQ] = columns;
tableMap[BBQ].forEach((item) => {
if (item.label === column.label) item.width = newWidth;
});
localstorageSet("ElTable", tableMap);
},
tableReady(table) {
const tableMap = localstorageGet("ElTable");
const name = this.$options.name;
const BBQ = `${name}`;
table.changeExistingSort(tableMap[BBQ]);
},
headerCellDragend(columns) {
const name = this.$options.name;
const tableMap = localstorageGet("ElTable");
const BBQ = `${name}`; // 本来想和tableId串起来,但是做法不太行
if (!tableMap[BBQ]) tableMap[BBQ] = columns;
localstorageSet("ElTable", tableMap);
/** 组件名称作为 Key
* ElTable: {
* Demo: [
* {"width": 48},
* {"label": "#","width": 48},
* {"label": "IP","width": 129},
* {"label": "总访问次数"},
* {"width": 48},
* {"label": "操作"}
* ]
* }
*/
},
},
};
</script>
table 组件中 (./el-table/table.vue)
javascript
<script>
export default {
name: "ElTable",
mounted() {
...
,
this.$ready = true;
this.$emit("table-ready", this);
},
methods: {
...
,
changeExistingSort(columns) {
this.store.commit("updateColumnsCell", columns);
},
}
}
</script>
store/index.js (./el-table/store/index.js)
javascript
Watcher.prototype.mutations = {
// ...其他代码
// 根据传入的列,更新列和列宽
updateColumnsCell(states, cache) {
if (!Array.isArray(cache)) return;
const { _columns } = states;
_columns.sort((item1, item2) => {
const aa = cache.findIndex((elem) => elem.label === item1.label);
if (aa === -1) return 1;
const bb = cache.findIndex((elem) => elem.label === item2.label);
return aa - bb;
});
_columns.forEach((item) => {
const corresponding = cache.find((x) => x.label === item.label);
if (corresponding && corresponding.width && corresponding.width !== item.width) {
item.width = corresponding.width;
}
});
this.scheduleLayout();
this.updateColumns();
},
};
Watcher.prototype.commit = function (name, ...args) {...
};
Watcher.prototype.updateTableScrollY = function () {...
};
export default Watcher;
table-header (./el-table/table-header.js)
改动比较大,建议直接复制文件,我会分为两部分,一部分是仅添加代码,一部分是完整代码
table-header (./el-table/table-header.js) 【改动代码】
javascript
// ...其他代码
import Sortable from "sortablejs";
export default {
name: "ElTableHeader",
render(h) {
// ...其他代码
// 1. tr 上 加入 了 sortableRef_rowIndex 的 ref 属性
// 2. th 上 加入 了 on-mouseover={($event) => this.handleMouseOver($event)} 事件
return (
<table class="el-table__header" cellspacing="0" cellpadding="0" border="0">
<colgroup>
{this.columns.map((column) => (
<col name={column.id} key={column.id} />
))}
{this.hasGutter ? <col name="gutter" /> : ""}
</colgroup>
<thead class={[{ "is-group": isGroup, "has-gutter": this.hasGutter }]}>
{this._l(columnRows, (columns, rowIndex) => (
<tr ref={"sortableRef_" + rowIndex} style={this.getHeaderRowStyle(rowIndex)} class={this.getHeaderRowClass(rowIndex)}>
{columns.map((column, cellIndex) => (
<th colspan={column.colSpan} rowspan={column.rowSpan} on-mouseover={($event) => this.handleMouseOver($event)} on-mousedown={($event) => this.handleMouseDown($event, column)} on-mousemove={($event) => this.handleMouseMove($event, column)} on-mouseout={this.handleMouseOut} on-click={($event) => this.handleHeaderClick($event, column)} on-contextmenu={($event) => this.handleHeaderContextMenu($event, column)} style={this.getHeaderCellStyle(rowIndex, cellIndex, columns, column)} class={this.getHeaderCellClass(rowIndex, cellIndex, columns, column)} key={column.id}>
<div class={["cell", column.filteredValue && column.filteredValue.length > 0 ? "highlight" : "", column.labelClassName]}>
{column.renderHeader ? column.renderHeader.call(this._renderProxy, h, { column, $index: cellIndex, store: this.store, _self: this.$parent.$vnode.context }) : column.label}
{column.sortable ? (
<span class="caret-wrapper" on-click={($event) => this.handleSortClick($event, column)}>
<i class="sort-caret ascending" on-click={($event) => this.handleSortClick($event, column, "ascending")}></i>
<i class="sort-caret descending" on-click={($event) => this.handleSortClick($event, column, "descending")}></i>
</span>
) : (
""
)}
{column.filterable ? (
<span class="el-table__column-filter-trigger" on-click={($event) => this.handleFilterClick($event, column)}>
<i class={["el-icon-arrow-down", column.filterOpened ? "el-icon-arrow-up" : ""]}></i>
</span>
) : (
""
)}
</div>
</th>
))}
{this.hasGutter ? <th class="el-table__cell gutter"></th> : ""}
</tr>
))}
</thead>
</table>
);
},
mounted() {
this.$nextTick(() => {
// ...其他代码
const _this = this;
// 只针对第一行表头进行拖拽
new Sortable(this.$refs.sortableRef_0, {
animation: 150,
draggable: "th",
direction: "horizontal",
swapThreshold: 0.5,
clone: false,
supportPointer: false,
filter: function (e, target) {
const { _columns } = _this.store.states;
const findItem = _columns.find((item) => target.classList.contains(item.id));
if (["selection", "index", "expand"].includes(findItem?.type) || ["#", "操作"].includes(findItem?.label)) {
return true;
}
const rect = target.getBoundingClientRect();
const { clientX } = e;
const left = clientX - rect.left;
const right = rect.right - clientX;
const padding = 30;
if (left < padding || right < padding) {
return true;
}
return false;
},
onMove: this.onSortableMove,
onEnd: this.onSortableEnd,
});
});
},
methods: {
// ...其他代码
// 禁止移动到固定列
onSortableMove(evt) {
const { _columns } = this.store.states;
const related = evt.related;
const findItem = _columns.find((item) => related.classList.contains(item.id));
if (["selection", "index", "expand"].includes(findItem?.type) || ["#", "操作"].includes(findItem?.label)) {
return false;
}
return true;
},
// 更新列顺序并抛出自定义事件
onSortableEnd(e) {
const { _columns } = this.store.states;
const oldIndex = _columns.findIndex((item) => e.item.classList.contains(item.id));
const target = _columns[oldIndex];
if (oldIndex === -1 || target.level !== 1 || target.fixed) {
e.preventDefault();
return;
}
const newIndex = e.newIndex;
const item = _columns.splice(oldIndex, 1)[0];
_columns.splice(newIndex, 0, item);
this.store.scheduleLayout();
this.store.updateColumns();
this.$parent.$emit(
"header-cell-dragend",
_columns.map((x) => ({ label: x.label, width: x.width }))
);
e.preventDefault();
},
// 给列设置padding边界,防止拖拽和列宽调整冲突
handleMouseOver(event) {
event.stopPropagation();
event.preventDefault();
const target = event.target;
const cell = target.tagName === "TH" ? target : target.parentNode;
function mouseMove(event) {
const { clientX } = event;
const rect = target.getBoundingClientRect();
const left = clientX - rect.left;
const right = rect.right - clientX;
const padding = 30;
if (left < padding || right < padding) return;
document.body.style.cursor = "move";
}
function mouseOut() {
document.removeEventListener("mousemove", mouseMove);
cell.removeEventListener("mouseout", mouseOut);
}
document.addEventListener("mousemove", mouseMove);
cell.addEventListener("mouseout", mouseOut);
},
// 在鼠标弹起发起emit事件参数中追加了一个columns参数,用于拖拽时记录当前表格的所有列名称和宽
handleMouseDown(event, column) {
if (this.$isServer) return;
if (column.children && column.children.length > 0) return;
/* istanbul ignore if */
if (this.draggingColumn && this.border) {
this.dragging = true;
this.$parent.resizeProxyVisible = true;
const table = this.$parent;
const tableEl = table.$el;
const tableLeft = tableEl.getBoundingClientRect().left;
const columnEl = this.$el.querySelector(`th.${column.id}`);
const columnRect = columnEl.getBoundingClientRect();
const minLeft = columnRect.left - tableLeft + 30;
addClass(columnEl, "noclick");
this.dragState = {
startMouseLeft: event.clientX,
startLeft: columnRect.right - tableLeft,
startColumnLeft: columnRect.left - tableLeft,
tableLeft,
};
const resizeProxy = table.$refs.resizeProxy;
resizeProxy.style.left = this.dragState.startLeft + "px";
document.onselectstart = function () {
return false;
};
document.ondragstart = function () {
return false;
};
const handleMouseMove = (event) => {
const deltaLeft = event.clientX - this.dragState.startMouseLeft;
const proxyLeft = this.dragState.startLeft + deltaLeft;
resizeProxy.style.left = Math.max(minLeft, proxyLeft) + "px";
};
const handleMouseUp = () => {
if (this.dragging) {
const { startColumnLeft, startLeft } = this.dragState;
const finalLeft = parseInt(resizeProxy.style.left, 10);
const columnWidth = finalLeft - startColumnLeft;
column.width = column.realWidth = columnWidth;
const { _columns } = this.store.states;
table.$emit(
"header-dragend",
column.width,
startLeft - startColumnLeft,
column,
event,
_columns.map((x) => ({ label: x.label, width: x.width }))
);
this.store.scheduleLayout();
document.body.style.cursor = "";
this.dragging = false;
this.draggingColumn = null;
this.dragState = {};
table.resizeProxyVisible = false;
}
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
document.onselectstart = null;
document.ondragstart = null;
setTimeout(function () {
removeClass(columnEl, "noclick");
}, 0);
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
}
},
},
};
table-header (./el-table/table-header.js) 【完整代码】
javascript
import Vue from "vue";
import { hasClass, addClass, removeClass } from "element-ui/src/utils/dom";
import FilterPanel from "./filter-panel.vue";
import LayoutObserver from "./layout-observer";
import { mapStates } from "./store/helper";
import Sortable from "sortablejs";
const getAllColumns = (columns) => {
const result = [];
columns.forEach((column) => {
if (column.children) {
result.push(column);
result.push.apply(result, getAllColumns(column.children));
} else {
result.push(column);
}
});
return result;
};
const convertToRows = (originColumns) => {
let maxLevel = 1;
const traverse = (column, parent) => {
if (parent) {
column.level = parent.level + 1;
if (maxLevel < column.level) {
maxLevel = column.level;
}
}
if (column.children) {
let colSpan = 0;
column.children.forEach((subColumn) => {
traverse(subColumn, column);
colSpan += subColumn.colSpan;
});
column.colSpan = colSpan;
} else {
column.colSpan = 1;
}
};
originColumns.forEach((column) => {
column.level = 1;
traverse(column);
});
const rows = [];
for (let i = 0; i < maxLevel; i++) {
rows.push([]);
}
const allColumns = getAllColumns(originColumns);
allColumns.forEach((column) => {
if (!column.children) {
column.rowSpan = maxLevel - column.level + 1;
} else {
column.rowSpan = 1;
}
rows[column.level - 1].push(column);
});
return rows;
};
export default {
name: "ElTableHeader",
mixins: [LayoutObserver],
render(h) {
const originColumns = this.store.states.originColumns;
const columnRows = convertToRows(originColumns, this.columns);
console.log(columnRows);
// 是否拥有多级表头
const isGroup = columnRows.length > 1;
if (isGroup) this.$parent.isGroup = true;
return (
<table class="el-table__header" cellspacing="0" cellpadding="0" border="0">
<colgroup>
{this.columns.map((column) => (
<col name={column.id} key={column.id} />
))}
{this.hasGutter ? <col name="gutter" /> : ""}
</colgroup>
<thead class={[{ "is-group": isGroup, "has-gutter": this.hasGutter }]}>
{this._l(columnRows, (columns, rowIndex) => (
<tr ref={"sortableRef_" + rowIndex} style={this.getHeaderRowStyle(rowIndex)} class={this.getHeaderRowClass(rowIndex)}>
{columns.map((column, cellIndex) => (
<th colspan={column.colSpan} rowspan={column.rowSpan} on-mouseover={($event) => this.handleMouseOver($event)} on-mousedown={($event) => this.handleMouseDown($event, column)} on-mousemove={($event) => this.handleMouseMove($event, column)} on-mouseout={this.handleMouseOut} on-click={($event) => this.handleHeaderClick($event, column)} on-contextmenu={($event) => this.handleHeaderContextMenu($event, column)} style={this.getHeaderCellStyle(rowIndex, cellIndex, columns, column)} class={this.getHeaderCellClass(rowIndex, cellIndex, columns, column)} key={column.id}>
<div class={["cell", column.filteredValue && column.filteredValue.length > 0 ? "highlight" : "", column.labelClassName]}>
{column.renderHeader ? column.renderHeader.call(this._renderProxy, h, { column, $index: cellIndex, store: this.store, _self: this.$parent.$vnode.context }) : column.label}
{column.sortable ? (
<span class="caret-wrapper" on-click={($event) => this.handleSortClick($event, column)}>
<i class="sort-caret ascending" on-click={($event) => this.handleSortClick($event, column, "ascending")}></i>
<i class="sort-caret descending" on-click={($event) => this.handleSortClick($event, column, "descending")}></i>
</span>
) : (
""
)}
{column.filterable ? (
<span class="el-table__column-filter-trigger" on-click={($event) => this.handleFilterClick($event, column)}>
<i class={["el-icon-arrow-down", column.filterOpened ? "el-icon-arrow-up" : ""]}></i>
</span>
) : (
""
)}
</div>
</th>
))}
{this.hasGutter ? <th class="el-table__cell gutter"></th> : ""}
</tr>
))}
</thead>
</table>
);
},
data() {
return {
draggingColumn: null,
dragging: false,
dragState: {},
};
},
props: {
fixed: String,
store: {
required: true,
},
border: Boolean,
defaultSort: {
type: Object,
default() {
return {
prop: "",
order: "",
};
},
},
},
created() {
this.filterPanels = {};
},
mounted() {
// nextTick 是有必要的 https://github.com/ElemeFE/element/pull/11311
this.$nextTick(() => {
const { prop, order } = this.defaultSort;
const init = true;
this.store.commit("sort", { prop, order, init });
const _this = this;
new Sortable(this.$refs.sortableRef_0, {
animation: 150,
draggable: "th",
direction: "horizontal",
swapThreshold: 0.5,
clone: false,
supportPointer: false,
filter: function (e, target) {
const { _columns } = _this.store.states;
const findItem = _columns.find((item) => target.classList.contains(item.id));
if (["selection", "index", "expand"].includes(findItem?.type) || ["#", "操作"].includes(findItem?.label)) {
return true;
}
const rect = target.getBoundingClientRect();
const { clientX } = e;
const left = clientX - rect.left;
const right = rect.right - clientX;
const padding = 30;
if (left < padding || right < padding) {
return true;
}
return false;
},
onMove: this.onSortableMove,
onEnd: this.onSortableEnd,
});
});
},
methods: {
onSortableMove(evt) {
const { _columns } = this.store.states;
const related = evt.related;
const findItem = _columns.find((item) => related.classList.contains(item.id));
if (["selection", "index", "expand"].includes(findItem?.type) || ["#", "操作"].includes(findItem?.label)) {
return false;
}
return true;
},
onSortableEnd(e) {
const { _columns } = this.store.states;
const oldIndex = _columns.findIndex((item) => e.item.classList.contains(item.id));
const target = _columns[oldIndex];
if (oldIndex === -1 || target.level !== 1 || target.fixed) {
e.preventDefault();
return;
}
const newIndex = e.newIndex;
const item = _columns.splice(oldIndex, 1)[0];
_columns.splice(newIndex, 0, item);
this.store.scheduleLayout();
this.store.updateColumns();
this.$parent.$emit(
"header-cell-dragend",
_columns.map((x) => ({ label: x.label, width: x.width }))
);
e.preventDefault();
},
handleMouseOver(event) {
event.stopPropagation();
event.preventDefault();
const target = event.target;
const cell = target.tagName === "TH" ? target : target.parentNode;
function mouseMove(event) {
const { clientX } = event;
const rect = target.getBoundingClientRect();
const left = clientX - rect.left;
const right = rect.right - clientX;
const padding = 30;
if (left < padding || right < padding) return;
document.body.style.cursor = "move";
}
function mouseOut() {
document.removeEventListener("mousemove", mouseMove);
cell.removeEventListener("mouseout", mouseOut);
}
document.addEventListener("mousemove", mouseMove);
cell.addEventListener("mouseout", mouseOut);
},
isCellHidden(index, columns) {
let start = 0;
for (let i = 0; i < index; i++) {
start += columns[i].colSpan;
}
const after = start + columns[index].colSpan - 1;
if (this.fixed === true || this.fixed === "left") {
return after >= this.leftFixedLeafCount;
} else if (this.fixed === "right") {
return start < this.columnsCount - this.rightFixedLeafCount;
} else {
return after < this.leftFixedLeafCount || start >= this.columnsCount - this.rightFixedLeafCount;
}
},
getHeaderRowStyle(rowIndex) {
const headerRowStyle = this.table.headerRowStyle;
if (typeof headerRowStyle === "function") {
return headerRowStyle.call(null, { rowIndex });
}
return headerRowStyle;
},
getHeaderRowClass(rowIndex) {
const classes = [];
const headerRowClassName = this.table.headerRowClassName;
if (typeof headerRowClassName === "string") {
classes.push(headerRowClassName);
} else if (typeof headerRowClassName === "function") {
classes.push(headerRowClassName.call(null, { rowIndex }));
}
return classes.join(" ");
},
getHeaderCellStyle(rowIndex, columnIndex, row, column) {
const headerCellStyle = this.table.headerCellStyle;
if (typeof headerCellStyle === "function") {
return headerCellStyle.call(null, {
rowIndex,
columnIndex,
row,
column,
});
}
return headerCellStyle;
},
getHeaderCellClass(rowIndex, columnIndex, row, column) {
const classes = [column.id, column.order, column.headerAlign, column.className, column.labelClassName];
if (rowIndex === 0 && this.isCellHidden(columnIndex, row)) {
classes.push("is-hidden");
}
if (!column.children) {
classes.push("is-leaf");
}
if (column.sortable) {
classes.push("is-sortable");
}
const headerCellClassName = this.table.headerCellClassName;
if (typeof headerCellClassName === "string") {
classes.push(headerCellClassName);
} else if (typeof headerCellClassName === "function") {
classes.push(
headerCellClassName.call(null, {
rowIndex,
columnIndex,
row,
column,
})
);
}
classes.push("el-table__cell");
return classes.join(" ");
},
toggleAllSelection() {
this.store.commit("toggleAllSelection");
},
handleFilterClick(event, column) {
event.stopPropagation();
const target = event.target;
let cell = target.tagName === "TH" ? target : target.parentNode;
if (hasClass(cell, "noclick")) return;
cell = cell.querySelector(".el-table__column-filter-trigger") || cell;
const table = this.$parent;
let filterPanel = this.filterPanels[column.id];
if (filterPanel && column.filterOpened) {
filterPanel.showPopper = false;
return;
}
if (!filterPanel) {
filterPanel = new Vue(FilterPanel);
this.filterPanels[column.id] = filterPanel;
if (column.filterPlacement) {
filterPanel.placement = column.filterPlacement;
}
filterPanel.table = table;
filterPanel.cell = cell;
filterPanel.column = column;
!this.$isServer && filterPanel.$mount(document.createElement("div"));
}
setTimeout(() => {
filterPanel.showPopper = true;
}, 16);
},
handleHeaderClick(event, column) {
if (!column.filters && column.sortable) {
this.handleSortClick(event, column);
} else if (column.filterable && !column.sortable) {
this.handleFilterClick(event, column);
}
this.$parent.$emit("header-click", column, event);
},
handleHeaderContextMenu(event, column) {
this.$parent.$emit("header-contextmenu", column, event);
},
handleMouseDown(event, column) {
if (this.$isServer) return;
if (column.children && column.children.length > 0) return;
/* istanbul ignore if */
if (this.draggingColumn && this.border) {
this.dragging = true;
this.$parent.resizeProxyVisible = true;
const table = this.$parent;
const tableEl = table.$el;
const tableLeft = tableEl.getBoundingClientRect().left;
const columnEl = this.$el.querySelector(`th.${column.id}`);
const columnRect = columnEl.getBoundingClientRect();
const minLeft = columnRect.left - tableLeft + 30;
addClass(columnEl, "noclick");
this.dragState = {
startMouseLeft: event.clientX,
startLeft: columnRect.right - tableLeft,
startColumnLeft: columnRect.left - tableLeft,
tableLeft,
};
const resizeProxy = table.$refs.resizeProxy;
resizeProxy.style.left = this.dragState.startLeft + "px";
document.onselectstart = function () {
return false;
};
document.ondragstart = function () {
return false;
};
const handleMouseMove = (event) => {
const deltaLeft = event.clientX - this.dragState.startMouseLeft;
const proxyLeft = this.dragState.startLeft + deltaLeft;
resizeProxy.style.left = Math.max(minLeft, proxyLeft) + "px";
};
const handleMouseUp = () => {
if (this.dragging) {
const { startColumnLeft, startLeft } = this.dragState;
const finalLeft = parseInt(resizeProxy.style.left, 10);
const columnWidth = finalLeft - startColumnLeft;
column.width = column.realWidth = columnWidth;
const { _columns } = this.store.states;
table.$emit(
"header-dragend",
column.width,
startLeft - startColumnLeft,
column,
event,
_columns.map((x) => ({ label: x.label, width: x.width }))
);
this.store.scheduleLayout();
document.body.style.cursor = "";
this.dragging = false;
this.draggingColumn = null;
this.dragState = {};
table.resizeProxyVisible = false;
}
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
document.onselectstart = null;
document.ondragstart = null;
setTimeout(function () {
removeClass(columnEl, "noclick");
}, 0);
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
}
},
handleMouseMove(event, column) {
if (column.children && column.children.length > 0) return;
let target = event.target;
while (target && target.tagName !== "TH") {
target = target.parentNode;
}
if (!column || !column.resizable) return;
if (!this.dragging && this.border) {
const rect = target.getBoundingClientRect();
const bodyStyle = document.body.style;
if (rect.width > 12 && rect.right - event.pageX < 8) {
bodyStyle.cursor = "col-resize";
if (hasClass(target, "is-sortable")) {
target.style.cursor = "col-resize";
}
this.draggingColumn = column;
} else if (!this.dragging) {
bodyStyle.cursor = "";
if (hasClass(target, "is-sortable")) {
target.style.cursor = "pointer";
}
this.draggingColumn = null;
}
}
},
handleMouseOut() {
if (this.$isServer) return;
document.body.style.cursor = "";
},
toggleOrder({ order, sortOrders }) {
if (order === "") return sortOrders[0];
const index = sortOrders.indexOf(order || null);
return sortOrders[index > sortOrders.length - 2 ? 0 : index + 1];
},
handleSortClick(event, column, givenOrder) {
event.stopPropagation();
const order = column.order === givenOrder ? null : givenOrder || this.toggleOrder(column);
let target = event.target;
while (target && target.tagName !== "TH") {
target = target.parentNode;
}
if (target && target.tagName === "TH") {
if (hasClass(target, "noclick")) {
removeClass(target, "noclick");
return;
}
}
if (!column.sortable) return;
const states = this.store.states;
let sortProp = states.sortProp;
let sortOrder;
const sortingColumn = states.sortingColumn;
if (sortingColumn !== column || (sortingColumn === column && sortingColumn.order === null)) {
if (sortingColumn) {
sortingColumn.order = null;
}
states.sortingColumn = column;
sortProp = column.property;
}
if (!order) {
sortOrder = column.order = null;
} else {
sortOrder = column.order = order;
}
states.sortProp = sortProp;
states.sortOrder = sortOrder;
this.store.commit("changeSortCondition");
},
},
computed: {
table() {
return this.$parent;
},
hasGutter() {
return !this.fixed && this.tableLayout.gutterWidth;
},
...mapStates({
columns: "columns",
isAllSelected: "isAllSelected",
leftFixedLeafCount: "fixedLeafColumnsLength",
rightFixedLeafCount: "rightFixedLeafColumnsLength",
columnsCount: (states) => states.columns.length,
leftFixedCount: (states) => states.fixedColumns.length,
rightFixedCount: (states) => states.rightFixedColumns.length,
}),
},
beforeDestroy() {
const panels = this.filterPanels;
for (const prop in panels) {
// eslint-disable-next-line
if (panels.hasOwnProperty(prop) && panels[prop]) {
panels[prop].$destroy(true);
}
}
},
};
解决 ElTable 鼠标浮在header/footer 滑动滚轮出现X轴偏移。
这是 ElTable 的 bug,当 table 出现 X 轴滚动条时候,鼠标放在表头滑动滚轮就会移动,源代码有实现。不过源代码中参数写反了,它不产生效果,也不会有负面影响。如果后续有改动(当前版本2.15.13),不过停止更新多年了,应该不会被改。
javascript
// 该函数是table.vue中methods 中的一个事件,本来说写博客中,忘记了。评论区提一下
handleHeaderFooterMousewheel(event, data) {
const { pixelX, pixelY } = data;
if (Math.abs(pixelX) <= Math.abs(pixelY)) {
this.bodyWrapper.scrollLeft += data.pixelY / 5;
}
},