stk-table-vue 0.4.15 → 0.5.0

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.
@@ -41,6 +41,9 @@
41
41
  --fixed-col-shadow-color-from: rgba(0, 0, 0, 0.1);
42
42
  --fixed-col-shadow-color-to: rgba(0, 0, 0, 0);
43
43
 
44
+ /** 拖动行hover背景 */
45
+ --drag-handle-hover-color: #d0d1e0;
46
+
44
47
  position: relative;
45
48
  overflow: auto;
46
49
  display: flex;
@@ -83,6 +86,8 @@
83
86
  --fixed-col-shadow-color-from: rgba(135, 135, 156, 0.1);
84
87
  --fixed-col-shadow-color-to: rgba(135, 135, 156, 0);
85
88
 
89
+ --drag-handle-hover-color: #5d6064;
90
+
86
91
  /* background-color: var(--table-bgc); */
87
92
  /* ⭐这里加background-color会导致表格出滚动白屏*/
88
93
  color: #d1d1e0;
@@ -170,10 +175,10 @@
170
175
  }
171
176
 
172
177
  /* 单元格高亮 */
173
- &.cell-active tbody td.active{
178
+ &.cell-active tbody td.active {
174
179
  box-shadow: inset 0 0 0 2px var(--td-active-color);
175
180
  }
176
-
181
+
177
182
  /* td 溢出*/
178
183
  &.text-overflow {
179
184
  .table-cell-wrapper {
@@ -202,7 +207,7 @@
202
207
  /* 为不影响布局,表头行高要定死*/
203
208
  .table-header-cell-wrapper {
204
209
  overflow: hidden;
205
- max-height: calc(var(--header-row-height) * var(--row-span));
210
+ max-height: calc(var(--header-row-height) * var(--row-span, 1));
206
211
  }
207
212
 
208
213
  tbody td {
@@ -217,6 +222,14 @@
217
222
  .padding-top-tr td {
218
223
  height: 0;
219
224
  }
225
+
226
+ .expand-cell .table-cell-wrapper {
227
+ white-space: nowrap;
228
+ }
229
+
230
+ // .expanded-row .table-cell-wrapper{
231
+ // overflow: auto;
232
+ // }
220
233
  }
221
234
 
222
235
  &.fixed-relative-mode {
@@ -345,6 +358,68 @@
345
358
  text-align: center;
346
359
  }
347
360
 
361
+ .expand-cell {
362
+ cursor: pointer;
363
+
364
+ .table-cell-wrapper {
365
+ &.expanded-cell-wrapper {
366
+ &::before {
367
+ content: '';
368
+ display: inline-block;
369
+ margin: 0 2px;
370
+ width: 0;
371
+ height: 0;
372
+ border-left: 5px solid #757699;
373
+ border-top: 4px solid transparent;
374
+ border-bottom: 4px solid transparent;
375
+ transition: transform 0.2s ease;
376
+ }
377
+
378
+ >span {
379
+ margin-left: var(--cell-padding-x)
380
+ }
381
+ }
382
+ }
383
+
384
+ &.expanded {
385
+ .table-cell-wrapper::before {
386
+ transform: rotate(90deg);
387
+ }
388
+ }
389
+ }
390
+
391
+ .drag-row-cell {
392
+ .table-cell-wrapper {
393
+ display: inline-flex;
394
+ align-items: center;
395
+ }
396
+
397
+ .drag-row-handle {
398
+ cursor: grab;
399
+ border-radius: 4px;
400
+
401
+ &:hover {
402
+ background-color: var(--drag-handle-hover-color);
403
+ }
404
+
405
+ &:active {
406
+ cursor: grabbing;
407
+ }
408
+
409
+ >svg {
410
+ vertical-align: -2px;
411
+ }
412
+ }
413
+ }
414
+
415
+ .tr-dragging {
416
+ opacity: .5;
417
+ }
418
+
419
+ .tr-dragging-over {
420
+ background-color: var(--tr-hover-bgc);
421
+ }
422
+
348
423
  /*固定列阴影-左*/
349
424
  .fixed-cell--left {
350
425
  --shadow-rotate: 90deg;
@@ -453,4 +528,6 @@
453
528
  }
454
529
  }
455
530
 
531
+
532
+
456
533
  }
@@ -1,4 +1,4 @@
1
- import { Component, ConcreteComponent, VNode } from 'vue';
1
+ import { Component, ConcreteComponent } from 'vue';
2
2
 
3
3
  /** 排序方式,asc-正序,desc-倒序,null-默认顺序 */
4
4
  export type Order = null | 'asc' | 'desc';
@@ -12,26 +12,20 @@ export type CustomCellProps<T extends Record<string, any>> = {
12
12
  cellValue: any;
13
13
  rowIndex: number;
14
14
  colIndex: number;
15
+ /**
16
+ * 当前行是否展开
17
+ * - 不展开: null
18
+ * - 展开: 返回column配置
19
+ */
20
+ expanded?: PrivateRowDT['__EXPANDED__'];
15
21
  };
16
22
 
17
- /**
18
- * $*$自定义单元格渲染函数
19
- * @deprecated
20
- */
21
- export type CustomCellFunc<T extends Record<string, any>> = (props: CustomCellProps<T>) => VNode;
22
-
23
23
  export type CustomHeaderCellProps<T extends Record<string, any>> = {
24
24
  col: StkTableColumn<T>;
25
25
  rowIndex: number;
26
26
  colIndex: number;
27
27
  };
28
28
 
29
- /**
30
- * $*$自定义表头渲染函数
31
- * @deprecated
32
- */
33
- export type CustomHeaderCellFunc<T extends Record<string, any>> = (props: CustomHeaderCellProps<T>) => VNode;
34
-
35
29
  /**
36
30
  * 自定义渲染单元格
37
31
  *
@@ -48,11 +42,18 @@ export type CustomCell<T extends CustomCellProps<U> | CustomHeaderCellProps<U>,
48
42
 
49
43
  /** 表格列配置 */
50
44
  export type StkTableColumn<T extends Record<string, any>> = {
45
+ /**
46
+ * 用于区分相同dataIndex 的列。
47
+ * 需要自行配置colKey="(col: StkTableColumn<any>) => col.key ?? col.dataIndex"
48
+ */
49
+ key?: any;
51
50
  /**
52
51
  * 列类型
53
52
  * - seq 序号列
53
+ * - expand 展开列
54
+ * - dragRow 拖拽列(使用sktTableRef.getTableData 获取改变后的顺序)
54
55
  */
55
- type?: 'seq';
56
+ type?: 'seq' | 'expand' | 'dragRow';
56
57
  /** 取值id */
57
58
  dataIndex: keyof T & string;
58
59
  /** 表头文字 */
@@ -103,27 +104,49 @@ export type StkTableColumn<T extends Record<string, any>> = {
103
104
  customHeaderCell?: CustomCell<CustomHeaderCellProps<T>, T>;
104
105
  /** 二级表头 */
105
106
  children?: StkTableColumn<T>[];
106
- /** private 父节点引用 */
107
+ };
108
+
109
+ /** private StkTableColumn type. Add some private key */
110
+ export type PrivateStkTableColumn<T extends Record<string, any>> = StkTableColumn<T> & {
111
+ /**
112
+ * 父节点引用
113
+ * @private
114
+ */
107
115
  __PARENT__?: StkTableColumn<T> | null;
108
- /** private 保存计算的宽度。横向虚拟滚动用。 */
116
+ /**
117
+ * 保存计算的宽度。横向虚拟滚动用。
118
+ * @private
119
+ */
109
120
  __WIDTH__?: number;
110
121
  };
122
+ /** private row keys */
123
+ export type PrivateRowDT = {
124
+ /**
125
+ * Only expanded row will add this key
126
+ *
127
+ * If user define the `__ROW_KEY__` in table data, this value will be used as the row key
128
+ * @private
129
+ */
130
+ __ROW_KEY__: string;
131
+ /**
132
+ * if row expanded
133
+ * @private
134
+ */
135
+ __EXPANDED__?: StkTableColumn<any> | null;
136
+ };
111
137
 
112
138
  export type SortOption<T extends Record<string, any>> = Pick<StkTableColumn<T>, 'sorter' | 'dataIndex' | 'sortField' | 'sortType'>;
113
139
 
114
- /** 排序状态 */
115
140
  export type SortState<T> = {
116
141
  dataIndex: keyof T;
117
142
  order: Order;
118
143
  sortType?: 'number' | 'string';
119
144
  };
120
145
 
121
- /** 唯一键 */
122
146
  export type UniqKey = string | number;
123
147
  export type UniqKeyFun = (param: any) => UniqKey;
124
148
  export type UniqKeyProp = UniqKey | UniqKeyFun;
125
149
 
126
- /** 排序配置 */
127
150
  export type SortConfig<T extends Record<string, any>> = {
128
151
  /** 空值始终排在列表末尾 */
129
152
  emptyToBottom?: boolean;
@@ -147,22 +170,53 @@ export type SortConfig<T extends Record<string, any>> = {
147
170
  stringLocaleCompare?: boolean;
148
171
  };
149
172
 
150
- /** th td类型 */
173
+ /** th td type */
151
174
  export const enum TagType {
152
175
  TH,
153
176
  TD,
154
177
  }
155
178
 
156
- /** 高亮配置 */
157
179
  export type HighlightConfig = {
158
- /** 高亮持续时间(s) */
180
+ /** Duration of the highlight in seconds */
159
181
  duration?: number;
160
- /** 高亮帧率 */
182
+ /** Frame rate of the highlight */
161
183
  fps?: number;
162
184
  };
163
185
 
164
- /** 序号列配置 */
186
+ /**
187
+ * Configuration options for the sequence column.
188
+ */
165
189
  export type SeqConfig = {
166
190
  /** 序号列起始下标 用于适配分页 */
167
191
  startIndex?: number;
168
192
  };
193
+
194
+ /** Configuration options for the expand column */
195
+ export type ExpandConfig = {
196
+ height?: number;
197
+ };
198
+
199
+ export type ExpandedRow = PrivateRowDT & {
200
+ __EXPANDED_ROW__: any;
201
+ __EXPANDED_COL__: any;
202
+ };
203
+
204
+ /** drag row config */
205
+ export type DragRowConfig = {
206
+ mode?: 'none' | 'insert' | 'swap';
207
+ // disabled?: (row: T, rowIndex: number) => boolean;
208
+ };
209
+ /** header drag config */
210
+ export type HeaderDragConfig<DT extends Record<string, any> = any> =
211
+ | boolean
212
+ | {
213
+ /**
214
+ * 列交换模式
215
+ * - none - 不做任何事
216
+ * - insert - 插入(默认值)
217
+ * - swap - 交换
218
+ */
219
+ mode?: 'none' | 'insert' | 'swap';
220
+ /** 禁用拖动的列 */
221
+ disabled?: (col: StkTableColumn<DT>) => boolean;
222
+ };
@@ -1,72 +1,102 @@
1
- import { StkTableColumn } from './types';
1
+ import { computed, ComputedRef } from 'vue';
2
+ import { StkTableColumn, UniqKey } from './types';
2
3
 
3
- type Params = {
4
+ type Params<T extends Record<string, any>> = {
4
5
  props: any;
5
6
  emits: any;
7
+ colKeyGen: ComputedRef<(col: StkTableColumn<T>) => UniqKey>;
6
8
  };
7
9
  /**
8
10
  * 列顺序拖动
9
- * @param param0
10
11
  * @returns
11
12
  */
12
- export function useThDrag<DT extends Record<string, any>>({ props, emits }: Params) {
13
- let dragStartKey: string | undefined = void 0;
14
-
15
- function findParentTH(el: HTMLElement | Node) {
16
- let n: any = el;
17
- while (n) {
18
- if (n.tagName === 'TH') return n;
19
- n = n.parentElement;
20
- }
21
- }
13
+ export function useThDrag<DT extends Record<string, any>>({ props, emits, colKeyGen }: Params<DT>) {
14
+ const findParentTH = (e: DragEvent) => (e.target as HTMLElement).closest('th');
15
+
16
+ const dragConfig = computed(() => {
17
+ const headerDrag = props.headerDrag;
18
+ const draggable = headerDrag !== false;
19
+ return {
20
+ draggable,
21
+ mode: 'insert',
22
+ disabled: () => !draggable,
23
+ ...headerDrag,
24
+ };
25
+ });
26
+
22
27
  /** 开始拖动记录th位置 */
23
- function onThDragStart(e: MouseEvent) {
24
- // const i = Array.prototype.indexOf.call(e.target.parentNode.children, e.target); // 得到是第几个子元素
25
- const th = findParentTH(e.target as HTMLElement | Node);
28
+ function onThDragStart(e: DragEvent) {
29
+ const th = findParentTH(e);
26
30
  if (!th) return;
31
+ const dragStartKey = th.dataset.colKey || '';
32
+ const dt = e.dataTransfer;
33
+ if (dt) {
34
+ dt.effectAllowed = 'move';
35
+ dt.setData('text/plain', dragStartKey);
36
+ }
27
37
 
28
- dragStartKey = th.dataset.colKey;
29
38
  emits('th-drag-start', dragStartKey);
30
39
  }
31
40
 
32
- function onThDragOver(e: MouseEvent) {
33
- const th = findParentTH(e.target as HTMLElement | Node);
41
+ function onThDragOver(e: DragEvent) {
42
+ const th = findParentTH(e);
34
43
  if (!th) return;
35
44
 
36
45
  const isHeaderDraggable = th.getAttribute('draggable') === 'true';
37
- if (!isHeaderDraggable) {
38
- // 不可drag的表头不可被覆盖
39
- return;
46
+ if (!isHeaderDraggable) return;
47
+
48
+ const dt = e.dataTransfer;
49
+ if (dt) {
50
+ dt.dropEffect = 'move';
40
51
  }
41
52
  e.preventDefault();
42
53
  }
43
54
 
44
55
  /** th拖动释放时 */
45
- function onThDrop(e: MouseEvent) {
46
- const th = findParentTH(e.target as HTMLElement | Node);
56
+ function onThDrop(e: DragEvent) {
57
+ const th = findParentTH(e);
47
58
  if (!th) return;
59
+ const dragStartKey = e.dataTransfer?.getData('text');
48
60
 
49
- // const i = Array.prototype.indexOf.call(th.parentNode.children, th); // 得到是第几个子元素
50
61
  if (dragStartKey !== th.dataset.colKey) {
51
- emits('col-order-change', dragStartKey, th.dataset.colKey);
62
+ handleColOrderChange(dragStartKey, th.dataset.colKey);
52
63
  }
53
64
  emits('th-drop', th.dataset.colKey);
54
65
  }
55
66
 
56
- const isHeaderDragFun = typeof props.headerDrag === 'function';
57
- /** 是否可拖拽 */
58
- function isHeaderDraggable(col: StkTableColumn<DT>) {
59
- if (isHeaderDragFun) {
60
- return props.headerDrag(col);
61
- } else {
62
- return props.headerDrag;
67
+ /** 列拖动交换顺序 */
68
+ function handleColOrderChange(dragStartKey: string | undefined, dragEndKey: string | undefined) {
69
+ if (!dragStartKey || !dragEndKey) return;
70
+
71
+ if (dragConfig.value.mode !== 'none') {
72
+ const columns = [...props.columns];
73
+
74
+ const dragStartIndex = columns.findIndex(col => colKeyGen.value(col) === dragStartKey);
75
+ const dragEndIndex = columns.findIndex(col => colKeyGen.value(col) === dragEndKey);
76
+
77
+ if (dragStartIndex === -1 || dragEndIndex === -1) return;
78
+
79
+ const dragStartCol = columns[dragStartIndex];
80
+ // if mode is none, do nothing
81
+ if (dragConfig.value.mode === 'swap') {
82
+ columns[dragStartIndex] = columns[dragEndIndex];
83
+ columns[dragEndIndex] = dragStartCol;
84
+ } else {
85
+ // default is insert
86
+ columns.splice(dragStartIndex, 1);
87
+ columns.splice(dragEndIndex, 0, dragStartCol);
88
+ }
89
+ emits('update:columns', columns);
63
90
  }
91
+
92
+ emits('col-order-change', dragStartKey, dragEndKey);
64
93
  }
65
94
 
66
95
  return {
67
96
  onThDragStart,
68
97
  onThDragOver,
69
98
  onThDrop,
70
- isHeaderDraggable,
99
+ /** 是否可拖拽 */
100
+ isHeaderDraggable: dragConfig.value.disabled,
71
101
  };
72
102
  }
@@ -0,0 +1,118 @@
1
+ import { computed, ShallowRef } from 'vue';
2
+ import { DragRowConfig } from './types';
3
+
4
+ type Params = {
5
+ props: any;
6
+ emits: any;
7
+ dataSourceCopy: ShallowRef<any[]>;
8
+ };
9
+
10
+ const TR_DRAGGING_CLASS = 'tr-dragging';
11
+ const TR_DRAG_OVER_CLASS = 'tr-dragging-over';
12
+ const DATA_TRANSFER_FORMAT = 'text/plain';
13
+
14
+ /**
15
+ * 拖拽行
16
+ * TODO: 不在虚拟滚动的情况下,从上面拖拽到下面,跨页的时候,滚动条会自适应位置。没有顶上去
17
+ */
18
+ export function useTrDrag({ props, emits, dataSourceCopy }: Params) {
19
+ let trDragFlag = false;
20
+
21
+ const dragRowConfig = computed<DragRowConfig>(() => {
22
+ return { mode: 'insert', ...props.dragRowConfig };
23
+ });
24
+
25
+ function getClosestTr(e: DragEvent) {
26
+ const target = e.target as HTMLElement;
27
+ const tr = target?.closest('tr');
28
+ return tr;
29
+ }
30
+
31
+ function onTrDragStart(e: DragEvent, rowIndex: number) {
32
+ const tr = getClosestTr(e);
33
+ if (tr) {
34
+ const trRect = tr.getBoundingClientRect();
35
+ const x = e.clientX - (trRect.left ?? 0);
36
+ e.dataTransfer?.setDragImage(tr, x, trRect.height / 2);
37
+ tr.classList.add(TR_DRAGGING_CLASS);
38
+ }
39
+ const dt = e.dataTransfer;
40
+ if (dt) {
41
+ dt.effectAllowed = 'move';
42
+ dt.setData(DATA_TRANSFER_FORMAT, String(rowIndex));
43
+ }
44
+ trDragFlag = true;
45
+ }
46
+
47
+ function onTrDragOver(e: DragEvent) {
48
+ if (!trDragFlag) return;
49
+ e.preventDefault(); // 阻止默认行为,否则不会触发 drop 事件
50
+
51
+ const dt = e.dataTransfer;
52
+ if (dt) {
53
+ dt.dropEffect = 'move';
54
+ }
55
+ }
56
+
57
+ let oldTr: HTMLElement | null = null;
58
+ function onTrDragEnter(e: DragEvent) {
59
+ if (!trDragFlag) return;
60
+ e.preventDefault();
61
+ const tr = getClosestTr(e);
62
+ if (oldTr && oldTr !== tr) {
63
+ // 两个tr不一样说明移动到了另一个tr中
64
+ oldTr.classList.remove(TR_DRAG_OVER_CLASS);
65
+ }
66
+ if (tr) {
67
+ oldTr = tr;
68
+ tr.classList.add(TR_DRAG_OVER_CLASS);
69
+ }
70
+ }
71
+
72
+ function onTrDragEnd(e: DragEvent) {
73
+ if (!trDragFlag) return;
74
+ const tr = getClosestTr(e);
75
+ if (tr) {
76
+ tr.classList.remove(TR_DRAGGING_CLASS);
77
+ }
78
+ if (oldTr) {
79
+ oldTr.classList.remove(TR_DRAG_OVER_CLASS);
80
+ oldTr = null;
81
+ }
82
+ trDragFlag = false;
83
+ }
84
+
85
+ function onTrDrop(e: DragEvent, rowIndex: number) {
86
+ if (!trDragFlag) return;
87
+ const dt = e.dataTransfer;
88
+ if (!dt) return;
89
+ const mode = dragRowConfig.value.mode;
90
+ const sourceIndex = Number(dt.getData(DATA_TRANSFER_FORMAT));
91
+ dt.clearData(DATA_TRANSFER_FORMAT);
92
+ const endIndex = rowIndex;
93
+ if (sourceIndex === endIndex) return;
94
+
95
+ if (mode !== 'none') {
96
+ const dataSourceTemp = [...dataSourceCopy.value];
97
+ const sourceRow = dataSourceTemp[sourceIndex];
98
+ if (mode === 'swap') {
99
+ dataSourceTemp[sourceIndex] = dataSourceTemp[endIndex];
100
+ dataSourceTemp[endIndex] = sourceRow;
101
+ } else {
102
+ dataSourceTemp.splice(sourceIndex, 1);
103
+ dataSourceTemp.splice(endIndex, 0, sourceRow);
104
+ }
105
+ dataSourceCopy.value = [...dataSourceTemp];
106
+ }
107
+ emits('row-order-change', sourceIndex, endIndex);
108
+ }
109
+
110
+ return {
111
+ dragRowConfig,
112
+ onTrDragStart,
113
+ onTrDragEnter,
114
+ onTrDragOver,
115
+ onTrDrop,
116
+ onTrDragEnd,
117
+ };
118
+ }
@@ -6,7 +6,6 @@ import { getCalculatedColWidth } from './utils';
6
6
  type Option<DT extends Record<string, any>> = {
7
7
  props: any;
8
8
  tableContainerRef: Ref<HTMLElement | undefined>;
9
- theadRef: Ref<HTMLElement | undefined>;
10
9
  dataSourceCopy: ShallowRef<DT[]>;
11
10
  tableHeaderLast: ShallowRef<StkTableColumn<DT>[]>;
12
11
  tableHeaders: ShallowRef<StkTableColumn<DT>[][]>;
@@ -58,7 +57,6 @@ const VUE2_SCROLL_TIMEOUT_MS = 200;
58
57
  export function useVirtualScroll<DT extends Record<string, any>>({
59
58
  props,
60
59
  tableContainerRef,
61
- theadRef,
62
60
  dataSourceCopy,
63
61
  tableHeaderLast,
64
62
  tableHeaders,
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_COL_WIDTH, STK_ID_PREFIX } from '../const';
2
- import { Order, SortConfig, SortOption, SortState, StkTableColumn } from '../types';
2
+ import { Order, PrivateStkTableColumn, SortConfig, SortOption, SortState, StkTableColumn } from '../types';
3
3
 
4
4
  /** 是否空值 */
5
5
  function isEmptyValue(val: any, isNumber?: boolean) {
@@ -195,7 +195,7 @@ export function getColWidth(col: StkTableColumn<any> | null): number {
195
195
  }
196
196
 
197
197
  /** 获取计算后的宽度 */
198
- export function getCalculatedColWidth(col: StkTableColumn<any> | null) {
198
+ export function getCalculatedColWidth(col: PrivateStkTableColumn<any> | null) {
199
199
  return col?.__WIDTH__ ?? +DEFAULT_COL_WIDTH;
200
200
  }
201
201