Skip to content

Commit

Permalink
fix(table): optimize column resize algorithm (#1649)
Browse files Browse the repository at this point in the history
  • Loading branch information
ZTao-z authored Sep 13, 2022
1 parent a73d80b commit 71c4786
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 144 deletions.
12 changes: 11 additions & 1 deletion src/table/base-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export default defineComponent({

// 列宽拖拽逻辑
const columnResizeParams = useColumnResize(tableContentRef, refreshTable, getThWidthList, updateThWidthList);
const { resizeLineRef, resizeLineStyle, recalculateColWidth } = columnResizeParams;
const { resizeLineRef, resizeLineStyle, recalculateColWidth, setEffectColMap } = columnResizeParams;
setRecalculateColWidthFuncRef(recalculateColWidth);

const dynamicBaseTableClasses = computed(() => [
Expand Down Expand Up @@ -156,6 +156,16 @@ export default defineComponent({
{ immediate: true },
);

watch(
thList,
() => {
setEffectColMap(thList.value[0], null);
},
{
immediate: true,
},
);

const onFixedChange = () => {
nextTick(() => {
onHorizontalScroll();
Expand Down
199 changes: 69 additions & 130 deletions src/table/hooks/useColumnResize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { ref, Ref, reactive, CSSProperties } from 'vue';
import isNumber from 'lodash/isNumber';
import { BaseTableCol, TableRowData } from '../type';
import { RecalculateColumnWidthFunc } from '../interface';
import setThWidthListByColumnDrag from '../../_common/js/table/set-column-width-by-drag';
import recalculateColumnWidth from '../../_common/js/table/recalculate-column-width';

const DEFAULT_MIN_WIDTH = 80;
const DEFAULT_MAX_WIDTH = 600;
Expand All @@ -14,6 +16,23 @@ export default function useColumnResize(
) {
const resizeLineRef = ref<HTMLDivElement>();
const notCalculateWidthCols = ref<string[]>([]);
const effectColMap = ref<{ [colKey: string]: any }>({});

// 递归查找列宽度变化后,受影响的相关列
const setEffectColMap = (nodes: BaseTableCol<TableRowData>[], parent: BaseTableCol<TableRowData> | null) => {
if (!nodes) return;
nodes.forEach((n, index) => {
const parentPrevCol = parent ? effectColMap.value[parent.colKey].prev : nodes[index + 1];
const parentNextCol = parent ? effectColMap.value[parent.colKey].next : nodes[index - 1];
const prev = index === 0 ? parentPrevCol : nodes[index - 1];
const next = index === nodes.length - 1 ? parentNextCol : nodes[index + 1];
effectColMap.value[n.colKey] = {
prev,
next,
};
setEffectColMap(n.children, n);
});
};

const resizeLineParams = {
isDragging: false,
Expand Down Expand Up @@ -67,22 +86,32 @@ export default function useColumnResize(
};

// 调整表格列宽
const onColumnMousedown = (
e: MouseEvent,
col: BaseTableCol<TableRowData>,
effectNextCol: BaseTableCol<TableRowData>,
effectPrevCol: BaseTableCol<TableRowData>,
) => {
const onColumnMousedown = (e: MouseEvent, col: BaseTableCol<TableRowData>) => {
// 非 resize 的点击,不做处理
if (!resizeLineParams.draggingCol) return;

const getMinMaxColWidth = (col: BaseTableCol<TableRowData>, effectPrevCol: BaseTableCol<TableRowData>) => {
let targetCol = null;
if (resizeLineParams.effectCol === 'next') {
targetCol = col;
} else {
targetCol = effectPrevCol;
}
const propMinWidth = isNumber(targetCol.minWidth) ? targetCol.minWidth : parseFloat(targetCol.minWidth);
return {
minColWidth: Math.max(targetCol.resize?.minWidth || DEFAULT_MIN_WIDTH, propMinWidth || DEFAULT_MIN_WIDTH),
maxColWidth: targetCol.resize?.maxWidth || DEFAULT_MAX_WIDTH,
};
};

const target = resizeLineParams.draggingCol;
const targetBoundRect = target.getBoundingClientRect();
const tableBoundRect = tableContentRef.value?.getBoundingClientRect();
const resizeLinePos = targetBoundRect.right - tableBoundRect.left;
const colLeft = targetBoundRect.left - tableBoundRect.left;
const minColWidth = col.resize?.minWidth || DEFAULT_MIN_WIDTH;
const maxColWidth = col.resize?.maxWidth || DEFAULT_MAX_WIDTH;
const effectNextCol = effectColMap.value[col.colKey].next;
const effectPrevCol = effectColMap.value[col.colKey].prev;
const { minColWidth, maxColWidth } = getMinMaxColWidth(col, effectPrevCol);
const minResizeLineLeft = colLeft + minColWidth;
const maxResizeLineLeft = colLeft + maxColWidth;

Expand All @@ -99,26 +128,6 @@ export default function useColumnResize(
resizeLineStyle.bottom = `${parent.bottom - tableBoundRect.bottom}px`;
}

const setThWidthListByColumnDrag = (
dragCol: BaseTableCol<TableRowData>,
dragWidth: number,
nearCol: BaseTableCol<TableRowData>,
) => {
const thWidthList = getThWidthList();

const propColWidth = isNumber(dragCol.width) ? dragCol.width : parseFloat(dragCol.width);
const propNearColWidth = isNumber(nearCol.width) ? nearCol.width : parseFloat(nearCol.width);
const oldWidth = thWidthList[dragCol.colKey] || propColWidth;
const oldNearWidth = thWidthList[nearCol.colKey] || propNearColWidth;

updateThWidthList({
[dragCol.colKey]: dragWidth,
[nearCol.colKey]: Math.max(nearCol.resize?.minWidth || DEFAULT_MIN_WIDTH, oldWidth + oldNearWidth - dragWidth),
});

setNotCalculateWidthCols([dragCol.colKey, nearCol.colKey]);
};

// 拖拽时鼠标可能会超出 table 范围,需要给 document 绑定拖拽相关事件
const onDragEnd = () => {
if (resizeLineParams.isDragging) {
Expand All @@ -132,9 +141,27 @@ export default function useColumnResize(
}
// 更新列宽
if (resizeLineParams.effectCol === 'next') {
setThWidthListByColumnDrag(col, width, effectNextCol);
setThWidthListByColumnDrag<BaseTableCol<TableRowData>>(
col,
width,
effectNextCol,
{ getThWidthList, DEFAULT_MIN_WIDTH },
(updateMap, notCalculateCols) => {
updateThWidthList(updateMap);
setNotCalculateWidthCols(notCalculateCols);
},
);
} else if (resizeLineParams.effectCol === 'prev') {
setThWidthListByColumnDrag(effectPrevCol, width, col);
setThWidthListByColumnDrag<BaseTableCol<TableRowData>>(
effectPrevCol,
width,
col,
{ getThWidthList, DEFAULT_MIN_WIDTH },
(updateMap, notCalculateCols) => {
updateThWidthList(updateMap);
setNotCalculateWidthCols(notCalculateCols);
},
);
}

// 恢复设置
Expand Down Expand Up @@ -175,108 +202,19 @@ export default function useColumnResize(
tableLayout: string,
tableElmWidth: number,
): void => {
let actualWidth = 0;
const missingWidthCols: BaseTableCol<TableRowData>[] = [];
const thMap: { [colKey: string]: number } = {};

// 计算现有列的列宽总和
columns.forEach((col) => {
if (!thWidthList[col.colKey]) {
thMap[col.colKey] = isNumber(col.width) ? col.width : parseFloat(col.width);
} else {
thMap[col.colKey] = thWidthList[col.colKey];
}
const originWidth = thMap[col.colKey];
if (originWidth) {
actualWidth += originWidth;
} else {
missingWidthCols.push(col);
}
});

let tableWidth = tableElmWidth;
let needUpdate = false;
// 表宽没有初始化时,默认给没有指定列宽的列指定宽度为100px
if (tableWidth > 0) {
// 存在没有指定列宽的列
if (missingWidthCols.length) {
// 当前列宽总宽度小于表宽,将剩余宽度平均分配给未指定宽度的列
if (actualWidth < tableWidth) {
const widthDiff = tableWidth - actualWidth;
const avgWidth = widthDiff / missingWidthCols.length;
missingWidthCols.forEach((col) => {
thMap[col.colKey] = avgWidth;
});
} else if (tableLayout === 'fixed') {
// 当前列表总宽度大于等于表宽,且当前排版模式为fixed,默认填充100px
missingWidthCols.forEach((col) => {
const originWidth = thMap[col.colKey] || 100;
thMap[col.colKey] = isNumber(originWidth) ? originWidth : parseFloat(originWidth);
});
} else {
// 当前列表总宽度大于等于表宽,且当前排版模式为auto,默认填充100px,然后按比例重新分配各列宽度
const extraWidth = missingWidthCols.length * 100;
const totalWidth = extraWidth + actualWidth;
columns.forEach((col) => {
if (!thMap[col.colKey]) {
thMap[col.colKey] = (100 / totalWidth) * tableWidth;
} else {
thMap[col.colKey] = (thMap[col.colKey] / totalWidth) * tableWidth;
}
});
}
needUpdate = true;
} else {
// 所有列都已经指定宽度
recalculateColumnWidth<BaseTableCol<TableRowData>>(
columns,
thWidthList,
tableLayout,
tableElmWidth,
notCalculateWidthCols.value,
(widthMap) => {
updateThWidthList(widthMap);
if (notCalculateWidthCols.value.length) {
// 存在不允许重新计算宽度的列(一般是resize后的两列),这些列不参与后续计算
let sum = 0;
notCalculateWidthCols.value.forEach((colKey) => {
sum += thMap[colKey];
});
actualWidth -= sum;
tableWidth -= sum;
}
// 重新计算其他列的宽度,按表格剩余宽度进行按比例分配
if (actualWidth !== tableWidth || notCalculateWidthCols.value.length) {
columns.forEach((col) => {
if (notCalculateWidthCols.value.includes(col.colKey)) return;
thMap[col.colKey] = (thMap[col.colKey] / actualWidth) * tableWidth;
});
needUpdate = true;
}
}
} else {
// 表格宽度未初始化,默认填充100px
missingWidthCols.forEach((col) => {
const originWidth = thMap[col.colKey] || 100;
thMap[col.colKey] = isNumber(originWidth) ? originWidth : parseFloat(originWidth);
});

needUpdate = true;
}

// 列宽转为整数
if (needUpdate) {
let addon = 0;
Object.keys(thMap).forEach((key) => {
const width = thMap[key];
addon += width - Math.floor(width);
thMap[key] = Math.floor(width) + (addon > 1 ? 1 : 0);
if (addon > 1) {
addon -= 1;
notCalculateWidthCols.value = [];
}
});
if (addon > 0.5) {
thMap[columns[0].colKey] += 1;
}
}

updateThWidthList(thMap);

if (notCalculateWidthCols.value.length) {
notCalculateWidthCols.value = [];
}
},
);
};

return {
Expand All @@ -285,5 +223,6 @@ export default function useColumnResize(
onColumnMouseover,
onColumnMousedown,
recalculateColWidth,
setEffectColMap,
};
}
12 changes: 12 additions & 0 deletions src/table/hooks/useFixed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,10 @@ export default function useFixed(
}, 0);
};

const resetThWidthList = () => {
thWidthList.value = {};
};

const emitScrollEvent = (e: WheelEvent) => {
props.onScrollX?.({ e });
props.onScrollY?.({ e });
Expand Down Expand Up @@ -479,6 +483,14 @@ export default function useFixed(
{ immediate: true },
);

watch(finalColumns, () => {
updateTableWidth();
resetThWidthList();
if (columnResizable.value) {
recalculateColWidth.value(finalColumns.value, thWidthList.value, tableLayout.value, tableElmWidth.value);
}
});

const refreshTable = debounce(() => {
updateTableWidth();
updateFixedHeader();
Expand Down
15 changes: 2 additions & 13 deletions src/table/thead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,7 @@ export interface TheadProps {
resizeLineRef: Ref<HTMLDivElement>;
resizeLineStyle: CSSProperties;
onColumnMouseover: (e: MouseEvent) => void;
onColumnMousedown: (
e: MouseEvent,
col: BaseTableCol<TableRowData>,
effectNextCol: BaseTableCol<TableRowData>,
effectPrevCol: BaseTableCol<TableRowData>,
) => void;
onColumnMousedown: (e: MouseEvent, col: BaseTableCol<TableRowData>) => void;
};
resizable: Boolean;
}
Expand Down Expand Up @@ -114,13 +109,7 @@ export default defineComponent({
const innerTh = renderTitle(this.slots, col, index);
const resizeColumnListener = this.resizable
? {
onMousedown: (e: MouseEvent) =>
this.columnResizeParams?.onColumnMousedown?.(
e,
col,
index < row.length - 1 ? row[index + 1] : row[index - 1],
index > 0 ? row[index - 1] : row[index + 1],
),
onMousedown: (e: MouseEvent) => this.columnResizeParams?.onColumnMousedown?.(e, col),
onMousemove: (e: MouseEvent) => this.columnResizeParams?.onColumnMouseover?.(e),
}
: {};
Expand Down

0 comments on commit 71c4786

Please sign in to comment.