perf: table use sticky for fixed column
parent
4fed700e96
commit
38569c28c7
|
@ -0,0 +1,41 @@
|
|||
import type { Ref } from 'vue';
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import wrapperRaf from '../raf';
|
||||
|
||||
export type Updater<State> = (prev: State) => State;
|
||||
/**
|
||||
* Execute code before next frame but async
|
||||
*/
|
||||
export function useLayoutState<State>(
|
||||
defaultState: State,
|
||||
): [Ref<State>, (updater: Updater<State>) => void] {
|
||||
const stateRef = ref(defaultState);
|
||||
let tempState = stateRef.value;
|
||||
|
||||
let updateBatchRef = [];
|
||||
const rafRef = ref();
|
||||
function setFrameState(updater: Updater<State>) {
|
||||
wrapperRaf.cancel(rafRef.value);
|
||||
updateBatchRef.push(updater);
|
||||
|
||||
rafRef.value = wrapperRaf(() => {
|
||||
const prevBatch = updateBatchRef;
|
||||
// const prevState = stateRef.value;
|
||||
updateBatchRef = [];
|
||||
|
||||
prevBatch.forEach(batchUpdater => {
|
||||
tempState = batchUpdater(tempState);
|
||||
});
|
||||
|
||||
// if (tempState !== stateRef.value) {
|
||||
stateRef.value = tempState;
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
wrapperRaf.cancel(rafRef.value);
|
||||
});
|
||||
|
||||
return [stateRef as Ref<State>, setFrameState];
|
||||
}
|
|
@ -615,15 +615,6 @@
|
|||
overflow-x: hidden;
|
||||
table {
|
||||
min-width: 100%;
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/14545
|
||||
// https://github.com/ant-design/ant-design/issues/19491
|
||||
.@{table-prefix-cls}-fixed-columns-in-body:not([colspan]) {
|
||||
color: transparent;
|
||||
& > * {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -776,6 +767,96 @@
|
|||
&-row[class*='@{table-prefix-cls}-row-level-0'] .@{table-prefix-cls}-selection-column > span {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// ============================ Fixed =============================
|
||||
&-cell-fix-left,
|
||||
&-cell-fix-right {
|
||||
position: -webkit-sticky !important;
|
||||
position: sticky !important;
|
||||
z-index: @zindex-table-fixed;
|
||||
background: @table-bg;
|
||||
}
|
||||
|
||||
&-cell-fix-left-first::after,
|
||||
&-cell-fix-left-last::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: -1px;
|
||||
width: 30px;
|
||||
transform: translateX(100%);
|
||||
transition: box-shadow 0.3s;
|
||||
content: '';
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&-cell-fix-right-first::after,
|
||||
&-cell-fix-right-last::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
width: 30px;
|
||||
transform: translateX(-100%);
|
||||
transition: box-shadow 0.3s;
|
||||
content: '';
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.@{table-prefix-cls}-container {
|
||||
&::before,
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
width: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
content: '';
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
}
|
||||
&::after {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&-ping-left {
|
||||
&:not(.@{table-prefix-cls}-has-fix-left) .@{table-prefix-cls}-container {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
box-shadow: inset 10px 0 8px -8px darken(@shadow-color, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
.@{table-prefix-cls}-cell-fix-left-first::after,
|
||||
.@{table-prefix-cls}-cell-fix-left-last::after {
|
||||
box-shadow: inset 10px 0 8px -8px darken(@shadow-color, 5%);
|
||||
}
|
||||
|
||||
.@{table-prefix-cls}-cell-fix-left-last::before {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-ping-right {
|
||||
&:not(.@{table-prefix-cls}-has-fix-right) .@{table-prefix-cls}-container {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
box-shadow: inset -10px 0 8px -8px darken(@shadow-color, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
.@{table-prefix-cls}-cell-fix-right-first::after,
|
||||
.@{table-prefix-cls}-cell-fix-right-last::after {
|
||||
box-shadow: inset -10px 0 8px -8px darken(@shadow-color, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.@{table-prefix-cls}-filter-dropdown,
|
||||
|
|
|
@ -27,15 +27,10 @@ const BaseTable = {
|
|||
},
|
||||
methods: {
|
||||
getColumns(cols) {
|
||||
const { columns = [], fixed } = this.$props;
|
||||
const { table } = this;
|
||||
const { prefixCls } = table.$props;
|
||||
const { columns = [] } = this.$props;
|
||||
return (cols || columns).map(column => ({
|
||||
...column,
|
||||
className:
|
||||
!!column.fixed && !fixed
|
||||
? classNames(`${prefixCls}-fixed-columns-in-body`, column.className, column.class)
|
||||
: classNames(column.className, column.class),
|
||||
className: classNames(column.className, column.class),
|
||||
}));
|
||||
},
|
||||
handleRowHover(isHover, key) {
|
||||
|
@ -44,7 +39,6 @@ const BaseTable = {
|
|||
|
||||
renderRows(renderData, indent, ancestorKeys = []) {
|
||||
const {
|
||||
columnManager,
|
||||
sComponents: components,
|
||||
prefixCls,
|
||||
childrenColumnName,
|
||||
|
@ -57,6 +51,7 @@ const BaseTable = {
|
|||
onRowMouseLeave = noop,
|
||||
rowRef,
|
||||
} = { ...this.table.$attrs, ...this.table.$props, ...this.table.$data };
|
||||
const { columnManager } = this.store;
|
||||
const { getRowKey, fixed, expander, isAnyColumnsFixed } = this;
|
||||
|
||||
const rows = [];
|
||||
|
@ -68,17 +63,17 @@ const BaseTable = {
|
|||
typeof rowClassName === 'string' ? rowClassName : rowClassName(record, i, indent);
|
||||
|
||||
const onHoverProps = {};
|
||||
if (columnManager.isAnyColumnsFixed()) {
|
||||
if (columnManager.isAnyColumnsFixed) {
|
||||
onHoverProps.onHover = this.handleRowHover;
|
||||
}
|
||||
|
||||
let leafColumns;
|
||||
if (fixed === 'left') {
|
||||
leafColumns = columnManager.leftLeafColumns();
|
||||
leafColumns = columnManager.leftLeafColumns;
|
||||
} else if (fixed === 'right') {
|
||||
leafColumns = columnManager.rightLeafColumns();
|
||||
leafColumns = columnManager.rightLeafColumns;
|
||||
} else {
|
||||
leafColumns = this.getColumns(columnManager.leafColumns());
|
||||
leafColumns = this.getColumns(columnManager.leafColumns);
|
||||
}
|
||||
|
||||
const rowPrefixCls = `${prefixCls}-row`;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { inject } from 'vue';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { INTERNAL_COL_DEFINE } from './utils';
|
||||
import ResizeObserver from '../../vc-resize-observer';
|
||||
|
||||
export default {
|
||||
name: 'ColGroup',
|
||||
|
@ -12,11 +13,12 @@ export default {
|
|||
setup() {
|
||||
return {
|
||||
table: inject('table', {}),
|
||||
store: inject('table-store', () => ({})),
|
||||
};
|
||||
},
|
||||
render() {
|
||||
const { fixed, table } = this;
|
||||
const { prefixCls, expandIconAsCell, columnManager } = table;
|
||||
const { prefixCls, expandIconAsCell, onColumnResize } = table;
|
||||
|
||||
let cols = [];
|
||||
|
||||
|
@ -25,19 +27,32 @@ export default {
|
|||
}
|
||||
|
||||
let leafColumns;
|
||||
|
||||
const { columnManager } = this.store;
|
||||
if (fixed === 'left') {
|
||||
leafColumns = columnManager.leftLeafColumns();
|
||||
leafColumns = columnManager.leftLeafColumns;
|
||||
} else if (fixed === 'right') {
|
||||
leafColumns = columnManager.rightLeafColumns();
|
||||
leafColumns = columnManager.rightLeafColumns;
|
||||
} else {
|
||||
leafColumns = columnManager.leafColumns();
|
||||
leafColumns = columnManager.leafColumns;
|
||||
}
|
||||
cols = cols.concat(
|
||||
leafColumns.map(({ key, dataIndex, width, [INTERNAL_COL_DEFINE]: additionalProps }) => {
|
||||
const mergedKey = key !== undefined ? key : dataIndex;
|
||||
const w = typeof width === 'number' ? `${width}px` : width;
|
||||
return <col key={mergedKey} style={{ width: w, minWidth: w }} {...additionalProps} />;
|
||||
return (
|
||||
<ResizeObserver
|
||||
onResize={({ offsetWidth }) => {
|
||||
onColumnResize(mergedKey, offsetWidth);
|
||||
}}
|
||||
>
|
||||
<col
|
||||
data-key={mergedKey}
|
||||
key={mergedKey}
|
||||
style={{ width: w, minWidth: w }}
|
||||
{...additionalProps}
|
||||
/>
|
||||
</ResizeObserver>
|
||||
);
|
||||
}),
|
||||
);
|
||||
return <colgroup>{cols}</colgroup>;
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
import { toRaw } from 'vue';
|
||||
export default class ColumnManager {
|
||||
constructor(columns) {
|
||||
this.columns = toRaw(columns);
|
||||
this._cached = {};
|
||||
}
|
||||
|
||||
isAnyColumnsFixed() {
|
||||
return this._cache('isAnyColumnsFixed', () => this.columns.some(column => !!column.fixed));
|
||||
}
|
||||
|
||||
isAnyColumnsLeftFixed() {
|
||||
return this._cache('isAnyColumnsLeftFixed', () =>
|
||||
this.columns.some(column => column.fixed === 'left' || column.fixed === true),
|
||||
);
|
||||
}
|
||||
|
||||
isAnyColumnsRightFixed() {
|
||||
return this._cache('isAnyColumnsRightFixed', () =>
|
||||
this.columns.some(column => column.fixed === 'right'),
|
||||
);
|
||||
}
|
||||
|
||||
leftColumns() {
|
||||
return this._cache('leftColumns', () =>
|
||||
this.groupedColumns().filter(column => column.fixed === 'left' || column.fixed === true),
|
||||
);
|
||||
}
|
||||
|
||||
rightColumns() {
|
||||
return this._cache('rightColumns', () =>
|
||||
this.groupedColumns().filter(column => column.fixed === 'right'),
|
||||
);
|
||||
}
|
||||
|
||||
leafColumns() {
|
||||
return this._cache('leafColumns', () => this._leafColumns(this.columns));
|
||||
}
|
||||
|
||||
leftLeafColumns() {
|
||||
return this._cache('leftLeafColumns', () => this._leafColumns(this.leftColumns()));
|
||||
}
|
||||
|
||||
rightLeafColumns() {
|
||||
return this._cache('rightLeafColumns', () => this._leafColumns(this.rightColumns()));
|
||||
}
|
||||
|
||||
// add appropriate rowspan and colspan to column
|
||||
groupedColumns() {
|
||||
return this._cache('groupedColumns', () => {
|
||||
const _groupColumns = (columns, currentRow = 0, parentColumn = {}, rows = []) => {
|
||||
// track how many rows we got
|
||||
rows[currentRow] = rows[currentRow] || [];
|
||||
const grouped = [];
|
||||
const setRowSpan = column => {
|
||||
const rowSpan = rows.length - currentRow;
|
||||
if (
|
||||
column &&
|
||||
!column.children && // parent columns are supposed to be one row
|
||||
rowSpan > 1 &&
|
||||
(!column.rowSpan || column.rowSpan < rowSpan)
|
||||
) {
|
||||
column.rowSpan = rowSpan;
|
||||
}
|
||||
};
|
||||
columns.forEach((column, index) => {
|
||||
const newColumn = { ...column };
|
||||
rows[currentRow].push(newColumn);
|
||||
parentColumn.colSpan = parentColumn.colSpan || 0;
|
||||
if (newColumn.children && newColumn.children.length > 0) {
|
||||
newColumn.children = _groupColumns(newColumn.children, currentRow + 1, newColumn, rows);
|
||||
parentColumn.colSpan += newColumn.colSpan;
|
||||
} else {
|
||||
parentColumn.colSpan += 1;
|
||||
}
|
||||
// update rowspan to all same row columns
|
||||
for (let i = 0; i < rows[currentRow].length - 1; i += 1) {
|
||||
setRowSpan(rows[currentRow][i]);
|
||||
}
|
||||
// last column, update rowspan immediately
|
||||
if (index + 1 === columns.length) {
|
||||
setRowSpan(newColumn);
|
||||
}
|
||||
grouped.push(newColumn);
|
||||
});
|
||||
return grouped;
|
||||
};
|
||||
return _groupColumns(this.columns);
|
||||
});
|
||||
}
|
||||
|
||||
reset(columns) {
|
||||
this.columns = toRaw(columns);
|
||||
this._cached = {};
|
||||
}
|
||||
|
||||
_cache(name, fn) {
|
||||
if (name in this._cached) {
|
||||
return this._cached[name];
|
||||
}
|
||||
this._cached[name] = fn();
|
||||
return this._cached[name];
|
||||
}
|
||||
|
||||
_leafColumns(columns) {
|
||||
const leafColumns = [];
|
||||
columns.forEach(column => {
|
||||
if (!column.children) {
|
||||
leafColumns.push(column);
|
||||
} else {
|
||||
leafColumns.push(...this._leafColumns(column.children));
|
||||
}
|
||||
});
|
||||
return leafColumns;
|
||||
}
|
||||
}
|
|
@ -147,11 +147,11 @@ const ExpandableTable = {
|
|||
};
|
||||
let colCount;
|
||||
if (fixed === 'left') {
|
||||
colCount = this.columnManager.leftLeafColumns().length;
|
||||
colCount = this.columnManager.leftLeafColumns.value.length;
|
||||
} else if (fixed === 'right') {
|
||||
colCount = this.columnManager.rightLeafColumns().length;
|
||||
colCount = this.columnManager.rightLeafColumns.value.length;
|
||||
} else {
|
||||
colCount = this.columnManager.leafColumns().length;
|
||||
colCount = this.columnManager.leafColumns.value.length;
|
||||
}
|
||||
const columns = [
|
||||
{
|
||||
|
|
|
@ -1,19 +1,33 @@
|
|||
/* eslint-disable camelcase */
|
||||
import { provide, markRaw, defineComponent, nextTick, reactive } from 'vue';
|
||||
import {
|
||||
provide,
|
||||
markRaw,
|
||||
defineComponent,
|
||||
nextTick,
|
||||
reactive,
|
||||
computed,
|
||||
ref,
|
||||
onUpdated,
|
||||
onMounted,
|
||||
} from 'vue';
|
||||
import shallowequal from '../../_util/shallowequal';
|
||||
import merge from 'lodash-es/merge';
|
||||
import classes from '../../_util/component-classes';
|
||||
import classNames from '../../_util/classNames';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { debounce, getDataAndAriaProps } from './utils';
|
||||
import { debounce, getColumnsKey, getDataAndAriaProps, validateValue } from './utils';
|
||||
import warning from '../../_util/warning';
|
||||
import addEventListener from '../../vc-util/Dom/addEventListener';
|
||||
import ColumnManager from './ColumnManager';
|
||||
import HeadTable from './HeadTable';
|
||||
import BodyTable from './BodyTable';
|
||||
import ExpandableTable from './ExpandableTable';
|
||||
import { initDefaultProps, getOptionProps } from '../../_util/props-util';
|
||||
import BaseMixin from '../../_util/BaseMixin';
|
||||
import { useLayoutState } from '../../_util/hooks/useLayoutState';
|
||||
import useColumnManager from './useColumnManager';
|
||||
import useStickyOffsets from './useStickyOffsets';
|
||||
import { getCellFixedInfo } from './fixUtil';
|
||||
import ResizeObserver from '../../vc-resize-observer';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Table',
|
||||
|
@ -84,23 +98,76 @@ export default defineComponent({
|
|||
customHeaderRow: () => {},
|
||||
},
|
||||
),
|
||||
setup() {
|
||||
setup(props) {
|
||||
const columnManager = useColumnManager(props.columns);
|
||||
const colsKeys = computed(() => getColumnsKey(columnManager.leafColumns.value));
|
||||
const [colsWidths, updateColsWidths] = useLayoutState(new Map());
|
||||
const pureColWidths = computed(() =>
|
||||
colsKeys.value.map(columnKey => colsWidths.value.get(columnKey)),
|
||||
);
|
||||
const stickyOffsets = useStickyOffsets(pureColWidths, columnManager.leafColumns);
|
||||
const onColumnResize = (columnKey, width) => {
|
||||
updateColsWidths(widths => {
|
||||
if (widths.get(columnKey) !== width) {
|
||||
const newWidths = new Map(widths);
|
||||
newWidths.set(columnKey, width);
|
||||
return newWidths;
|
||||
}
|
||||
return widths;
|
||||
});
|
||||
};
|
||||
const fixedInfoList = computed(() =>
|
||||
columnManager.leafColumns.value.map((_, colIndex) =>
|
||||
getCellFixedInfo(colIndex, colIndex, columnManager.leafColumns.value, stickyOffsets.value),
|
||||
),
|
||||
);
|
||||
const store = reactive({
|
||||
currentHoverKey: null,
|
||||
fixedColumnsHeadRowsHeight: [],
|
||||
fixedColumnsBodyRowsHeight: {},
|
||||
expandedRowsHeight: {},
|
||||
expandedRowKeys: [],
|
||||
columnManager,
|
||||
fixedInfoList,
|
||||
stickyOffsets,
|
||||
});
|
||||
provide('table-store', store);
|
||||
const bodyRef = ref();
|
||||
const pingedLeft = ref(false);
|
||||
const pingedRight = ref(false);
|
||||
const horizonScroll = computed(() => props.scroll && validateValue(props.scroll.x));
|
||||
const onScroll = currentTarget => {
|
||||
const { scrollWidth, clientWidth, scrollLeft } = currentTarget;
|
||||
pingedLeft.value = scrollLeft > 0;
|
||||
pingedRight.value = scrollLeft < scrollWidth - clientWidth;
|
||||
};
|
||||
onUpdated(() => {
|
||||
nextTick(() => {
|
||||
horizonScroll.value && onScroll(bodyRef.value.$el);
|
||||
});
|
||||
});
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
horizonScroll.value && onScroll(bodyRef.value.$el);
|
||||
});
|
||||
});
|
||||
const onFullTableResize = () => {
|
||||
horizonScroll.value && onScroll(bodyRef.value.$el);
|
||||
};
|
||||
return {
|
||||
bodyRef,
|
||||
store,
|
||||
onColumnResize,
|
||||
columnManager,
|
||||
onScroll,
|
||||
pingedLeft,
|
||||
pingedRight,
|
||||
onFullTableResize,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
this.preData = [...this.data];
|
||||
return {
|
||||
columnManager: markRaw(new ColumnManager(this.columns)),
|
||||
sComponents: markRaw(
|
||||
merge(
|
||||
{
|
||||
|
@ -145,11 +212,6 @@ export default defineComponent({
|
|||
this.components,
|
||||
);
|
||||
},
|
||||
columns(val) {
|
||||
if (val) {
|
||||
this.columnManager.reset(val);
|
||||
}
|
||||
},
|
||||
dataLen(val, preVal) {
|
||||
if ((val === 0 || preVal === 0) && this.hasScrollX()) {
|
||||
nextTick(() => {
|
||||
|
@ -160,21 +222,6 @@ export default defineComponent({
|
|||
},
|
||||
created() {
|
||||
provide('table', this);
|
||||
// ['rowClick', 'rowDoubleclick', 'rowContextmenu', 'rowMouseenter', 'rowMouseleave'].forEach(
|
||||
// name => {
|
||||
// warning(
|
||||
// getListeners(this)[name] === undefined,
|
||||
// `${name} is deprecated, please use customRow instead.`,
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
|
||||
// warning(
|
||||
// this.getBodyWrapper === undefined,
|
||||
// 'getBodyWrapper is deprecated, please use custom components instead.',
|
||||
// );
|
||||
|
||||
// this.columnManager = new ColumnManager(this.columns, this.$slots.default)
|
||||
|
||||
this.setScrollPosition('left');
|
||||
|
||||
|
@ -183,7 +230,7 @@ export default defineComponent({
|
|||
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (this.columnManager.isAnyColumnsFixed()) {
|
||||
if (this.columnManager.isAnyColumnsFixed.value) {
|
||||
this.handleWindowResize();
|
||||
this.resizeEvent = addEventListener(window, 'resize', this.debouncedWindowResize);
|
||||
}
|
||||
|
@ -199,7 +246,7 @@ export default defineComponent({
|
|||
|
||||
updated() {
|
||||
this.$nextTick(() => {
|
||||
if (this.columnManager.isAnyColumnsFixed()) {
|
||||
if (this.columnManager.isAnyColumnsFixed.value) {
|
||||
this.handleWindowResize();
|
||||
if (!this.resizeEvent) {
|
||||
this.resizeEvent = addEventListener(window, 'resize', this.debouncedWindowResize);
|
||||
|
@ -383,8 +430,8 @@ export default defineComponent({
|
|||
// Remember last scrollTop for scroll direction detecting.
|
||||
this.lastScrollTop = target.scrollTop;
|
||||
},
|
||||
|
||||
handleBodyScroll(e) {
|
||||
this.onScroll(e.target);
|
||||
this.handleBodyScrollLeft(e);
|
||||
this.handleBodyScrollTop(e);
|
||||
},
|
||||
|
@ -432,28 +479,34 @@ export default defineComponent({
|
|||
},
|
||||
renderMainTable() {
|
||||
const { scroll, prefixCls } = this;
|
||||
const isAnyColumnsFixed = this.columnManager.isAnyColumnsFixed();
|
||||
const isAnyColumnsFixed = this.columnManager.isAnyColumnsFixed.value;
|
||||
const scrollable = isAnyColumnsFixed || scroll.x || scroll.y;
|
||||
|
||||
const table = [
|
||||
this.renderTable({
|
||||
columns: this.columnManager.groupedColumns(),
|
||||
columns: this.columnManager.groupedColumns.value,
|
||||
isAnyColumnsFixed,
|
||||
}),
|
||||
this.renderEmptyText(),
|
||||
this.renderFooter(),
|
||||
];
|
||||
|
||||
return scrollable ? <div class={`${prefixCls}-scroll`}>{table}</div> : table;
|
||||
return scrollable ? (
|
||||
<ResizeObserver onResize={this.onFullTableResize}>
|
||||
<div class={`${prefixCls}-scroll`}>{table}</div>
|
||||
</ResizeObserver>
|
||||
) : (
|
||||
table
|
||||
);
|
||||
},
|
||||
|
||||
renderLeftFixedTable() {
|
||||
const { prefixCls } = this;
|
||||
|
||||
return (
|
||||
<div class={`${prefixCls}-fixed-left`}>
|
||||
<div key="left" class={`${prefixCls}-fixed-left`}>
|
||||
{this.renderTable({
|
||||
columns: this.columnManager.leftColumns(),
|
||||
columns: this.columnManager.leftColumns.value,
|
||||
fixed: 'left',
|
||||
})}
|
||||
</div>
|
||||
|
@ -465,7 +518,7 @@ export default defineComponent({
|
|||
return (
|
||||
<div class={`${prefixCls}-fixed-right`}>
|
||||
{this.renderTable({
|
||||
columns: this.columnManager.rightColumns(),
|
||||
columns: this.columnManager.rightColumns.value,
|
||||
fixed: 'right',
|
||||
})}
|
||||
</div>
|
||||
|
@ -499,6 +552,7 @@ export default defineComponent({
|
|||
handleBodyScroll={this.handleBodyScroll}
|
||||
expander={this.expander}
|
||||
isAnyColumnsFixed={isAnyColumnsFixed}
|
||||
ref="bodyRef"
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -548,10 +602,9 @@ export default defineComponent({
|
|||
this.scrollPosition === 'both',
|
||||
[`${prefixCls}-scroll-position-${this.scrollPosition}`]: this.scrollPosition !== 'both',
|
||||
[`${prefixCls}-layout-fixed`]: this.isTableLayoutFixed(),
|
||||
[`${prefixCls}-ping-left`]: this.pingedLeft,
|
||||
[`${prefixCls}-ping-right`]: this.pingedRight,
|
||||
});
|
||||
|
||||
const hasLeftFixed = columnManager.isAnyColumnsLeftFixed();
|
||||
const hasRightFixed = columnManager.isAnyColumnsRightFixed();
|
||||
const dataAndAriaProps = getDataAndAriaProps(props);
|
||||
const expandableTableProps = {
|
||||
...props,
|
||||
|
@ -573,11 +626,7 @@ export default defineComponent({
|
|||
{...dataAndAriaProps}
|
||||
>
|
||||
{this.renderTitle()}
|
||||
<div class={`${prefixCls}-content`}>
|
||||
{this.renderMainTable()}
|
||||
{hasLeftFixed && this.renderLeftFixedTable()}
|
||||
{hasRightFixed && this.renderRightFixedTable()}
|
||||
</div>
|
||||
<div class={`${prefixCls}-content`}>{this.renderMainTable()}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
@ -22,10 +22,12 @@ export default {
|
|||
column: PropTypes.object,
|
||||
expandIcon: PropTypes.any,
|
||||
component: PropTypes.any,
|
||||
colIndex: PropTypes.number,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
table: inject('table', {}),
|
||||
store: inject('table-store', {}),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -51,8 +53,24 @@ export default {
|
|||
column,
|
||||
component: BodyCell,
|
||||
} = this;
|
||||
const fixedInfoList = this.store.fixedInfoList || [];
|
||||
const fixedInfo = fixedInfoList[this.colIndex];
|
||||
const { fixLeft, fixRight, firstFixLeft, lastFixLeft, firstFixRight, lastFixRight } = fixedInfo;
|
||||
// ====================== Fixed =======================
|
||||
const fixedStyle = {};
|
||||
const isFixLeft = typeof fixLeft === 'number';
|
||||
const isFixRight = typeof fixRight === 'number';
|
||||
|
||||
if (isFixLeft) {
|
||||
fixedStyle.position = 'sticky';
|
||||
fixedStyle.left = `${fixLeft}px`;
|
||||
}
|
||||
if (isFixRight) {
|
||||
fixedStyle.position = 'sticky';
|
||||
fixedStyle.right = `${fixRight}px`;
|
||||
}
|
||||
const { dataIndex, customRender, className = '' } = column;
|
||||
const { transformCellText } = this.table;
|
||||
const { transformCellText, prefixCls: rootPrefixCls } = this.table;
|
||||
// We should return undefined if no dataIndex is specified, but in order to
|
||||
// be compatible with object-path's behavior, we return the record object instead.
|
||||
let text;
|
||||
|
@ -110,6 +128,12 @@ export default {
|
|||
// 如果有宽度,增加断行处理
|
||||
// https://github.com/ant-design/ant-design/issues/13825#issuecomment-449889241
|
||||
[`${prefixCls}-cell-break-word`]: !!column.width,
|
||||
[`${rootPrefixCls}-cell-fix-left`]: isFixLeft,
|
||||
[`${rootPrefixCls}-cell-fix-left-first`]: firstFixLeft,
|
||||
[`${rootPrefixCls}-cell-fix-left-last`]: lastFixLeft,
|
||||
[`${rootPrefixCls}-cell-fix-right`]: isFixRight,
|
||||
[`${rootPrefixCls}-cell-fix-right-first`]: firstFixRight,
|
||||
[`${rootPrefixCls}-cell-fix-right-last`]: lastFixRight,
|
||||
});
|
||||
|
||||
if (column.ellipsis) {
|
||||
|
@ -124,7 +148,11 @@ export default {
|
|||
}
|
||||
|
||||
return (
|
||||
<BodyCell class={cellClassName} {...tdProps}>
|
||||
<BodyCell
|
||||
class={cellClassName}
|
||||
{...tdProps}
|
||||
{...{ style: { ...(tdProps.style || {}), ...fixedStyle } }}
|
||||
>
|
||||
{indentText}
|
||||
{expandIcon}
|
||||
{toRaw(text)}
|
||||
|
|
|
@ -1,44 +1,70 @@
|
|||
import { inject } from 'vue';
|
||||
import { computed, inject } from 'vue';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import TableHeaderRow from './TableHeaderRow';
|
||||
|
||||
function getHeaderRows({ columns = [], currentRow = 0, rows = [], isLast = true }) {
|
||||
rows = rows || [];
|
||||
rows[currentRow] = rows[currentRow] || [];
|
||||
function parseHeaderRows(rootColumns) {
|
||||
const rows = [];
|
||||
|
||||
columns.forEach((column, i) => {
|
||||
if (column.rowSpan && rows.length < column.rowSpan) {
|
||||
while (rows.length < column.rowSpan) {
|
||||
rows.push([]);
|
||||
function fillRowCells(columns, colIndex, rowIndex = 0) {
|
||||
// Init rows
|
||||
rows[rowIndex] = rows[rowIndex] || [];
|
||||
|
||||
let currentColIndex = colIndex;
|
||||
const colSpans = columns.filter(Boolean).map(column => {
|
||||
const cell = {
|
||||
key: column.key,
|
||||
className: column.className || column.class || '',
|
||||
children: column.title,
|
||||
column,
|
||||
colStart: currentColIndex,
|
||||
};
|
||||
|
||||
let colSpan = 1;
|
||||
|
||||
const subColumns = column.children;
|
||||
if (subColumns && subColumns.length > 0) {
|
||||
colSpan = fillRowCells(subColumns, currentColIndex, rowIndex + 1).reduce(
|
||||
(total, count) => total + count,
|
||||
0,
|
||||
);
|
||||
cell.hasSubColumns = true;
|
||||
}
|
||||
}
|
||||
const cellIsLast = isLast && i === columns.length - 1;
|
||||
const cell = {
|
||||
key: column.key,
|
||||
className: column.className || column.class || '',
|
||||
children: column.title,
|
||||
isLast: cellIsLast,
|
||||
column,
|
||||
};
|
||||
if (column.children) {
|
||||
getHeaderRows({
|
||||
columns: column.children,
|
||||
currentRow: currentRow + 1,
|
||||
rows,
|
||||
isLast: cellIsLast,
|
||||
});
|
||||
}
|
||||
if ('colSpan' in column) {
|
||||
cell.colSpan = column.colSpan;
|
||||
}
|
||||
if ('rowSpan' in column) {
|
||||
cell.rowSpan = column.rowSpan;
|
||||
}
|
||||
if (cell.colSpan !== 0) {
|
||||
rows[currentRow].push(cell);
|
||||
}
|
||||
});
|
||||
return rows.filter(row => row.length > 0);
|
||||
|
||||
if ('colSpan' in column) {
|
||||
({ colSpan } = column);
|
||||
}
|
||||
|
||||
if ('rowSpan' in column) {
|
||||
cell.rowSpan = column.rowSpan;
|
||||
}
|
||||
|
||||
cell.colSpan = colSpan;
|
||||
cell.colEnd = cell.colStart + colSpan - 1;
|
||||
rows[rowIndex].push(cell);
|
||||
|
||||
currentColIndex += colSpan;
|
||||
|
||||
return colSpan;
|
||||
});
|
||||
|
||||
return colSpans;
|
||||
}
|
||||
|
||||
// Generate `rows` cell data
|
||||
fillRowCells(rootColumns, 0);
|
||||
|
||||
// Handle `rowSpan`
|
||||
const rowCount = rows.length;
|
||||
for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) {
|
||||
rows[rowIndex].forEach(cell => {
|
||||
if (!('rowSpan' in cell) && !cell.hasSubColumns) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
cell.rowSpan = rowCount - rowIndex;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -49,22 +75,21 @@ export default {
|
|||
columns: PropTypes.array.isRequired,
|
||||
expander: PropTypes.object.isRequired,
|
||||
},
|
||||
setup() {
|
||||
setup(props) {
|
||||
return {
|
||||
table: inject('table', {}),
|
||||
rows: computed(() => parseHeaderRows(props.columns)),
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
const { sComponents: components, prefixCls, showHeader, customHeaderRow } = this.table;
|
||||
const { expander, columns, fixed } = this;
|
||||
const { expander, columns, fixed, rows } = this;
|
||||
|
||||
if (!showHeader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rows = getHeaderRows({ columns });
|
||||
|
||||
expander.renderExpandIndentCell(rows, fixed);
|
||||
|
||||
const HeaderWrapper = components.header.wrapper;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import classNames from '../../_util/classNames';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { computed, inject } from 'vue';
|
||||
import { getCellFixedInfo } from './fixUtil';
|
||||
|
||||
const TableHeaderRow = {
|
||||
name: 'TableHeaderRow',
|
||||
|
@ -35,6 +36,7 @@ const TableHeaderRow = {
|
|||
}
|
||||
return null;
|
||||
}),
|
||||
store,
|
||||
};
|
||||
},
|
||||
render() {
|
||||
|
@ -50,21 +52,44 @@ const TableHeaderRow = {
|
|||
if (style.height === null) {
|
||||
delete style.height;
|
||||
}
|
||||
const { stickyOffsets, columnManager } = this.store;
|
||||
return (
|
||||
<HeaderRow {...rowProps} style={style}>
|
||||
{row.map((cell, i) => {
|
||||
const { column, isLast, children, className, ...cellProps } = cell;
|
||||
const fixedInfo = getCellFixedInfo(
|
||||
cell.colStart,
|
||||
cell.colEnd,
|
||||
columnManager.leafColumns,
|
||||
stickyOffsets,
|
||||
);
|
||||
const customProps = column.customHeaderCell ? column.customHeaderCell(column) : {};
|
||||
const headerCellProps = {
|
||||
...cellProps,
|
||||
...customProps,
|
||||
key: column.key || column.dataIndex || i,
|
||||
};
|
||||
|
||||
if (headerCellProps.colSpan === 0) {
|
||||
return null;
|
||||
}
|
||||
if (column.align) {
|
||||
headerCellProps.style = { ...customProps.style, textAlign: column.align };
|
||||
}
|
||||
// ====================== Fixed =======================
|
||||
const { fixLeft, fixRight, firstFixLeft, lastFixLeft, firstFixRight, lastFixRight } =
|
||||
fixedInfo;
|
||||
const fixedStyle = {};
|
||||
const isFixLeft = typeof fixLeft === 'number';
|
||||
const isFixRight = typeof fixRight === 'number';
|
||||
|
||||
if (isFixLeft) {
|
||||
fixedStyle.position = 'sticky';
|
||||
fixedStyle.left = `${fixLeft}px`;
|
||||
}
|
||||
if (isFixRight) {
|
||||
fixedStyle.position = 'sticky';
|
||||
fixedStyle.right = `${fixRight}px`;
|
||||
}
|
||||
headerCellProps.class = classNames(
|
||||
customProps.class,
|
||||
customProps.className,
|
||||
|
@ -75,9 +100,15 @@ const TableHeaderRow = {
|
|||
[`${prefixCls}-row-cell-ellipsis`]: !!column.ellipsis,
|
||||
[`${prefixCls}-row-cell-break-word`]: !!column.width,
|
||||
[`${prefixCls}-row-cell-last`]: isLast,
|
||||
[`${prefixCls}-cell-fix-left`]: isFixLeft,
|
||||
[`${prefixCls}-cell-fix-left-first`]: firstFixLeft,
|
||||
[`${prefixCls}-cell-fix-left-last`]: lastFixLeft,
|
||||
[`${prefixCls}-cell-fix-right`]: isFixRight,
|
||||
[`${prefixCls}-cell-fix-right-first`]: firstFixRight,
|
||||
[`${prefixCls}-cell-fix-right-last`]: lastFixRight,
|
||||
},
|
||||
);
|
||||
|
||||
headerCellProps.style = { ...(headerCellProps.style || {}), ...fixedStyle };
|
||||
if (typeof HeaderCell === 'function') {
|
||||
return HeaderCell(headerCellProps, children);
|
||||
}
|
||||
|
|
|
@ -250,6 +250,7 @@ const TableRow = {
|
|||
indentSize={indentSize}
|
||||
indent={indent}
|
||||
index={index}
|
||||
colIndex={i}
|
||||
column={column}
|
||||
key={column.key || column.dataIndex}
|
||||
expandIcon={hasExpandIcon(i) && renderExpandIcon()}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
export interface StickyOffsets {
|
||||
left: readonly number[];
|
||||
right: readonly number[];
|
||||
isSticky?: boolean;
|
||||
}
|
||||
export type FixedType = 'left' | 'right' | boolean;
|
||||
export interface FixedInfo {
|
||||
fixLeft: number | false;
|
||||
fixRight: number | false;
|
||||
lastFixLeft: boolean;
|
||||
firstFixRight: boolean;
|
||||
|
||||
// For Rtl Direction
|
||||
lastFixRight: boolean;
|
||||
firstFixLeft: boolean;
|
||||
|
||||
isSticky: boolean;
|
||||
}
|
||||
|
||||
export function getCellFixedInfo(
|
||||
colStart: number,
|
||||
colEnd: number,
|
||||
columns: readonly { fixed?: FixedType }[],
|
||||
stickyOffsets: StickyOffsets,
|
||||
direction: 'ltr' | 'rtl',
|
||||
): FixedInfo {
|
||||
const startColumn = columns[colStart] || {};
|
||||
const endColumn = columns[colEnd] || {};
|
||||
|
||||
let fixLeft: number;
|
||||
let fixRight: number;
|
||||
|
||||
if (startColumn.fixed === 'left') {
|
||||
fixLeft = stickyOffsets.left[colStart];
|
||||
} else if (endColumn.fixed === 'right') {
|
||||
fixRight = stickyOffsets.right[colEnd];
|
||||
}
|
||||
|
||||
let lastFixLeft = false;
|
||||
let firstFixRight = false;
|
||||
|
||||
let lastFixRight = false;
|
||||
let firstFixLeft = false;
|
||||
|
||||
const nextColumn = columns[colEnd + 1];
|
||||
const prevColumn = columns[colStart - 1];
|
||||
|
||||
if (direction === 'rtl') {
|
||||
if (fixLeft !== undefined) {
|
||||
const prevFixLeft = prevColumn && prevColumn.fixed === 'left';
|
||||
firstFixLeft = !prevFixLeft;
|
||||
} else if (fixRight !== undefined) {
|
||||
const nextFixRight = nextColumn && nextColumn.fixed === 'right';
|
||||
lastFixRight = !nextFixRight;
|
||||
}
|
||||
} else if (fixLeft !== undefined) {
|
||||
const nextFixLeft = nextColumn && nextColumn.fixed === 'left';
|
||||
lastFixLeft = !nextFixLeft;
|
||||
} else if (fixRight !== undefined) {
|
||||
const prevFixRight = prevColumn && prevColumn.fixed === 'right';
|
||||
firstFixRight = !prevFixRight;
|
||||
}
|
||||
|
||||
return {
|
||||
fixLeft,
|
||||
fixRight,
|
||||
lastFixLeft,
|
||||
firstFixRight,
|
||||
lastFixRight,
|
||||
firstFixLeft,
|
||||
isSticky: stickyOffsets.isSticky,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
import { computed } from 'vue';
|
||||
export default function useColumnManager(columns) {
|
||||
const _leafColumns = (cls, fixed = false) => {
|
||||
const leafColumns = [];
|
||||
cls.forEach(column => {
|
||||
column.fixed = fixed || column.fixed;
|
||||
if (!column.children) {
|
||||
leafColumns.push(column);
|
||||
} else {
|
||||
leafColumns.push(..._leafColumns(column.children, column.fixed));
|
||||
}
|
||||
});
|
||||
return leafColumns;
|
||||
};
|
||||
|
||||
// add appropriate rowspan and colspan to column
|
||||
const groupedColumns = computed(() => {
|
||||
const _groupColumns = (cls, currentRow = 0, parentColumn = {}, rows = [], fixed = false) => {
|
||||
// track how many rows we got
|
||||
rows[currentRow] = rows[currentRow] || [];
|
||||
const grouped = [];
|
||||
const setRowSpan = column => {
|
||||
const rowSpan = rows.length - currentRow;
|
||||
if (
|
||||
column &&
|
||||
!column.children && // parent columns are supposed to be one row
|
||||
rowSpan > 1 &&
|
||||
(!column.rowSpan || column.rowSpan < rowSpan)
|
||||
) {
|
||||
column.rowSpan = rowSpan;
|
||||
}
|
||||
};
|
||||
cls.forEach((column, index) => {
|
||||
const newColumn = { ...column };
|
||||
newColumn.fixed = fixed || column.fixed;
|
||||
rows[currentRow].push(newColumn);
|
||||
parentColumn.colSpan = parentColumn.colSpan || 0;
|
||||
if (newColumn.children && newColumn.children.length > 0) {
|
||||
newColumn.children = _groupColumns(
|
||||
newColumn.children,
|
||||
currentRow + 1,
|
||||
newColumn,
|
||||
rows,
|
||||
newColumn.fixed,
|
||||
);
|
||||
parentColumn.colSpan += newColumn.colSpan;
|
||||
} else {
|
||||
parentColumn.colSpan += 1;
|
||||
}
|
||||
// update rowspan to all same row columns
|
||||
for (let i = 0; i < rows[currentRow].length - 1; i += 1) {
|
||||
setRowSpan(rows[currentRow][i]);
|
||||
}
|
||||
// last column, update rowspan immediately
|
||||
if (index + 1 === cls.length) {
|
||||
setRowSpan(newColumn);
|
||||
}
|
||||
grouped.push(newColumn);
|
||||
});
|
||||
return grouped;
|
||||
};
|
||||
return _groupColumns(columns);
|
||||
});
|
||||
|
||||
const isAnyColumnsFixed = computed(() => columns.some(column => !!column.fixed));
|
||||
|
||||
const isAnyColumnsLeftFixed = computed(() =>
|
||||
columns.some(column => column.fixed === 'left' || column.fixed === true),
|
||||
);
|
||||
|
||||
const isAnyColumnsRightFixed = computed(() => columns.some(column => column.fixed === 'right'));
|
||||
|
||||
const leftColumns = computed(() =>
|
||||
groupedColumns.value.filter(column => column.fixed === 'left' || column.fixed === true),
|
||||
);
|
||||
|
||||
const rightColumns = computed(() => {
|
||||
return groupedColumns.value.filter(column => column.fixed === 'right');
|
||||
});
|
||||
|
||||
const leafColumns = computed(() => _leafColumns(columns));
|
||||
|
||||
const leftLeafColumns = computed(() => _leafColumns(leftColumns.value));
|
||||
|
||||
const rightLeafColumns = computed(() => _leafColumns(rightColumns.value));
|
||||
return {
|
||||
groupedColumns,
|
||||
isAnyColumnsFixed,
|
||||
isAnyColumnsLeftFixed,
|
||||
isAnyColumnsRightFixed,
|
||||
leftColumns,
|
||||
rightColumns,
|
||||
leafColumns,
|
||||
leftLeafColumns,
|
||||
rightLeafColumns,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import { ref, watch } from 'vue';
|
||||
|
||||
/**
|
||||
* Get sticky column offset width
|
||||
*/
|
||||
function useStickyOffsets(colWidths, columns) {
|
||||
const stickyOffsets = ref({
|
||||
left: [],
|
||||
right: [],
|
||||
});
|
||||
const columnCount = ref();
|
||||
watch(
|
||||
columns,
|
||||
() => {
|
||||
columnCount.value = columns.value.length;
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
watch([colWidths, columnCount], () => {
|
||||
const leftOffsets = [];
|
||||
const rightOffsets = [];
|
||||
let left = 0;
|
||||
let right = 0;
|
||||
|
||||
for (let start = 0; start < columnCount.value; start += 1) {
|
||||
// Left offset
|
||||
leftOffsets[start] = left;
|
||||
left += colWidths.value[start] || 0;
|
||||
|
||||
// Right offset
|
||||
const end = columnCount.value - start - 1;
|
||||
rightOffsets[end] = right;
|
||||
right += colWidths.value[end] || 0;
|
||||
}
|
||||
stickyOffsets.value = {
|
||||
left: leftOffsets,
|
||||
right: rightOffsets,
|
||||
};
|
||||
});
|
||||
return stickyOffsets;
|
||||
}
|
||||
|
||||
export default useStickyOffsets;
|
|
@ -98,3 +98,19 @@ export function getDataAndAriaProps(props) {
|
|||
return memo;
|
||||
}, {});
|
||||
}
|
||||
|
||||
export function getColumnsKey(columns) {
|
||||
const columnKeys = [];
|
||||
|
||||
columns.forEach(column => {
|
||||
const { key, dataIndex } = column || {};
|
||||
|
||||
columnKeys.push(key !== undefined ? key : dataIndex);
|
||||
});
|
||||
|
||||
return columnKeys;
|
||||
}
|
||||
|
||||
export function validateValue(val) {
|
||||
return val !== null && val !== undefined;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@ export default function toArray(children: any[], option: Option = {}): any[] {
|
|||
if ((child === undefined || child === null) && !option.keepEmpty) {
|
||||
return;
|
||||
}
|
||||
debugger;
|
||||
if (Array.isArray(child)) {
|
||||
ret = ret.concat(toArray(child));
|
||||
} else if (isFragment(child) && child.props) {
|
||||
|
|
|
@ -185,7 +185,7 @@
|
|||
"vue-clipboard2": "0.3.1",
|
||||
"vue-draggable-resizable": "^2.1.0",
|
||||
"vue-eslint-parser": "^7.0.0",
|
||||
"vue-i18n": "^9.0.0-alpha.11",
|
||||
"vue-i18n": "^9.1.7",
|
||||
"vue-infinite-scroll": "^2.0.2",
|
||||
"vue-jest": "^5.0.0-alpha.3",
|
||||
"vue-loader": "^16.1.1",
|
||||
|
|
2
v2-doc
2
v2-doc
|
@ -1 +1 @@
|
|||
Subproject commit e67d63729715291f481029fe9497f0109f328504
|
||||
Subproject commit 1882ee4e30b707551a08561f090e72a3f261f6cf
|
Loading…
Reference in New Issue