stk-table-vue 0.2.2 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,41 @@
1
1
  import { interpolateRgb } from 'd3-interpolate';
2
- import { Ref, computed } from 'vue';
2
+ import { Ref, computed, ref } from 'vue';
3
3
  import { Highlight_Color, Highlight_Color_Change_Freq, Highlight_Duration } from './const';
4
+ import { UniqKey } from './types';
4
5
 
5
6
  type Params = {
6
7
  props: { theme: 'light' | 'dark'; virtual: boolean; dataSource: any[] };
7
8
  tableContainer: Ref<HTMLElement | undefined>;
8
- rowKeyGen: (p: any) => string;
9
9
  };
10
+
11
+ /** 高亮行保存的东西 */
12
+ type HighlightRowStore = {
13
+ bgc: string;
14
+ bgc_progress_ms: number;
15
+ bgc_progress: number;
16
+ };
17
+
18
+ /** 高亮行class */
19
+ const HIGHLIGHT_ROW_CLASS = 'highlight-row';
20
+ /** 高连单元格class */
21
+ const HIGHLIGHT_CELL_CLASS = 'highlight-cell';
22
+
10
23
  /**
11
24
  * 高亮单元格,行
12
25
  * row中新增_bgc_progress_ms 属性控制高亮状态,_bgc控制颜色
13
26
  */
