Skip to content

Commit

Permalink
fix(table): 多级表头支持调整列宽 (#1395)
Browse files Browse the repository at this point in the history
* fix(table): multiple header resize

* fix(table): update demo

* fix(table): update test case

* fix(table): extract public functions

* fix(table): update import

* fix(table): update _common

* fix(table): optimize code

* fix(table): remove useless code
  • Loading branch information
ZTao-z authored Aug 29, 2022
1 parent a6f5d35 commit db93b9a
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 144 deletions.
2 changes: 1 addition & 1 deletion src/table/__tests__/__snapshots__/demo.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17777,7 +17777,7 @@ exports[`Table Table multiHeaderVue demo works fine 1`] = `
</div>

<div
class="t-table t-table--bordered t-table--multiple-header t-table--col-draggable"
class="t-table t-table--bordered t-table--multiple-header t-table--column-resizable t-table--col-draggable"
style="position: relative;"
>
<div
Expand Down
2 changes: 2 additions & 0 deletions src/table/_example/multi-header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
:headerAffixedTop="headerAffixedTop ? { offsetTop: 87 } : false"
:scroll="{ type: 'virtual' }"
drag-sort="col"
resizable
:table-layout="'fixed'"
@drag-sort="onDragSort"
@data-change="onDataChange"
@filter-change="onFilterChange"
Expand Down
14 changes: 13 additions & 1 deletion src/table/base-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ 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 @@ -157,6 +159,16 @@ export default defineComponent({
{ immediate: true },
);

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

const onFixedChange = () => {
nextTick(() => {
onHorizontalScroll();
Expand Down
184 changes: 55 additions & 129 deletions src/table/hooks/useColumnResize.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ref, Ref, reactive } from '@vue/composition-api';
import isNumber from 'lodash/isNumber';
import { RecalculateColumnWidthFunc } from '../interface';
import { BaseTableCol, TableRowData } from '../type';
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 +15,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,12 +85,7 @@ 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;

Expand All @@ -86,6 +99,9 @@ export default function useColumnResize(
const minResizeLineLeft = colLeft + minColWidth;
const maxResizeLineLeft = colLeft + maxColWidth;

const effectNextCol = effectColMap.value[col.colKey].next;
const effectPrevCol = effectColMap.value[col.colKey].prev;

// 开始拖拽,记录下鼠标起始位置
resizeLineParams.isDragging = true;
resizeLineParams.draggingStart = e.x;
Expand All @@ -99,26 +115,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 +128,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 +189,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 +210,6 @@ export default function useColumnResize(
onColumnMouseover,
onColumnMousedown,
recalculateColWidth,
setEffectColMap,
};
}
14 changes: 2 additions & 12 deletions src/table/thead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,7 @@ export interface TheadProps {
resizeLineRef: HTMLDivElement;
resizeLineStyle: Object;
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 @@ -119,12 +114,7 @@ export default defineComponent({
const innerTh = renderTitle(h, this.slots, col, index);
const resizeColumnListener = this.resizable
? {
mousedown: (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],
),
mousedown: (e: MouseEvent) => this.columnResizeParams?.onColumnMousedown?.(e, col),
mousemove: (e: MouseEvent) => this.columnResizeParams?.onColumnMouseover?.(e),
}
: {};
Expand Down
2 changes: 1 addition & 1 deletion test/ssr/__snapshots__/ssr.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17771,7 +17771,7 @@ exports[`ssr snapshot test renders ./src/table/_example/merge-cells.vue correctl
exports[`ssr snapshot test renders ./src/table/_example/multi-header.vue correctly 1`] = `
<div class="tdesign-demo-block-column-large tdesign-demo-table-multi-heade tdesign-demo__tabler" style="width:100%;">
<div><label class="t-checkbox t-is-checked"><input type="checkbox" checked="checked" class="t-checkbox__former"><span class="t-checkbox__input"></span><span class="t-checkbox__label">显示表格边框</span></label> <label class="t-checkbox t-is-checked"><input type="checkbox" checked="checked" class="t-checkbox__former"><span class="t-checkbox__input"></span><span class="t-checkbox__label">显示固定表头</span></label> <label class="t-checkbox"><input type="checkbox" class="t-checkbox__former"><span class="t-checkbox__input"></span><span class="t-checkbox__label">固定左侧列</span></label> <label class="t-checkbox"><input type="checkbox" class="t-checkbox__former"><span class="t-checkbox__input"></span><span class="t-checkbox__label">固定右侧列</span></label> <label class="t-checkbox"><input type="checkbox" class="t-checkbox__former"><span class="t-checkbox__input"></span><span class="t-checkbox__label">表头吸顶</span></label></div>
<div class="t-table t-table--bordered t-table--multiple-header t-table--col-draggable" style="position:relative;">
<div class="t-table t-table--bordered t-table--multiple-header t-table--column-resizable t-table--col-draggable" style="position:relative;">
<div class="t-table__top-content">
<div class="t-table__column-controller-trigger t-align-top-right"><button type="button" class="t-button t-size-m t-button--variant-outline t-button--theme-default"><svg fill="none" viewBox="0 0 16 16" width="1em" height="1em" class="t-icon t-icon-setting">
<path fill="currentColor" d="M11 8a3 3 0 11-6 0 3 3 0 016 0zm-1 0a2 2 0 10-4 0 2 2 0 004 0z" fill-opacity="0.9"></path>
Expand Down

0 comments on commit db93b9a

Please sign in to comment.