Skip to content

Commit db93b9a

Browse files
authored
fix(table): 多级表头支持调整列宽 (#1395)
* 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
1 parent a6f5d35 commit db93b9a

File tree

6 files changed

+74
-144
lines changed

6 files changed

+74
-144
lines changed

src/table/__tests__/__snapshots__/demo.test.js.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17777,7 +17777,7 @@ exports[`Table Table multiHeaderVue demo works fine 1`] = `
1777717777
</div>
1777817778

1777917779
<div
17780-
class="t-table t-table--bordered t-table--multiple-header t-table--col-draggable"
17780+
class="t-table t-table--bordered t-table--multiple-header t-table--column-resizable t-table--col-draggable"
1778117781
style="position: relative;"
1778217782
>
1778317783
<div

src/table/_example/multi-header.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
:headerAffixedTop="headerAffixedTop ? { offsetTop: 87 } : false"
2929
:scroll="{ type: 'virtual' }"
3030
drag-sort="col"
31+
resizable
32+
:table-layout="'fixed'"
3133
@drag-sort="onDragSort"
3234
@data-change="onDataChange"
3335
@filter-change="onFilterChange"

src/table/base-table.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,9 @@ export default defineComponent({
105105

106106
// 列宽拖拽逻辑
107107
const columnResizeParams = useColumnResize(tableContentRef, refreshTable, getThWidthList, updateThWidthList);
108-
const { resizeLineRef, resizeLineStyle, recalculateColWidth } = columnResizeParams;
108+
const {
109+
resizeLineRef, resizeLineStyle, recalculateColWidth, setEffectColMap,
110+
} = columnResizeParams;
109111
setRecalculateColWidthFuncRef(recalculateColWidth);
110112

111113
const dynamicBaseTableClasses = computed(() => [
@@ -157,6 +159,16 @@ export default defineComponent({
157159
{ immediate: true },
158160
);
159161

162+
watch(
163+
thList,
164+
() => {
165+
setEffectColMap(thList.value[0], null);
166+
},
167+
{
168+
immediate: true,
169+
},
170+
);
171+
160172
const onFixedChange = () => {
161173
nextTick(() => {
162174
onHorizontalScroll();

src/table/hooks/useColumnResize.ts

Lines changed: 55 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { ref, Ref, reactive } from '@vue/composition-api';
2-
import isNumber from 'lodash/isNumber';
32
import { RecalculateColumnWidthFunc } from '../interface';
43
import { BaseTableCol, TableRowData } from '../type';
4+
import setThWidthListByColumnDrag from '../../_common/js/table/set-column-width-by-drag';
5+
import recalculateColumnWidth from '../../_common/js/table/recalculate-column-width';
56

67
const DEFAULT_MIN_WIDTH = 80;
78
const DEFAULT_MAX_WIDTH = 600;
@@ -14,6 +15,23 @@ export default function useColumnResize(
1415
) {
1516
const resizeLineRef = ref<HTMLDivElement>();
1617
const notCalculateWidthCols = ref<string[]>([]);
18+
const effectColMap = ref<{ [colKey: string]: any }>({});
19+
20+
// 递归查找列宽度变化后,受影响的相关列
21+
const setEffectColMap = (nodes: BaseTableCol<TableRowData>[], parent: BaseTableCol<TableRowData> | null) => {
22+
if (!nodes) return;
23+
nodes.forEach((n, index) => {
24+
const parentPrevCol = parent ? effectColMap.value[parent.colKey].prev : nodes[index + 1];
25+
const parentNextCol = parent ? effectColMap.value[parent.colKey].next : nodes[index - 1];
26+
const prev = index === 0 ? parentPrevCol : nodes[index - 1];
27+
const next = index === nodes.length - 1 ? parentNextCol : nodes[index + 1];
28+
effectColMap.value[n.colKey] = {
29+
prev,
30+
next,
31+
};
32+
setEffectColMap(n.children, n);
33+
});
34+
};
1735

1836
const resizeLineParams = {
1937
isDragging: false,
@@ -67,12 +85,7 @@ export default function useColumnResize(
6785
};
6886

6987
// 调整表格列宽
70-
const onColumnMousedown = (
71-
e: MouseEvent,
72-
col: BaseTableCol<TableRowData>,
73-
effectNextCol: BaseTableCol<TableRowData>,
74-
effectPrevCol: BaseTableCol<TableRowData>,
75-
) => {
88+
const onColumnMousedown = (e: MouseEvent, col: BaseTableCol<TableRowData>) => {
7689
// 非 resize 的点击,不做处理
7790
if (!resizeLineParams.draggingCol) return;
7891

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

102+
const effectNextCol = effectColMap.value[col.colKey].next;
103+
const effectPrevCol = effectColMap.value[col.colKey].prev;
104+
89105
// 开始拖拽,记录下鼠标起始位置
90106
resizeLineParams.isDragging = true;
91107
resizeLineParams.draggingStart = e.x;
@@ -99,26 +115,6 @@ export default function useColumnResize(
99115
resizeLineStyle.bottom = `${parent.bottom - tableBoundRect.bottom}px`;
100116
}
101117

102-
const setThWidthListByColumnDrag = (
103-
dragCol: BaseTableCol<TableRowData>,
104-
dragWidth: number,
105-
nearCol: BaseTableCol<TableRowData>,
106-
) => {
107-
const thWidthList = getThWidthList();
108-
109-
const propColWidth = isNumber(dragCol.width) ? dragCol.width : parseFloat(dragCol.width);
110-
const propNearColWidth = isNumber(nearCol.width) ? nearCol.width : parseFloat(nearCol.width);
111-
const oldWidth = thWidthList[dragCol.colKey] || propColWidth;
112-
const oldNearWidth = thWidthList[nearCol.colKey] || propNearColWidth;
113-
114-
updateThWidthList({
115-
[dragCol.colKey]: dragWidth,
116-
[nearCol.colKey]: Math.max(nearCol.resize?.minWidth || DEFAULT_MIN_WIDTH, oldWidth + oldNearWidth - dragWidth),
117-
});
118-
119-
setNotCalculateWidthCols([dragCol.colKey, nearCol.colKey]);
120-
};
121-
122118
// 拖拽时鼠标可能会超出 table 范围,需要给 document 绑定拖拽相关事件
123119
const onDragEnd = () => {
124120
if (resizeLineParams.isDragging) {
@@ -132,9 +128,27 @@ export default function useColumnResize(
132128
}
133129
// 更新列宽
134130
if (resizeLineParams.effectCol === 'next') {
135-
setThWidthListByColumnDrag(col, width, effectNextCol);
131+
setThWidthListByColumnDrag<BaseTableCol<TableRowData>>(
132+
col,
133+
width,
134+
effectNextCol,
135+
{ getThWidthList, DEFAULT_MIN_WIDTH },
136+
(updateMap, notCalculateCols) => {
137+
updateThWidthList(updateMap);
138+
setNotCalculateWidthCols(notCalculateCols);
139+
},
140+
);
136141
} else if (resizeLineParams.effectCol === 'prev') {
137-
setThWidthListByColumnDrag(effectPrevCol, width, col);
142+
setThWidthListByColumnDrag<BaseTableCol<TableRowData>>(
143+
effectPrevCol,
144+
width,
145+
col,
146+
{ getThWidthList, DEFAULT_MIN_WIDTH },
147+
(updateMap, notCalculateCols) => {
148+
updateThWidthList(updateMap);
149+
setNotCalculateWidthCols(notCalculateCols);
150+
},
151+
);
138152
}
139153

140154
// 恢复设置
@@ -175,108 +189,19 @@ export default function useColumnResize(
175189
tableLayout: string,
176190
tableElmWidth: number,
177191
): void => {
178-
let actualWidth = 0;
179-
const missingWidthCols: BaseTableCol<TableRowData>[] = [];
180-
const thMap: { [colKey: string]: number } = {};
181-
182-
// 计算现有列的列宽总和
183-
columns.forEach((col) => {
184-
if (!thWidthList[col.colKey]) {
185-
thMap[col.colKey] = isNumber(col.width) ? col.width : parseFloat(col.width);
186-
} else {
187-
thMap[col.colKey] = thWidthList[col.colKey];
188-
}
189-
const originWidth = thMap[col.colKey];
190-
if (originWidth) {
191-
actualWidth += originWidth;
192-
} else {
193-
missingWidthCols.push(col);
194-
}
195-
});
196-
197-
let tableWidth = tableElmWidth;
198-
let needUpdate = false;
199-
// 表宽没有初始化时,默认给没有指定列宽的列指定宽度为100px
200-
if (tableWidth > 0) {
201-
// 存在没有指定列宽的列
202-
if (missingWidthCols.length) {
203-
// 当前列宽总宽度小于表宽,将剩余宽度平均分配给未指定宽度的列
204-
if (actualWidth < tableWidth) {
205-
const widthDiff = tableWidth - actualWidth;
206-
const avgWidth = widthDiff / missingWidthCols.length;
207-
missingWidthCols.forEach((col) => {
208-
thMap[col.colKey] = avgWidth;
209-
});
210-
} else if (tableLayout === 'fixed') {
211-
// 当前列表总宽度大于等于表宽,且当前排版模式为fixed,默认填充100px
212-
missingWidthCols.forEach((col) => {
213-
const originWidth = thMap[col.colKey] || 100;
214-
thMap[col.colKey] = isNumber(originWidth) ? originWidth : parseFloat(originWidth);
215-
});
216-
} else {
217-
// 当前列表总宽度大于等于表宽,且当前排版模式为auto,默认填充100px,然后按比例重新分配各列宽度
218-
const extraWidth = missingWidthCols.length * 100;
219-
const totalWidth = extraWidth + actualWidth;
220-
columns.forEach((col) => {
221-
if (!thMap[col.colKey]) {
222-
thMap[col.colKey] = (100 / totalWidth) * tableWidth;
223-
} else {
224-
thMap[col.colKey] = (thMap[col.colKey] / totalWidth) * tableWidth;
225-
}
226-
});
227-
}
228-
needUpdate = true;
229-
} else {
230-
// 所有列都已经指定宽度
192+
recalculateColumnWidth<BaseTableCol<TableRowData>>(
193+
columns,
194+
thWidthList,
195+
tableLayout,
196+
tableElmWidth,
197+
notCalculateWidthCols.value,
198+
(widthMap) => {
199+
updateThWidthList(widthMap);
231200
if (notCalculateWidthCols.value.length) {
232-
// 存在不允许重新计算宽度的列(一般是resize后的两列),这些列不参与后续计算
233-
let sum = 0;
234-
notCalculateWidthCols.value.forEach((colKey) => {
235-
sum += thMap[colKey];
236-
});
237-
actualWidth -= sum;
238-
tableWidth -= sum;
239-
}
240-
// 重新计算其他列的宽度,按表格剩余宽度进行按比例分配
241-
if (actualWidth !== tableWidth || notCalculateWidthCols.value.length) {
242-
columns.forEach((col) => {
243-
if (notCalculateWidthCols.value.includes(col.colKey)) return;
244-
thMap[col.colKey] = (thMap[col.colKey] / actualWidth) * tableWidth;
245-
});
246-
needUpdate = true;
247-
}
248-
}
249-
} else {
250-
// 表格宽度未初始化,默认填充100px
251-
missingWidthCols.forEach((col) => {
252-
const originWidth = thMap[col.colKey] || 100;
253-
thMap[col.colKey] = isNumber(originWidth) ? originWidth : parseFloat(originWidth);
254-
});
255-
256-
needUpdate = true;
257-
}
258-
259-
// 列宽转为整数
260-
if (needUpdate) {
261-
let addon = 0;
262-
Object.keys(thMap).forEach((key) => {
263-
const width = thMap[key];
264-
addon += width - Math.floor(width);
265-
thMap[key] = Math.floor(width) + (addon > 1 ? 1 : 0);
266-
if (addon > 1) {
267-
addon -= 1;
201+
notCalculateWidthCols.value = [];
268202
}
269-
});
270-
if (addon > 0.5) {
271-
thMap[columns[0].colKey] += 1;
272-
}
273-
}
274-
275-
updateThWidthList(thMap);
276-
277-
if (notCalculateWidthCols.value.length) {
278-
notCalculateWidthCols.value = [];
279-
}
203+
},
204+
);
280205
};
281206

282207
return {
@@ -285,5 +210,6 @@ export default function useColumnResize(
285210
onColumnMouseover,
286211
onColumnMousedown,
287212
recalculateColWidth,
213+
setEffectColMap,
288214
};
289215
}

src/table/thead.tsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,7 @@ export interface TheadProps {
2929
resizeLineRef: HTMLDivElement;
3030
resizeLineStyle: Object;
3131
onColumnMouseover: (e: MouseEvent) => void;
32-
onColumnMousedown: (
33-
e: MouseEvent,
34-
col: BaseTableCol<TableRowData>,
35-
effectNextCol: BaseTableCol<TableRowData>,
36-
effectPrevCol: BaseTableCol<TableRowData>,
37-
) => void;
32+
onColumnMousedown: (e: MouseEvent, col: BaseTableCol<TableRowData>) => void;
3833
};
3934
resizable: Boolean;
4035
}
@@ -119,12 +114,7 @@ export default defineComponent({
119114
const innerTh = renderTitle(h, this.slots, col, index);
120115
const resizeColumnListener = this.resizable
121116
? {
122-
mousedown: (e: MouseEvent) => this.columnResizeParams?.onColumnMousedown?.(
123-
e,
124-
col,
125-
index < row.length - 1 ? row[index + 1] : row[index - 1],
126-
index > 0 ? row[index - 1] : row[index + 1],
127-
),
117+
mousedown: (e: MouseEvent) => this.columnResizeParams?.onColumnMousedown?.(e, col),
128118
mousemove: (e: MouseEvent) => this.columnResizeParams?.onColumnMouseover?.(e),
129119
}
130120
: {};

test/ssr/__snapshots__/ssr.test.js.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17771,7 +17771,7 @@ exports[`ssr snapshot test renders ./src/table/_example/merge-cells.vue correctl
1777117771
exports[`ssr snapshot test renders ./src/table/_example/multi-header.vue correctly 1`] = `
1777217772
<div class="tdesign-demo-block-column-large tdesign-demo-table-multi-heade tdesign-demo__tabler" style="width:100%;">
1777317773
<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>
17774-
<div class="t-table t-table--bordered t-table--multiple-header t-table--col-draggable" style="position:relative;">
17774+
<div class="t-table t-table--bordered t-table--multiple-header t-table--column-resizable t-table--col-draggable" style="position:relative;">
1777517775
<div class="t-table__top-content">
1777617776
<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">
1777717777
<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>

0 commit comments

Comments
 (0)