14
- export function useHighlight({ props, tableContainer, rowKeyGen }: Params) {
15
- const highlightInter = computed(() => {
16
- return interpolateRgb(Highlight_Color[props.theme].from, Highlight_Color[props.theme].to);
17
- });
18
- /** 存放高亮行的对象*/
19
- const highlightDimRows = new Set<any>();
27
+ export function useHighlight({ props, tableContainer }: Params) {
28
+ /**
29
+ * 高亮行记录 key-rowKey, value-obj
30
+ */
31
+ const highlightRowStore = ref<Record<UniqKey, HighlightRowStore>>({});
32
+
33
+ const highlightFrom = computed(() => Highlight_Color[props.theme].from);
34
+ const highlightTo = computed(() => Highlight_Color[props.theme].to);
35
+ const highlightInter = computed(() => interpolateRgb(highlightFrom.value, highlightTo.value));
36
+
37
+ /** 存放高亮行的key*/
38
+ const highlightDimRowKeys = new Set<UniqKey>();
20
39
  /** 高亮后渐暗的行定时器 */
21
40
  const highlightDimRowsTimeout = new Map();
22
41
  /** 高亮后渐暗的单元格定时器 */
@@ -36,9 +55,8 @@ export function useHighlight({ props, tableContainer, rowKeyGen }: Params) {
36
55
  const recursion = () => {
37
56
  window.setTimeout(() => {
38
57
  const nowTs = Date.now();
39
- const needDeleteRows: any = [];
40
58
 
41
- highlightDimRows.forEach(row => {
59
+ highlightDimRowKeys.forEach(rowKeyValue => {
42
60
  // const rowKeyValue = rowKeyGen(row);
43
61
  // const rowEl = tableContainer.value?.querySelector<HTMLElement>(`[data-row-key="${rowKeyValue}"]`);
44
62
  // if (rowEl && row._bgc_progress === 0) {
@@ -47,21 +65,18 @@ export function useHighlight({ props, tableContainer, rowKeyGen }: Params) {
47
65
  // void rowEl.offsetHeight; // reflow
48
66
  // rowEl.classList.add('highlight-row-transition');
49
67
  // }
50
-
68
+ const highlightItem = highlightRowStore.value[rowKeyValue];
51
69
  /** 经过的时间 ÷ 高亮持续时间 计算出 颜色过渡进度 (0-1) */
52
- const progress = (nowTs - row._bgc_progress_ms) / Highlight_Duration;
53
- // row._bgc_progress = progress;
70
+ const progress = (nowTs - highlightItem.bgc_progress_ms) / Highlight_Duration;
54
71
  if (0 < progress && progress < 1) {
55
- row._bgc = highlightInter.value(progress);
72
+ highlightItem.bgc = highlightInter.value(progress);
56
73
  } else {
57
- row._bgc = ''; // 清空颜色
58
- needDeleteRows.push(row);
74
+ highlightItem.bgc = ''; // 清空颜色
75
+ highlightDimRowKeys.delete(rowKeyValue);
59
76
  }
60
77
  });
61
- needDeleteRows.forEach((row: any) => highlightDimRows.delete(row));
62
- // TODO: shallowRef 时,需要手动更新
63
78
 
64
- if (highlightDimRows.size > 0) {
79
+ if (highlightDimRowKeys.size > 0) {
65
80
  // 还有高亮的行,则下一次循环
66
81
  recursion();
67
82
  } else {
@@ -78,16 +93,16 @@ export function useHighlight({ props, tableContainer, rowKeyGen }: Params) {
78
93
  // TODO: 支持动态计算高亮颜色。不易实现。需记录每一个单元格的颜色情况。
79
94
  const cellEl = tableContainer.value?.querySelector<HTMLElement>(`[data-row-key="${rowKeyValue}"]>[data-index="${dataIndex}"]`);
80
95
  if (!cellEl) return;
81
- if (cellEl.classList.contains('highlight-cell')) {
82
- cellEl.classList.remove('highlight-cell');
96
+ if (cellEl.classList.contains(HIGHLIGHT_CELL_CLASS)) {
97
+ cellEl.classList.remove(HIGHLIGHT_CELL_CLASS);
83
98
  void cellEl.offsetHeight; // 通知浏览器重绘
84
99
  }
85
- cellEl.classList.add('highlight-cell');
100
+ cellEl.classList.add(HIGHLIGHT_CELL_CLASS);
86
101
  window.clearTimeout(highlightDimCellsTimeout.get(rowKeyValue));
87
102
  highlightDimCellsTimeout.set(
88
103
  rowKeyValue,
89
104
  window.setTimeout(() => {
90
- cellEl.classList.remove('highlight-cell');
105
+ cellEl.classList.remove(HIGHLIGHT_CELL_CLASS);
91
106
  highlightDimCellsTimeout.delete(rowKeyValue);
92
107
  }, Highlight_Duration),
93
108
  );
@@ -97,18 +112,19 @@ export function useHighlight({ props, tableContainer, rowKeyGen }: Params) {
97
112
  * 高亮一行
98
113
  * @param rowKeyValues
99
114
  */
100
- function setHighlightDimRow(rowKeyValues: Array<string | number>) {
115
+ function setHighlightDimRow(rowKeyValues: UniqKey[]) {
101
116
  if (!Array.isArray(rowKeyValues)) rowKeyValues = [rowKeyValues];
102
117
  if (props.virtual) {
103
118
  // --------虚拟滚动用js计算颜色渐变的高亮方案
104
119
  const nowTs = Date.now(); // 重置渐变进度
105
120
  for (let i = 0; i < rowKeyValues.length; i++) {
106
121
  const rowKeyValue = rowKeyValues[i];
107
- const row = props.dataSource.find((it: any) => rowKeyGen(it) === rowKeyValue);
108
- if (!row) continue;
109
- row._bgc_progress_ms = nowTs;
110
- // row._bgc_progress = 0;
111
- highlightDimRows.add(row);
122
+ highlightRowStore.value[rowKeyValue] = {
123
+ bgc: '',
124
+ bgc_progress: 0,
125
+ bgc_progress_ms: nowTs,
126
+ };
127
+ highlightDimRowKeys.add(rowKeyValue);
112
128
  }
113
129
  calcHighlightLoop();
114
130
  } else {
@@ -121,8 +137,8 @@ export function useHighlight({ props, tableContainer, rowKeyGen }: Params) {
121
137
  const rowKeyValue = rowKeyValues[i];
122
138
  const rowEl = tableContainer.value?.querySelector<HTMLTableRowElement>(`[data-row-key="${rowKeyValue}"]`);
123
139
  if (!rowEl) continue;
124
- if (rowEl.classList.contains('highlight-row')) {
125
- rowEl.classList.remove('highlight-row');
140
+ if (rowEl.classList.contains(HIGHLIGHT_ROW_CLASS)) {
141
+ rowEl.classList.remove(HIGHLIGHT_ROW_CLASS);
126
142
  needRepaint = true;
127
143
  }
128
144
  rowElTemp.push(rowEl);
@@ -131,7 +147,7 @@ export function useHighlight({ props, tableContainer, rowKeyGen }: Params) {
131
147
  highlightDimRowsTimeout.set(
132
148
  rowKeyValue,
133
149
  window.setTimeout(() => {
134
- rowEl.classList.remove('highlight-row');
150
+ rowEl.classList.remove(HIGHLIGHT_ROW_CLASS);
135
151
  highlightDimRowsTimeout.delete(rowKeyValue); // 回收内存
136
152
  }, Highlight_Duration),
137
153
  );
@@ -139,11 +155,12 @@ export function useHighlight({ props, tableContainer, rowKeyGen }: Params) {
139
155
  if (needRepaint) {
140
156
  void tableContainer.value?.offsetWidth; //强制浏览器重绘
141
157
  }
142
- rowElTemp.forEach(el => el.classList.add('highlight-row')); // 统一添加动画
158
+ rowElTemp.forEach(el => el.classList.add(HIGHLIGHT_ROW_CLASS)); // 统一添加动画
143
159
  }
144
160
  }
145
161
 
146
162
  return {
163
+ highlightRowStore,
147
164
  setHighlightDimRow,
148
165
  setHighlightDimCell,
149
166
  };
@@ -44,11 +44,15 @@ export function useKeyboardArrowScroll<DT extends Record<string, any>>(
44
44
  if (!isMouseOver) return; // 不悬浮还是要触发键盘事件的
45
45
  e.preventDefault(); // 不触发键盘默认的箭头事件
46
46
 
47
- const { scrollTop, rowHeight, pageSize } = virtualScroll.value;
47
+ const { scrollTop, rowHeight, containerHeight } = virtualScroll.value;
48
48
  const { scrollLeft } = virtualScrollX.value;
49
49
  const { headless, headerRowHeight } = props;
50
- /**表头高度 */
50
+
51
+ // 这里不用virtualScroll 中的pageSize,因为我需要上一页的最后一条放在下一页的第一条
51
52
  const headerHeight = headless ? 0 : tableHeaders.value.length * (headerRowHeight || rowHeight);
53
+ /** 表体的page */
54
+ const bodyPageSize = Math.floor((containerHeight - headerHeight) / rowHeight);
55
+
52
56
  if (e.code === SCROLL_CODES[0]) {
53
57
  scrollTo(scrollTop - rowHeight, null);
54
58
  } else if (e.code === SCROLL_CODES[1]) {
@@ -58,9 +62,9 @@ export function useKeyboardArrowScroll<DT extends Record<string, any>>(
58
62
  } else if (e.code === SCROLL_CODES[3]) {
59
63
  scrollTo(null, scrollLeft - rowHeight);
60
64
  } else if (e.code === SCROLL_CODES[4]) {
61
- scrollTo(scrollTop - rowHeight * pageSize - headerHeight, null);
65
+ scrollTo(scrollTop - rowHeight * bodyPageSize + headerHeight, null);
62
66
  } else if (e.code === SCROLL_CODES[5]) {
63
- scrollTo(scrollTop + rowHeight * pageSize - headerHeight, null);
67
+ scrollTo(scrollTop + rowHeight * bodyPageSize - headerHeight, null);
64
68
  }
65
69
  }
66
70
 
@@ -4,10 +4,11 @@ import { StkTableColumn } from './types';
4
4
  import { getColWidth } from './utils';
5
5
 
6
6
  type Option<DT extends Record<string, any>> = {
7
- tableContainer: Ref<HTMLElement | undefined>;
8
7
  props: any;
8
+ tableContainer: Ref<HTMLElement | undefined>;
9
9
  dataSourceCopy: ShallowRef<DT[]>;
10
10
  tableHeaderLast: Ref<StkTableColumn<DT>[]>;
11
+ tableHeaders: Ref<StkTableColumn<DT>[][]>;
11
12
  };
12
13
 
13
14
  /** 暂存纵向虚拟滚动的数据 */
@@ -49,7 +50,13 @@ const VUE2_SCROLL_TIMEOUT_MS = 200;
49
50
  * @param param0
50
51
  * @returns
51
52
  */
52
- export function useVirtualScroll<DT extends Record<string, any>>({ props, tableContainer, dataSourceCopy, tableHeaderLast }: Option<DT>) {
53
+ export function useVirtualScroll<DT extends Record<string, any>>({
54
+ props,
55
+ tableContainer,
56
+ dataSourceCopy,
57
+ tableHeaderLast,
58
+ tableHeaders,
59
+ }: Option<DT>) {
53
60
  const virtualScroll = ref<VirtualScrollStore>({
54
61
  containerHeight: 0,
55
62
  rowHeight: props.rowHeight,
@@ -140,10 +147,14 @@ export function useVirtualScroll<DT extends Record<string, any>>({ props, tableC
140
147
  } else {
141
148
  containerHeight = offsetHeight || Default_Table_Height;
142
149
  }
143
- Object.assign(virtualScroll.value, {
144
- containerHeight,
145
- pageSize: Math.ceil(containerHeight / rowHeight) + 1, // 这里最终+1,因为headless=true无头时,需要上下各预渲染一行。
146
- });
150
+ const { headless, headerRowHeight } = props;
151
+ let pageSize = Math.ceil(containerHeight / rowHeight);
152
+ if (!headless) {
153
+ /** 表头高度占几行表体高度数 */
154
+ const headerToBodyRowHeightCount = Math.floor((tableHeaders.value.length * (headerRowHeight || rowHeight)) / rowHeight);
155
+ pageSize -= headerToBodyRowHeightCount; //减去表头行数
156
+ }
157
+ Object.assign(virtualScroll.value, { containerHeight, pageSize });
147
158
  updateVirtualScrollY(scrollTop);
148
159
  }
149
160
 
@@ -168,9 +179,30 @@ export function useVirtualScroll<DT extends Record<string, any>>({ props, tableC
168
179
  /** 通过滚动条位置,计算虚拟滚动的参数 */
169
180
  function updateVirtualScrollY(sTop = 0) {
170
181
  const { rowHeight, pageSize, scrollTop, startIndex: oldStartIndex } = virtualScroll.value;
171
- const startIndex = Math.floor(sTop / rowHeight);
172
- const offsetTop = startIndex * rowHeight; // startIndex之前的高度
182
+ // 先更新滚动条位置记录,其他地方可能有依赖。(stripe 时ArrowUp/Down滚动依赖)
183
+ virtualScroll.value.scrollTop = sTop;
184
+ let startIndex = Math.floor(sTop / rowHeight);
185
+ if (props.stripe) {
186
+ startIndex -= 1; //预渲染1行
187
+ }
188
+ if (startIndex < 0) {
189
+ startIndex = 0;
190
+ }
191
+ if (props.stripe && startIndex !== 0) {
192
+ const scrollRows = Math.abs(oldStartIndex - startIndex);
193
+ // 斑马纹情况下,每滚动偶数行才加载。防止斑马纹错位。
194
+ if (scrollRows < 2) {
195
+ return;
196
+ } else if (scrollRows % 2) {
197
+ startIndex -= 1; // 奇数-1变成偶数
198
+ }
199
+ }
173
200
  let endIndex = startIndex + pageSize;
201
+ if (props.stripe) {
202
+ // 由于上方预渲染一行,这里也要预渲染1+1行
203
+ endIndex += 2;
204
+ }
205
+ const offsetTop = startIndex * rowHeight; // startIndex之前的高度
174
206
  if (endIndex > dataSourceCopy.value.length) {
175
207
  endIndex = dataSourceCopy.value.length; // 溢出index修正
176
208
  }
@@ -184,13 +216,12 @@ export function useVirtualScroll<DT extends Record<string, any>>({ props, tableC
184
216
  // 向上滚动
185
217
  Object.assign(virtualScroll.value, {
186
218
  startIndex,
187
- offsetTop,
188
219
  endIndex,
189
- scrollTop: sTop,
220
+ offsetTop,
190
221
  });
191
222
  } else {
192
223
  // vue2向下滚动优化
193
- Object.assign(virtualScroll.value, { endIndex, scrollTop: sTop });
224
+ virtualScroll.value.endIndex = endIndex;
194
225
  vue2ScrollYTimeout = window.setTimeout(() => {
195
226
  Object.assign(virtualScroll.value, { startIndex, offsetTop });
196
227
  }, VUE2_SCROLL_TIMEOUT_MS);
@@ -1,6 +1,15 @@
1
1
  import { Default_Col_Width } from './const';
2
2
  import { Order, SortConfig, SortOption, SortState, StkTableColumn } from './types';
3
3
 
4
+ /** 是否空值 */
5
+ function isEmptyValue(val: any, isNumber?: boolean) {
6
+ let isEmpty = val === null || val === '';
7
+ if (isNumber) {
8
+ isEmpty ||= typeof val === 'boolean' || Number.isNaN(+val);
9
+ }
10
+ return isEmpty;
11
+ }
12
+
4
13
  /**
5
14
  * 对有序数组插入新数据
6
15
  * @param sortState
@@ -10,15 +19,30 @@ import { Order, SortConfig, SortOption, SortState, StkTableColumn } from './type
10
19
  * @param newItem 要插入的数据
11
20
  * @param targetArray 表格数据
12
21
  */
13
- export function insertToOrderedArray<T extends object>(sortState: SortState<keyof T>, newItem: any, targetArray: T[]) {
22
+ export function insertToOrderedArray<T extends object>(
23
+ sortState: SortState<keyof T>,
24
+ newItem: any,
25
+ targetArray: T[],
26
+ sortConfig: SortConfig<T> = {},
27
+ ) {
14
28
  const { dataIndex, order } = sortState;
29
+ sortConfig = { emptyToBottom: false, ...sortConfig };
15
30
  let { sortType } = sortState;
16
31
  if (!sortType) sortType = typeof newItem[dataIndex] as 'number' | 'string';
32
+ const isNumber = sortType === 'number';
17
33
  const data = [...targetArray];
34
+
18
35
  if (!order) {
36
+ // 没有排序的情况,插入在最上方
19
37
  data.unshift(newItem);
20
38
  return data;
21
39
  }
40
+
41
+ if (sortConfig.emptyToBottom && isEmptyValue(data)) {
42
+ // 空值排在最下方
43
+ data.push(newItem);
44
+ }
45
+
22
46
  // 二分插入
23
47
  let sIndex = 0;
24
48
  let eIndex = data.length - 1;
@@ -27,7 +51,7 @@ export function insertToOrderedArray<T extends object>(sortState: SortState<keyo
27
51
  // console.log(sIndex, eIndex);
28
52
  const midIndex = Math.floor((sIndex + eIndex) / 2);
29
53
  const midVal: any = data[midIndex][dataIndex];
30
- const compareRes = strCompare(midVal, targetVal, sortType);
54
+ const compareRes = strCompare(midVal, targetVal, isNumber, sortConfig.stringLocaleCompare);
31
55
  if (compareRes === 0) {
32
56
  //midVal == targetVal
33
57
  sIndex = midIndex;
@@ -47,41 +71,44 @@ export function insertToOrderedArray<T extends object>(sortState: SortState<keyo
47
71
  }
48
72
  /**
49
73
  * 字符串比较
50
- * @param a
51
- * @param b
52
- * @param type 类型
74
+ * @param a
75
+ * @param b
76
+ * @param type 类型
77
+ * @param isNumber 是否是数字类型
78
+ * @param localeCompare 是否 使用Array.prototype.localeCompare
53
79
  * @return {-1|0|1}
54
80
  */
55
- function strCompare(a: string, b: string, type: 'number' | 'string'): number {
56
- // if (typeof a === 'number' && typeof b === 'number') type = 'number';
57
- if (type === 'number') {
58
- if (+a > +b) return 1;
59
- else if (+a === +b) return 0;
60
- else return -1;
61
- } else {
81
+ function strCompare(a: string, b: string, isNumber: boolean, localeCompare = false): number {
82
+ let _a: number | string = a;
83
+ let _b: number | string = b;
84
+ if (isNumber) {
85
+ // 是数字就转数字
86
+ _a = +a;
87
+ _b = +b;
88
+ } else if (localeCompare) {
89
+ // 字符串才可以localeCompare
62
90
  return String(a).localeCompare(b);
63
91
  }
92
+ if (_a > _b) return 1;
93
+ else if (_a === _b) return 0;
94
+ else return -1;
64
95
  }
65
96
 
66
97
  /**
67
- * 分离出空数据和非空数据成两个数组
98
+ * 分离出空数据和非空数据成两个数组。NaN视为空数据。
68
99
  * @param sortOption
69
100
  * @param targetDataSource
70
- * @param isNumber 1 数组
101
+ * @param isNumber 是否数字
71
102
  * @return [值数组,空数组]
72
103
  */
73
- function separatedData(sortOption: SortOption, targetDataSource: any[], isNumber?: boolean) {
74
- const emptyArr: any[] = [];
75
- const valueArr: any[] = [];
104
+ function separatedData<T extends Record<string, any>>(sortOption: SortOption<T>, targetDataSource: T[], isNumber?: boolean) {
105
+ const emptyArr: T[] = [];
106
+ const valueArr: T[] = [];
76
107
 
77
108
  for (let i = 0; i < targetDataSource.length; i++) {
78
109
  const row = targetDataSource[i];
79
110
  const sortField = sortOption.sortField || sortOption.dataIndex;
80
- let isEmpty = row[sortField] === null || row[sortField] === '';
81
- if (isNumber) {
82
- isEmpty ||= typeof row[sortField] === 'boolean' || Number.isNaN(+row[sortField]);
83
- }
84
-
111
+ const isEmpty = isEmptyValue(row[sortField], isNumber);
85
112
  if (isEmpty) {
86
113
  emptyArr.push(row);
87
114
  } else {
@@ -96,48 +123,49 @@ function separatedData(sortOption: SortOption, targetDataSource: any[], isNumber
96
123
  * 可以在组件外部自己实现表格排序,组件配置remote,使表格不排序。
97
124
  * 使用者在@sort-change事件中自行更改table props 'dataSource'完成排序。
98
125
  * TODO: key 唯一值,排序字段相同时,根据唯一值排序。
126
+ *
127
+ * sortConfig.defaultSort 会在order为null时生效
99
128
  * @param sortOption 列配置
100
129
  * @param order 排序方式
101
130
  * @param dataSource 排序的数组
102
131
  */
103
- export function tableSort(sortOption: SortOption, order: Order, dataSource: any[], sortConfig: SortConfig = {}): any[] {
132
+ export function tableSort<T extends Record<string, any>>(
133
+ sortOption: SortOption<T>,
134
+ order: Order,
135
+ dataSource: T[],
136
+ sortConfig: SortConfig<T> = {},
137
+ ): T[] {
104
138
  if (!dataSource?.length) return dataSource || [];
105
- sortConfig = Object.assign({ emptyToBottom: false } as SortConfig, sortConfig);
139
+ sortConfig = { emptyToBottom: false, ...sortConfig };
106
140
  let targetDataSource = [...dataSource];
141
+ let sortField = sortOption.sortField || sortOption.dataIndex;
142
+
143
+ if (!order && sortConfig.defaultSort) {
144
+ // 默认排序
145
+ order = sortConfig.defaultSort.order;
146
+ sortField = sortConfig.defaultSort.dataIndex;
147
+ }
107
148
 
108
149
  if (typeof sortOption.sorter === 'function') {
109
150
  const customSorterData = sortOption.sorter(targetDataSource, { order, column: sortOption });
110
151
  if (customSorterData) targetDataSource = customSorterData;
111
152
  } else if (order) {
112
- const sortField = sortOption.sortField || sortOption.dataIndex;
113
153
  let { sortType } = sortOption;
114
154
  if (!sortType) sortType = typeof dataSource[0][sortField] as 'number' | 'string';
115
155
 
116
156
  const [valueArr, emptyArr] = separatedData(sortOption, targetDataSource, sortType === 'number');
157
+ const isNumber = sortType === 'number';
117
158
 
118
- if (sortType === 'number') {
119
- // 按数字类型排序
120
- // 非数字当作最小值处理
121
- if (order === 'asc') {
122
- valueArr.sort((a, b) => +a[sortField] - +b[sortField]);
123
- targetDataSource = [...emptyArr, ...valueArr];
124
- } else {
125
- valueArr.sort((a, b) => +b[sortField] - +a[sortField]);
126
- targetDataSource = [...valueArr, ...emptyArr];
127
- }
159
+ if (order === 'asc') {
160
+ valueArr.sort((a, b) => strCompare(a[sortField], b[sortField], isNumber, sortConfig.stringLocaleCompare));
128
161
  } else {
129
- // 按string 排序
130
- if (order === 'asc') {
131
- valueArr.sort((a, b) => String(a[sortField]).localeCompare(b[sortField]));
132
- targetDataSource = [...emptyArr, ...valueArr];
133
- } else {
134
- valueArr.sort((a, b) => String(a[sortField]).localeCompare(b[sortField]) * -1);
135
- targetDataSource = [...valueArr, ...emptyArr];
136
- }
162
+ valueArr.sort((a, b) => strCompare(b[sortField], a[sortField], isNumber, sortConfig.stringLocaleCompare));
137
163
  }
138
164
 
139
- if (sortConfig.emptyToBottom) {
165
+ if (order === 'desc' || sortConfig.emptyToBottom) {
140
166
  targetDataSource = [...valueArr, ...emptyArr];
167
+ } else {
168
+ targetDataSource = [...emptyArr, ...valueArr];
141
169
  }
142
170
  }
143
171
  return targetDataSource;
@@ -156,8 +184,18 @@ export function howDeepTheHeader(arr: StkTableColumn<any>[], level = 1) {
156
184
 
157
185
  /** 获取列宽 */
158
186
  export function getColWidth(col: StkTableColumn<any> | null): number {
159
- if (typeof col?.width === 'number') {
160
- return Math.floor(col.width ?? Default_Col_Width);
187
+ const val = col?.width ?? Default_Col_Width;
188
+ if (typeof val === 'number') {
189
+ return Math.floor(val);
190
+ }
191
+ return parseInt(val);
192
+ }
193
+
194
+ /** 获取列宽配置。用于支持列宽配置数字 */
195
+ export function getColWidthStr(col: StkTableColumn<any> | null | undefined, key: 'width' | 'minWidth' | 'maxWidth' = 'width') {
196
+ const val = col?.[key];
197
+ if (typeof val === 'number') {
198
+ return val + 'px';
161
199
  }
162
- return parseInt(col?.width ?? Default_Col_Width);
200
+ return val;
163
201
  }