@hbdlzy/ui-core 0.1.5 → 0.1.7

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.
Files changed (57) hide show
  1. package/README.md +4 -0
  2. package/components.manifest.json +37 -1
  3. package/dist/components/BaseTable/BaseTable.types.d.ts +17 -2
  4. package/dist/components/BaseTable/BaseTable.vue.d.ts +4 -4
  5. package/dist/components/BaseTable/index.d.ts +1 -1
  6. package/dist/components/OutlinedCascader/OutlinedCascader.types.d.ts +3 -0
  7. package/dist/components/OutlinedCascader/OutlinedCascader.vue.d.ts +9 -0
  8. package/dist/components/OutlinedDatePicker/OutlinedDatePicker.types.d.ts +3 -0
  9. package/dist/components/OutlinedDatePicker/OutlinedDatePicker.vue.d.ts +9 -0
  10. package/dist/components/OutlinedDateTimePicker/OutlinedDateTimePicker.types.d.ts +3 -0
  11. package/dist/components/OutlinedDateTimePicker/OutlinedDateTimePicker.vue.d.ts +9 -0
  12. package/dist/components/OutlinedInput/OutlinedInput.types.d.ts +3 -0
  13. package/dist/components/OutlinedInput/OutlinedInput.vue.d.ts +9 -0
  14. package/dist/components/OutlinedSelect/OutlinedSelect.types.d.ts +3 -0
  15. package/dist/components/OutlinedSelect/OutlinedSelect.vue.d.ts +9 -0
  16. package/dist/components/OutlinedTimePicker/OutlinedTimePicker.types.d.ts +3 -0
  17. package/dist/components/OutlinedTimePicker/OutlinedTimePicker.vue.d.ts +9 -0
  18. package/dist/components/OutlinedTreeSelect/OutlinedTreeSelect.types.d.ts +3 -0
  19. package/dist/components/OutlinedTreeSelect/OutlinedTreeSelect.vue.d.ts +9 -0
  20. package/dist/components/SvgIcon/SvgIcon.types.d.ts +6 -0
  21. package/dist/components/SvgIcon/SvgIcon.vue.d.ts +32 -0
  22. package/dist/components/SvgIcon/index.d.ts +3 -0
  23. package/dist/index.cjs +5 -5
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.js +1114 -241
  26. package/dist/style.css +1 -1
  27. package/package.json +1 -1
  28. package/src/components/BaseTable/BaseTable.types.ts +19 -2
  29. package/src/components/BaseTable/BaseTable.vue +1089 -105
  30. package/src/components/BaseTable/README.md +88 -5
  31. package/src/components/BaseTable/index.ts +5 -0
  32. package/src/components/OutlinedCascader/OutlinedCascader.types.ts +3 -0
  33. package/src/components/OutlinedCascader/OutlinedCascader.vue +71 -6
  34. package/src/components/OutlinedCascader/README.md +3 -0
  35. package/src/components/OutlinedDatePicker/OutlinedDatePicker.types.ts +3 -0
  36. package/src/components/OutlinedDatePicker/OutlinedDatePicker.vue +71 -6
  37. package/src/components/OutlinedDatePicker/README.md +3 -0
  38. package/src/components/OutlinedDateTimePicker/OutlinedDateTimePicker.types.ts +3 -0
  39. package/src/components/OutlinedDateTimePicker/OutlinedDateTimePicker.vue +144 -64
  40. package/src/components/OutlinedDateTimePicker/README.md +3 -0
  41. package/src/components/OutlinedInput/OutlinedInput.types.ts +3 -0
  42. package/src/components/OutlinedInput/OutlinedInput.vue +87 -8
  43. package/src/components/OutlinedInput/README.md +3 -0
  44. package/src/components/OutlinedSelect/OutlinedSelect.types.ts +3 -0
  45. package/src/components/OutlinedSelect/OutlinedSelect.vue +72 -6
  46. package/src/components/OutlinedSelect/README.md +3 -0
  47. package/src/components/OutlinedTimePicker/OutlinedTimePicker.types.ts +3 -0
  48. package/src/components/OutlinedTimePicker/OutlinedTimePicker.vue +75 -8
  49. package/src/components/OutlinedTimePicker/README.md +3 -0
  50. package/src/components/OutlinedTreeSelect/OutlinedTreeSelect.types.ts +3 -0
  51. package/src/components/OutlinedTreeSelect/OutlinedTreeSelect.vue +72 -6
  52. package/src/components/OutlinedTreeSelect/README.md +3 -0
  53. package/src/components/SvgIcon/README.md +53 -0
  54. package/src/components/SvgIcon/SvgIcon.types.ts +6 -0
  55. package/src/components/SvgIcon/SvgIcon.vue +68 -0
  56. package/src/components/SvgIcon/index.ts +5 -0
  57. package/src/index.ts +2 -0
@@ -1,5 +1,38 @@
1
1
  <template>
2
2
  <div class="base-table">
3
+ <svg class="base-table__svg-sprite" aria-hidden="true" focusable="false">
4
+ <symbol :id="`${baseTableIconPrefix}-search`" viewBox="0 0 17.814 14.035">
5
+ <path fill="currentColor" d="M172.218,201.2a5.254,5.254,0,1,0-1.057.786l2.85,3.721a.659.659,0,0,0,1.047-.8l-2.841-3.709Zm-7.7-3.8a4.056,4.056,0,1,1,4.055,4.044,4.054,4.054,0,0,1-4.055-4.044Zm-2.554-3.968h-3.954a.657.657,0,1,1,0-1.313h3.952a.657.657,0,1,1,0,1.313Zm-.159,8.751h-3.8a.656.656,0,1,1,0-1.312h3.793a.656.656,0,1,1,0,1.312Zm-.78-3.958h-3.016a.659.659,0,1,1,0-1.319h3.015a.659.659,0,1,1,0,1.319Zm4.246,7.929h-7.261a.659.659,0,0,1,0-1.319h7.261a.659.659,0,1,1,0,1.319Z" transform="translate(-157.354 -192.113)" />
6
+ </symbol>
7
+ <symbol :id="`${baseTableIconPrefix}-search-y`" viewBox="0 0 17.814 14.035">
8
+ <path fill="#0938f7" d="M172.218,201.2a5.254,5.254,0,1,0-1.057.786l2.85,3.721a.659.659,0,0,0,1.047-.8l-2.841-3.709Zm-7.7-3.8a4.056,4.056,0,1,1,4.055,4.044,4.054,4.054,0,0,1-4.055-4.044Zm-2.554-3.968h-3.954a.657.657,0,1,1,0-1.313h3.952a.657.657,0,1,1,0,1.313Zm-.159,8.751h-3.8a.656.656,0,1,1,0-1.312h3.793a.656.656,0,1,1,0,1.312Zm-.78-3.958h-3.016a.659.659,0,1,1,0-1.319h3.015a.659.659,0,1,1,0,1.319Zm4.246,7.929h-7.261a.659.659,0,0,1,0-1.319h7.261a.659.659,0,1,1,0,1.319Z" transform="translate(-157.354 -192.113)" />
9
+ </symbol>
10
+ <symbol :id="`${baseTableIconPrefix}-select`" viewBox="0 0 16.04 12.653">
11
+ <path fill="currentColor" d="M0,12.653V11.248H11.792v1.406ZM9.328,4.429H16.04l-3.356,3.8ZM0,7.029V5.624H7.823V7.029ZM0,1.406V0H14.5V1.406Z" />
12
+ </symbol>
13
+ <symbol :id="`${baseTableIconPrefix}-select-y`" viewBox="0 0 16.04 12.653">
14
+ <path fill="#0938f7" d="M0,12.653V11.248H11.792v1.406ZM9.328,4.429H16.04l-3.356,3.8ZM0,7.029V5.624H7.823V7.029ZM0,1.406V0H14.5V1.406Z" />
15
+ </symbol>
16
+ <symbol :id="`${baseTableIconPrefix}-sort-none`" viewBox="0 0 15.351 16.031">
17
+ <g transform="translate(-1299.19 -185.182)">
18
+ <path fill="currentColor" d="M100.721,140.352l-3.679,3.679-3.679-3.679h2.944v-8.831h1.472v8.831h2.944Z" transform="translate(1213.82 57.182)" />
19
+ <path fill="currentColor" d="M92.692,131.679H89.749v8.831H88.277v-8.831H85.333L89.013,128l3.679,3.679Z" transform="translate(1213.856 57.182)" />
20
+ </g>
21
+ </symbol>
22
+ <symbol :id="`${baseTableIconPrefix}-sort-up`" viewBox="0 0 15.351 16.031">
23
+ <g transform="translate(-1299.19 -185.182)">
24
+ <path fill="#8c8c8c" d="M100.721,140.352l-3.679,3.679-3.679-3.679h2.944v-8.831h1.472v8.831h2.944Z" transform="translate(1213.82 57.182)" />
25
+ <path fill="#0938f7" d="M92.692,131.679H89.749v8.831H88.277v-8.831H85.333L89.013,128l3.679,3.679Z" transform="translate(1213.856 57.182)" />
26
+ </g>
27
+ </symbol>
28
+ <symbol :id="`${baseTableIconPrefix}-sort-down`" viewBox="0 0 15.351 16.031">
29
+ <g transform="translate(-1299.19 -185.182)">
30
+ <path fill="#0938f7" d="M100.721,140.352l-3.679,3.679-3.679-3.679h2.944v-8.831h1.472v8.831h2.944Z" transform="translate(1213.82 57.182)" />
31
+ <path fill="#8c8c8c" d="M92.692,131.679H89.749v8.831H88.277v-8.831H85.333L89.013,128l3.679,3.679Z" transform="translate(1213.856 57.182)" />
32
+ </g>
33
+ </symbol>
34
+ </svg>
35
+
3
36
  <div v-if="shouldRenderToolbar" class="base-table__toolbar">
4
37
  <div class="base-table__toolbar-left">
5
38
  <slot name="toolbar"></slot>
@@ -76,53 +109,202 @@
76
109
  </template>
77
110
 
78
111
  <template v-else-if="shouldRenderBuiltInHeader(column)" #header>
79
- <div class="base-table__header">
80
- <span class="base-table__header-label">{{ column.label }}</span>
81
-
82
- <div class="base-table__header-actions">
83
- <el-dropdown
84
- :ref="bindHeaderSearchDropdown(column)"
85
- class="base-table__header-dropdown"
86
- trigger="click"
87
- :hide-on-click="false"
88
- placement="bottom"
89
- popper-class="base-table__header-search-dropdown"
112
+ <div
113
+ class="base-table__header"
114
+ :class="[
115
+ `base-table__header--${resolveHeaderContentAlign(column)}`,
116
+ {
117
+ 'base-table__header--has-actions': hasHeaderSearch(column),
118
+ 'base-table__header--has-sort': isHeaderSortable(column)
119
+ }
120
+ ]"
121
+ >
122
+ <span class="base-table__header-main">
123
+ <span class="base-table__header-label">{{ column.label }}</span>
124
+ <span
125
+ v-if="isHeaderSortable(column)"
126
+ class="base-table__header-sort"
127
+ :class="{ 'is-active': isHeaderSortActive(column) }"
128
+ aria-hidden="true"
129
+ >
130
+ <SvgIcon
131
+ :prefix="baseTableIconPrefix"
132
+ :icon-class="getHeaderSortIconName(column)"
133
+ class-name="base-table__header-sort-icon"
134
+ />
135
+ </span>
136
+ </span>
137
+
138
+ <div v-if="hasHeaderSearch(column)" class="base-table__header-actions">
139
+ <button
140
+ :ref="bindHeaderSearchTrigger(column)"
141
+ class="base-table__header-icon"
142
+ :class="{ 'is-active': hasHeaderSearchValue(column) }"
143
+ type="button"
144
+ @click.stop="toggleHeaderSearch(column)"
90
145
  >
91
- <button
92
- class="base-table__header-icon"
93
- :class="{ 'is-active': hasHeaderSearchValue(column) }"
94
- type="button"
146
+ <SvgIcon
147
+ :prefix="baseTableIconPrefix"
148
+ :icon-class="getHeaderSearchIconName(column)"
149
+ class-name="base-table__header-search-icon"
150
+ />
151
+ </button>
152
+
153
+ <teleport to="body">
154
+ <div
155
+ v-if="isHeaderSearchVisible(column)"
156
+ :key="getHeaderSearchStateKey(column)"
157
+ class="base-table__header-search-panel"
158
+ :style="getHeaderSearchPanelStyle(column)"
159
+ @click.stop
160
+ @mousedown.stop
95
161
  >
96
- <span class="base-table__header-icon-mark" aria-hidden="true"></span>
97
- </button>
162
+ <div
163
+ v-if="isHeaderSearchSelect(column)"
164
+ class="base-table__header-select"
165
+ >
166
+ <button
167
+ class="base-table__header-select-trigger"
168
+ type="button"
169
+ @click="toggleHeaderSearchSelect(column)"
170
+ >
171
+ <span
172
+ v-if="isHeaderSearchMultiple(column) && getHeaderSearchSelectedLabels(column).length"
173
+ class="base-table__header-select-tags"
174
+ >
175
+ <span
176
+ v-for="label in getHeaderSearchSelectedLabels(column).slice(0, 2)"
177
+ :key="label"
178
+ class="base-table__header-select-tag"
179
+ >
180
+ {{ label }}
181
+ </span>
182
+ <span
183
+ v-if="getHeaderSearchSelectedLabels(column).length > 2"
184
+ class="base-table__header-select-tag base-table__header-select-tag--count"
185
+ >
186
+ +{{ getHeaderSearchSelectedLabels(column).length - 2 }}
187
+ </span>
188
+ </span>
189
+ <span
190
+ v-else
191
+ class="base-table__header-select-text"
192
+ :class="{ 'is-placeholder': !hasHeaderSearchValue(column) }"
193
+ >
194
+ {{ getHeaderSearchSelectLabel(column) }}
195
+ </span>
196
+ <span
197
+ class="base-table__header-select-arrow"
198
+ :class="{ 'is-open': isHeaderSearchSelectOpen(column) }"
199
+ aria-hidden="true"
200
+ ></span>
201
+ </button>
202
+
203
+ <div
204
+ v-if="isHeaderSearchSelectOpen(column)"
205
+ class="base-table__header-select-options"
206
+ >
207
+ <button
208
+ v-if="!isHeaderSearchMultiple(column)"
209
+ class="base-table__header-select-option"
210
+ :class="{ 'is-selected': !hasHeaderSearchValue(column) }"
211
+ type="button"
212
+ @click="handleHeaderSearchSelectEmpty(column)"
213
+ >
214
+ {{ getHeaderSearchPlaceholder(column) }}
215
+ </button>
216
+ <button
217
+ v-for="option in getHeaderSearchOptions(column)"
218
+ :key="String(getHeaderSearchOptionValue(option, column))"
219
+ class="base-table__header-select-option"
220
+ :class="{ 'is-selected': isHeaderSearchOptionSelected(option, column) }"
221
+ type="button"
222
+ :disabled="isHeaderSearchOptionDisabled(option, column)"
223
+ @click="handleHeaderSearchSelectValue(column, getHeaderSearchOptionValue(option, column))"
224
+ >
225
+ <span class="base-table__header-select-option-label">
226
+ {{ getHeaderSearchOptionLabel(option, column) }}
227
+ </span>
228
+ </button>
229
+ </div>
230
+ </div>
231
+
232
+ <div
233
+ v-else-if="isHeaderSearchCascader(column)"
234
+ class="base-table__header-cascader"
235
+ >
236
+ <button
237
+ class="base-table__header-select-trigger"
238
+ type="button"
239
+ @click="toggleHeaderSearchSelect(column)"
240
+ >
241
+ <span
242
+ class="base-table__header-select-text"
243
+ :class="{ 'is-placeholder': !hasHeaderSearchValue(column) }"
244
+ >
245
+ {{ getHeaderSearchCascaderLabel(column) }}
246
+ </span>
247
+ <span
248
+ class="base-table__header-select-arrow"
249
+ :class="{ 'is-open': isHeaderSearchSelectOpen(column) }"
250
+ aria-hidden="true"
251
+ ></span>
252
+ </button>
98
253
 
99
- <template #dropdown>
100
- <el-dropdown-menu class="base-table__header-search-menu">
101
254
  <div
102
- class="base-table__header-search-panel"
103
- :style="{ width: toCssValue(getHeaderSearchWidth(column)) || '280px' }"
104
- @click.stop
255
+ v-if="isHeaderSearchSelectOpen(column)"
256
+ class="base-table__header-cascader-panel"
105
257
  >
106
- <el-input
107
- :model-value="getHeaderSearchValue(column)"
108
- :placeholder="getHeaderSearchPlaceholder(column)"
109
- clearable
110
- @update:model-value="updateHeaderSearchValue(column, $event)"
111
- @keyup.enter="handleHeaderSearch(column)"
112
- />
113
-
114
- <div class="base-table__header-search-footer">
115
- <el-button link type="primary" @click="handleHeaderSearch(column)">
116
- {{ getHeaderSearchSearchText(column) }}
117
- </el-button>
118
- <el-button link @click="handleHeaderSearchReset(column)">
119
- {{ getHeaderSearchResetText(column) }}
120
- </el-button>
258
+ <div
259
+ v-for="(levelOptions, levelIndex) in getHeaderSearchCascaderColumns(column)"
260
+ :key="levelIndex"
261
+ class="base-table__header-cascader-menu"
262
+ >
263
+ <button
264
+ v-for="option in levelOptions"
265
+ :key="`${levelIndex}-${String(getHeaderSearchOptionValue(option, column))}`"
266
+ class="base-table__header-cascader-option"
267
+ :class="{
268
+ 'is-active': isHeaderSearchCascaderOptionActive(column, option, levelIndex),
269
+ 'is-selected': isHeaderSearchCascaderOptionSelected(column, option, levelIndex)
270
+ }"
271
+ type="button"
272
+ :disabled="isHeaderSearchOptionDisabled(option, column)"
273
+ @click="handleHeaderSearchCascaderOption(column, option, levelIndex)"
274
+ >
275
+ <span class="base-table__header-cascader-label">
276
+ {{ getHeaderSearchOptionLabel(option, column) }}
277
+ </span>
278
+ <span
279
+ v-if="hasHeaderSearchOptionChildren(option, column)"
280
+ class="base-table__header-cascader-arrow"
281
+ aria-hidden="true"
282
+ ></span>
283
+ </button>
121
284
  </div>
122
285
  </div>
123
- </el-dropdown-menu>
124
- </template>
125
- </el-dropdown>
286
+ </div>
287
+
288
+ <input
289
+ v-else
290
+ class="base-table__header-search-input"
291
+ type="text"
292
+ :value="getHeaderSearchInputValue(column)"
293
+ :placeholder="getHeaderSearchPlaceholder(column)"
294
+ @input="handleHeaderSearchInput(column, $event)"
295
+ @keyup.enter="handleHeaderSearch(column)"
296
+ />
297
+
298
+ <div class="base-table__header-search-footer">
299
+ <el-button link type="primary" @click="handleHeaderSearch(column)">
300
+ {{ getHeaderSearchSearchText(column) }}
301
+ </el-button>
302
+ <el-button link @click="handleHeaderSearchReset(column)">
303
+ {{ getHeaderSearchResetText(column) }}
304
+ </el-button>
305
+ </div>
306
+ </div>
307
+ </teleport>
126
308
  </div>
127
309
  </div>
128
310
  </template>
@@ -207,15 +389,20 @@
207
389
  </template>
208
390
 
209
391
  <script setup lang="ts">
210
- import { computed, onMounted, reactive, ref, useSlots, watch } from 'vue'
392
+ import { computed, onBeforeUnmount, onMounted, reactive, ref, useSlots, watch } from 'vue'
393
+ import type { ComponentPublicInstance } from 'vue'
211
394
  import type { TableInstance } from 'element-plus'
395
+ import SvgIcon from '../SvgIcon'
212
396
  import type {
213
397
  BaseTableAction,
398
+ BaseTableAlign,
214
399
  BaseTableCellPayload,
215
400
  BaseTableColumn,
216
401
  BaseTableCssValue,
217
402
  BaseTableExpose,
218
403
  BaseTableHeaderSearchConfig,
404
+ BaseTableHeaderSearchPrimitiveValue,
405
+ BaseTableHeaderSearchValue,
219
406
  BaseTableLoadedPayload,
220
407
  BaseTableOption,
221
408
  BaseTablePagination,
@@ -227,6 +414,8 @@ import type {
227
414
  BaseTableSortPayload
228
415
  } from './BaseTable.types'
229
416
 
417
+ let baseTableIconSeed = 0
418
+
230
419
  defineOptions({
231
420
  name: 'BaseTable'
232
421
  })
@@ -286,8 +475,14 @@ const loading = ref(false)
286
475
  const sourceRows = ref<BaseTableRow[]>(normalizeRows(props.data))
287
476
  const rows = ref<BaseTableRow[]>(normalizeRows(props.data))
288
477
  const selectionRows = ref<BaseTableRow[]>([])
289
- const headerSearchValues = reactive<Record<string, string>>({})
290
- const headerSearchDropdownRefs = ref<Record<string, { handleClose?: () => void }>>({})
478
+ const headerSearchValues = reactive<Record<string, BaseTableHeaderSearchValue>>({})
479
+ const headerSearchVisible = reactive<Record<string, boolean>>({})
480
+ const headerSearchSelectOpen = reactive<Record<string, boolean>>({})
481
+ const headerSearchCascaderActivePaths = reactive<Record<string, BaseTableHeaderSearchPrimitiveValue[]>>({})
482
+ const headerSearchTriggerRects = reactive<Record<string, DOMRect>>({})
483
+ const headerSearchTriggerRefs = ref<Record<string, HTMLElement>>({})
484
+ const baseTableIconPrefix = `base-table-icon-${++baseTableIconSeed}`
485
+ let headerSearchPositionFrame = 0
291
486
  const sortState = reactive({
292
487
  prop: props.defaultSort?.prop,
293
488
  order: props.defaultSort?.order || null
@@ -353,6 +548,9 @@ watch(
353
548
  )
354
549
 
355
550
  onMounted(() => {
551
+ window.addEventListener('scroll', scheduleHeaderSearchPositionUpdate, true)
552
+ window.addEventListener('resize', scheduleHeaderSearchPositionUpdate)
553
+
356
554
  if (props.request && props.autoLoad) {
357
555
  void load()
358
556
  return
@@ -363,6 +561,16 @@ onMounted(() => {
363
561
  }
364
562
  })
365
563
 
564
+ onBeforeUnmount(() => {
565
+ window.removeEventListener('scroll', scheduleHeaderSearchPositionUpdate, true)
566
+ window.removeEventListener('resize', scheduleHeaderSearchPositionUpdate)
567
+
568
+ if (headerSearchPositionFrame) {
569
+ window.cancelAnimationFrame(headerSearchPositionFrame)
570
+ headerSearchPositionFrame = 0
571
+ }
572
+ })
573
+
366
574
  function syncPagination(value?: Partial<BaseTablePagination>) {
367
575
  const nextPageSizes = value?.pageSizes
368
576
 
@@ -527,8 +735,12 @@ function handleInputChange(value: string, row: BaseTableRow, column: BaseTableCo
527
735
  })
528
736
  }
529
737
 
738
+ function resolveHeaderContentAlign(column: BaseTableColumn<BaseTableRow>): BaseTableAlign {
739
+ return column.headerAlign || column.align || 'left'
740
+ }
741
+
530
742
  function shouldRenderBuiltInHeader(column: BaseTableColumn<BaseTableRow>) {
531
- return hasHeaderSearch(column)
743
+ return hasHeaderSearch(column) || isHeaderSortable(column)
532
744
  }
533
745
 
534
746
  function hasHeaderSearch(column: BaseTableColumn<BaseTableRow>) {
@@ -551,47 +763,199 @@ function getHeaderSearchStateKey(column: BaseTableColumn<BaseTableRow>) {
551
763
  return resolveHeaderSearchParamKey(column) || getColumnProp(column) || getColumnKey(column)
552
764
  }
553
765
 
554
- function setHeaderSearchDropdownRef(
555
- instance: { handleClose?: () => void } | null,
766
+ function resolveHeaderSearchParamKey(column: BaseTableColumn<BaseTableRow>) {
767
+ const config = getHeaderSearchConfig(column)
768
+ const legacyParamKey = (column as Record<string, any>).otherProps
769
+
770
+ return config?.paramKey || legacyParamKey || getColumnProp(column) || ''
771
+ }
772
+
773
+ function setHeaderSearchTriggerRef(
774
+ instance: Element | ComponentPublicInstance | null,
556
775
  column: BaseTableColumn<BaseTableRow>
557
776
  ) {
558
777
  const stateKey = getHeaderSearchStateKey(column)
559
778
 
560
- if (!instance) {
561
- delete headerSearchDropdownRefs.value[stateKey]
779
+ if (!(instance instanceof HTMLElement)) {
780
+ delete headerSearchTriggerRefs.value[stateKey]
562
781
  return
563
782
  }
564
783
 
565
- headerSearchDropdownRefs.value[stateKey] = instance
784
+ headerSearchTriggerRefs.value[stateKey] = instance
566
785
  }
567
786
 
568
- function bindHeaderSearchDropdown(column: BaseTableColumn<BaseTableRow>) {
569
- return (instance: { handleClose?: () => void } | null) => {
570
- setHeaderSearchDropdownRef(instance, column)
787
+ function bindHeaderSearchTrigger(column: BaseTableColumn<BaseTableRow>) {
788
+ return (instance: Element | ComponentPublicInstance | null) => {
789
+ setHeaderSearchTriggerRef(instance, column)
571
790
  }
572
791
  }
573
792
 
574
- function resolveHeaderSearchParamKey(column: BaseTableColumn<BaseTableRow>) {
575
- const config = getHeaderSearchConfig(column)
576
- const legacyParamKey = (column as Record<string, any>).otherProps
793
+ function isHeaderSearchVisible(column: BaseTableColumn<BaseTableRow>) {
794
+ return Boolean(headerSearchVisible[getHeaderSearchStateKey(column)])
795
+ }
577
796
 
578
- return config?.paramKey || legacyParamKey || getColumnProp(column) || ''
797
+ function toggleHeaderSearch(column: BaseTableColumn<BaseTableRow>) {
798
+ const stateKey = getHeaderSearchStateKey(column)
799
+ const nextVisible = !headerSearchVisible[stateKey]
800
+
801
+ if (nextVisible) {
802
+ closeAllHeaderSearch()
803
+ updateHeaderSearchTriggerRect(column)
804
+ syncHeaderSearchCascaderActivePath(column)
805
+ }
806
+
807
+ headerSearchVisible[stateKey] = nextVisible
808
+ headerSearchSelectOpen[stateKey] = false
809
+ }
810
+
811
+ function closeAllHeaderSearch() {
812
+ Object.keys(headerSearchVisible).forEach((key) => {
813
+ headerSearchVisible[key] = false
814
+ headerSearchSelectOpen[key] = false
815
+ })
816
+ }
817
+
818
+ function updateHeaderSearchTriggerRect(column: BaseTableColumn<BaseTableRow>) {
819
+ const stateKey = getHeaderSearchStateKey(column)
820
+ const rect = headerSearchTriggerRefs.value[stateKey]?.getBoundingClientRect()
821
+
822
+ if (rect) {
823
+ headerSearchTriggerRects[stateKey] = rect
824
+ }
579
825
  }
580
826
 
581
- function getHeaderSearchValue(column: BaseTableColumn<BaseTableRow>) {
582
- return headerSearchValues[getHeaderSearchStateKey(column)] || ''
827
+ function updateVisibleHeaderSearchPosition() {
828
+ props.columns.forEach((column) => {
829
+ if (hasHeaderSearch(column) && isHeaderSearchVisible(column)) {
830
+ updateHeaderSearchTriggerRect(column)
831
+ }
832
+ })
833
+ }
834
+
835
+ function scheduleHeaderSearchPositionUpdate() {
836
+ if (headerSearchPositionFrame) {
837
+ return
838
+ }
839
+
840
+ headerSearchPositionFrame = window.requestAnimationFrame(() => {
841
+ headerSearchPositionFrame = 0
842
+ updateVisibleHeaderSearchPosition()
843
+ })
844
+ }
845
+
846
+ function getHeaderSearchPanelStyle(column: BaseTableColumn<BaseTableRow>) {
847
+ const stateKey = getHeaderSearchStateKey(column)
848
+ const triggerRect = headerSearchTriggerRects[stateKey]
849
+ const width = toCssValue(getHeaderSearchWidth(column)) || '280px'
850
+
851
+ if (!triggerRect) {
852
+ return {
853
+ width
854
+ }
855
+ }
856
+
857
+ const panelWidth = Number.parseFloat(String(getHeaderSearchWidth(column))) || 280
858
+ const maxLeft = Math.max(8, window.innerWidth - panelWidth - 8)
859
+ const left = Math.min(maxLeft, Math.max(8, triggerRect.right - panelWidth))
860
+ const top = triggerRect.bottom + 8
861
+
862
+ return {
863
+ width,
864
+ left: `${left}px`,
865
+ top: `${top}px`
866
+ }
867
+ }
868
+
869
+ function getHeaderSearchValue(column: BaseTableColumn<BaseTableRow>): BaseTableHeaderSearchValue {
870
+ const stateKey = getHeaderSearchStateKey(column)
871
+ const value = headerSearchValues[stateKey]
872
+
873
+ if (value !== undefined) {
874
+ return value
875
+ }
876
+
877
+ return getHeaderSearchEmptyValue(column)
878
+ }
879
+
880
+ function getHeaderSearchInputValue(column: BaseTableColumn<BaseTableRow>) {
881
+ const value = getHeaderSearchValue(column)
882
+ return Array.isArray(value) ? value.join(' ') : String(value)
583
883
  }
584
884
 
585
885
  function hasHeaderSearchValue(column: BaseTableColumn<BaseTableRow>) {
586
- return Boolean(getHeaderSearchValue(column).trim())
886
+ return isHeaderSearchValueFilled(getHeaderSearchValue(column))
887
+ }
888
+
889
+ function updateHeaderSearchValue(column: BaseTableColumn<BaseTableRow>, value: unknown) {
890
+ headerSearchValues[getHeaderSearchStateKey(column)] = normalizeHeaderSearchValue(column, value)
891
+ }
892
+
893
+ function handleHeaderSearchInput(column: BaseTableColumn<BaseTableRow>, event: Event) {
894
+ updateHeaderSearchValue(column, (event.target as HTMLInputElement | null)?.value || '')
895
+ }
896
+
897
+ function isHeaderSearchSelect(column: BaseTableColumn<BaseTableRow>) {
898
+ return getHeaderSearchConfig(column)?.type === 'select'
899
+ }
900
+
901
+ function isHeaderSearchCascader(column: BaseTableColumn<BaseTableRow>) {
902
+ return getHeaderSearchConfig(column)?.type === 'cascader'
903
+ }
904
+
905
+ function isHeaderSearchChoice(column: BaseTableColumn<BaseTableRow>) {
906
+ return isHeaderSearchSelect(column) || isHeaderSearchCascader(column)
907
+ }
908
+
909
+ function isHeaderSearchMultiple(column: BaseTableColumn<BaseTableRow>) {
910
+ return isHeaderSearchSelect(column) && getHeaderSearchConfig(column)?.multiple === true
911
+ }
912
+
913
+ function isHeaderSearchClearable(column: BaseTableColumn<BaseTableRow>) {
914
+ return getHeaderSearchConfig(column)?.clearable !== false
915
+ }
916
+
917
+ function isHeaderSearchFilterable(column: BaseTableColumn<BaseTableRow>) {
918
+ return getHeaderSearchConfig(column)?.filterable === true
919
+ }
920
+
921
+ function getHeaderSearchIconName(column: BaseTableColumn<BaseTableRow>) {
922
+ const iconName = isHeaderSearchChoice(column) ? 'select' : 'search'
923
+ return hasHeaderSearchValue(column) ? `${iconName}-y` : iconName
924
+ }
925
+
926
+ function isHeaderSortable(column: BaseTableColumn<BaseTableRow>) {
927
+ return Boolean(resolveColumnSortable(column))
928
+ }
929
+
930
+ function isHeaderSortActive(column: BaseTableColumn<BaseTableRow>) {
931
+ return String(sortState.prop || '') === String(getColumnProp(column) || '') && Boolean(sortState.order)
932
+ }
933
+
934
+ function getHeaderSortIconName(column: BaseTableColumn<BaseTableRow>) {
935
+ if (!isHeaderSortActive(column)) {
936
+ return 'sort-none'
937
+ }
938
+
939
+ return sortState.order === 'ascending' ? 'sort-up' : 'sort-down'
587
940
  }
588
941
 
589
- function updateHeaderSearchValue(column: BaseTableColumn<BaseTableRow>, value: string) {
590
- headerSearchValues[getHeaderSearchStateKey(column)] = normalizeHeaderSearchValue(value)
942
+ function isHeaderSearchSelectOpen(column: BaseTableColumn<BaseTableRow>) {
943
+ return Boolean(headerSearchSelectOpen[getHeaderSearchStateKey(column)])
944
+ }
945
+
946
+ function toggleHeaderSearchSelect(column: BaseTableColumn<BaseTableRow>) {
947
+ const stateKey = getHeaderSearchStateKey(column)
948
+ const nextOpen = !headerSearchSelectOpen[stateKey]
949
+
950
+ if (nextOpen) {
951
+ syncHeaderSearchCascaderActivePath(column)
952
+ }
953
+
954
+ headerSearchSelectOpen[stateKey] = nextOpen
591
955
  }
592
956
 
593
957
  function getHeaderSearchPlaceholder(column: BaseTableColumn<BaseTableRow>) {
594
- return getHeaderSearchConfig(column)?.placeholder || '请输入搜索内容'
958
+ return getHeaderSearchConfig(column)?.placeholder || (isHeaderSearchChoice(column) ? '请选择' : '请输入搜索内容')
595
959
  }
596
960
 
597
961
  function getHeaderSearchWidth(column: BaseTableColumn<BaseTableRow>) {
@@ -606,6 +970,218 @@ function getHeaderSearchResetText(column: BaseTableColumn<BaseTableRow>) {
606
970
  return getHeaderSearchConfig(column)?.resetText || '重置'
607
971
  }
608
972
 
973
+ function getHeaderSearchOptions(column: BaseTableColumn<BaseTableRow>) {
974
+ return getHeaderSearchConfig(column)?.options || column.options || []
975
+ }
976
+
977
+ function getHeaderSearchOptionValue(option: BaseTableOption, column: BaseTableColumn<BaseTableRow>) {
978
+ const valueKey = getHeaderSearchConfig(column)?.optionValueKey || column.optionValueKey || 'value'
979
+ return normalizeHeaderSearchPrimitiveValue((option as Record<string, any>)[valueKey])
980
+ }
981
+
982
+ function getHeaderSearchOptionLabel(option: BaseTableOption, column: BaseTableColumn<BaseTableRow>) {
983
+ const labelKey = getHeaderSearchConfig(column)?.optionLabelKey || column.optionLabelKey || 'label'
984
+ const label = (option as Record<string, any>)[labelKey]
985
+ return label === undefined || label === null ? '' : String(label)
986
+ }
987
+
988
+ function isHeaderSearchOptionDisabled(option: BaseTableOption, column: BaseTableColumn<BaseTableRow>) {
989
+ const disabledKey = getHeaderSearchConfig(column)?.optionDisabledKey || 'disabled'
990
+ return Boolean((option as Record<string, any>)[disabledKey])
991
+ }
992
+
993
+ function getHeaderSearchOptionChildren(option: BaseTableOption, column: BaseTableColumn<BaseTableRow>) {
994
+ const childrenKey = getHeaderSearchConfig(column)?.optionChildrenKey || 'children'
995
+ const children = (option as Record<string, any>)[childrenKey]
996
+ return Array.isArray(children) ? children as BaseTableOption[] : []
997
+ }
998
+
999
+ function hasHeaderSearchOptionChildren(option: BaseTableOption, column: BaseTableColumn<BaseTableRow>) {
1000
+ return getHeaderSearchOptionChildren(option, column).length > 0
1001
+ }
1002
+
1003
+ function getHeaderSearchSelectLabel(column: BaseTableColumn<BaseTableRow>) {
1004
+ const selectedLabels = getHeaderSearchSelectedLabels(column)
1005
+
1006
+ if (!selectedLabels.length) {
1007
+ return getHeaderSearchPlaceholder(column)
1008
+ }
1009
+
1010
+ return selectedLabels.join('、')
1011
+ }
1012
+
1013
+ function getHeaderSearchSelectedLabels(column: BaseTableColumn<BaseTableRow>) {
1014
+ const selectedValues = toComparableValues(getHeaderSearchValue(column))
1015
+
1016
+ return selectedValues.map((selectedValue) => {
1017
+ const matchedOption = getHeaderSearchOptions(column).find((option) => {
1018
+ return String(getHeaderSearchOptionValue(option, column)) === selectedValue
1019
+ })
1020
+
1021
+ return matchedOption ? getHeaderSearchOptionLabel(matchedOption, column) : selectedValue
1022
+ })
1023
+ }
1024
+
1025
+ function isHeaderSearchOptionSelected(option: BaseTableOption, column: BaseTableColumn<BaseTableRow>) {
1026
+ const optionValue = String(getHeaderSearchOptionValue(option, column))
1027
+ return toComparableValues(getHeaderSearchValue(column)).includes(optionValue)
1028
+ }
1029
+
1030
+ function handleHeaderSearchSelectEmpty(column: BaseTableColumn<BaseTableRow>) {
1031
+ updateHeaderSearchValue(column, getHeaderSearchEmptyValue(column))
1032
+ headerSearchSelectOpen[getHeaderSearchStateKey(column)] = false
1033
+ }
1034
+
1035
+ function handleHeaderSearchSelectValue(
1036
+ column: BaseTableColumn<BaseTableRow>,
1037
+ optionValue: BaseTableHeaderSearchPrimitiveValue
1038
+ ) {
1039
+ if (!isHeaderSearchMultiple(column)) {
1040
+ updateHeaderSearchValue(column, optionValue)
1041
+ headerSearchSelectOpen[getHeaderSearchStateKey(column)] = false
1042
+ return
1043
+ }
1044
+
1045
+ const currentValues = Array.isArray(getHeaderSearchValue(column))
1046
+ ? [...getHeaderSearchValue(column) as BaseTableHeaderSearchPrimitiveValue[]]
1047
+ : []
1048
+ const comparableValue = String(optionValue)
1049
+ const matchedIndex = currentValues.findIndex((item) => String(item) === comparableValue)
1050
+
1051
+ if (matchedIndex >= 0) {
1052
+ currentValues.splice(matchedIndex, 1)
1053
+ } else {
1054
+ currentValues.push(optionValue)
1055
+ }
1056
+
1057
+ updateHeaderSearchValue(column, currentValues)
1058
+ }
1059
+
1060
+ function handleHeaderSearchSelectOption(column: BaseTableColumn<BaseTableRow>, option: BaseTableOption) {
1061
+ handleHeaderSearchSelectValue(column, getHeaderSearchOptionValue(option, column))
1062
+ }
1063
+
1064
+ function getHeaderSearchCascaderValue(column: BaseTableColumn<BaseTableRow>) {
1065
+ return normalizeHeaderSearchPathValue(getHeaderSearchValue(column))
1066
+ }
1067
+
1068
+ function syncHeaderSearchCascaderActivePath(column: BaseTableColumn<BaseTableRow>) {
1069
+ if (!isHeaderSearchCascader(column)) {
1070
+ return
1071
+ }
1072
+
1073
+ const stateKey = getHeaderSearchStateKey(column)
1074
+ headerSearchCascaderActivePaths[stateKey] = getHeaderSearchCascaderValue(column)
1075
+ }
1076
+
1077
+ function getHeaderSearchCascaderActivePath(column: BaseTableColumn<BaseTableRow>) {
1078
+ const stateKey = getHeaderSearchStateKey(column)
1079
+ const activePath = headerSearchCascaderActivePaths[stateKey]
1080
+
1081
+ if (activePath) {
1082
+ return activePath
1083
+ }
1084
+
1085
+ return getHeaderSearchCascaderValue(column)
1086
+ }
1087
+
1088
+ function getHeaderSearchCascaderColumns(column: BaseTableColumn<BaseTableRow>) {
1089
+ const columns: BaseTableOption[][] = []
1090
+ let levelOptions = getHeaderSearchOptions(column)
1091
+ const activePath = getHeaderSearchCascaderActivePath(column)
1092
+ let levelIndex = 0
1093
+
1094
+ while (levelOptions.length) {
1095
+ columns.push(levelOptions)
1096
+
1097
+ const activeValue = activePath[levelIndex]
1098
+ const activeOption = levelOptions.find((option) => {
1099
+ return String(getHeaderSearchOptionValue(option, column)) === String(activeValue)
1100
+ })
1101
+
1102
+ if (!activeOption) {
1103
+ break
1104
+ }
1105
+
1106
+ levelOptions = getHeaderSearchOptionChildren(activeOption, column)
1107
+ levelIndex += 1
1108
+ }
1109
+
1110
+ return columns
1111
+ }
1112
+
1113
+ function getHeaderSearchCascaderLabel(column: BaseTableColumn<BaseTableRow>) {
1114
+ const path = getHeaderSearchCascaderValue(column)
1115
+
1116
+ if (!path.length) {
1117
+ return getHeaderSearchPlaceholder(column)
1118
+ }
1119
+
1120
+ return getHeaderSearchCascaderPathLabels(column, path).join(' / ')
1121
+ }
1122
+
1123
+ function getHeaderSearchCascaderPathLabels(
1124
+ column: BaseTableColumn<BaseTableRow>,
1125
+ path = getHeaderSearchCascaderValue(column)
1126
+ ) {
1127
+ const labels: string[] = []
1128
+ let levelOptions = getHeaderSearchOptions(column)
1129
+
1130
+ path.forEach((value) => {
1131
+ const matchedOption = levelOptions.find((option) => {
1132
+ return String(getHeaderSearchOptionValue(option, column)) === String(value)
1133
+ })
1134
+
1135
+ if (!matchedOption) {
1136
+ labels.push(String(value))
1137
+ levelOptions = []
1138
+ return
1139
+ }
1140
+
1141
+ labels.push(getHeaderSearchOptionLabel(matchedOption, column))
1142
+ levelOptions = getHeaderSearchOptionChildren(matchedOption, column)
1143
+ })
1144
+
1145
+ return labels
1146
+ }
1147
+
1148
+ function isHeaderSearchCascaderOptionActive(
1149
+ column: BaseTableColumn<BaseTableRow>,
1150
+ option: BaseTableOption,
1151
+ levelIndex: number
1152
+ ) {
1153
+ const activePath = getHeaderSearchCascaderActivePath(column)
1154
+ return String(activePath[levelIndex]) === String(getHeaderSearchOptionValue(option, column))
1155
+ }
1156
+
1157
+ function isHeaderSearchCascaderOptionSelected(
1158
+ column: BaseTableColumn<BaseTableRow>,
1159
+ option: BaseTableOption,
1160
+ levelIndex: number
1161
+ ) {
1162
+ const selectedPath = getHeaderSearchCascaderValue(column)
1163
+ return String(selectedPath[levelIndex]) === String(getHeaderSearchOptionValue(option, column))
1164
+ }
1165
+
1166
+ function handleHeaderSearchCascaderOption(
1167
+ column: BaseTableColumn<BaseTableRow>,
1168
+ option: BaseTableOption,
1169
+ levelIndex: number
1170
+ ) {
1171
+ const stateKey = getHeaderSearchStateKey(column)
1172
+ const optionValue = getHeaderSearchOptionValue(option, column)
1173
+ const nextPath = [
1174
+ ...getHeaderSearchCascaderActivePath(column).slice(0, levelIndex),
1175
+ optionValue
1176
+ ]
1177
+
1178
+ headerSearchCascaderActivePaths[stateKey] = nextPath
1179
+
1180
+ if (!hasHeaderSearchOptionChildren(option, column)) {
1181
+ updateHeaderSearchValue(column, nextPath)
1182
+ }
1183
+ }
1184
+
609
1185
  async function handleHeaderSearch(column: BaseTableColumn<BaseTableRow>) {
610
1186
  closeHeaderSearch(column)
611
1187
  paginationState.currentPage = 1
@@ -620,7 +1196,7 @@ async function handleHeaderSearch(column: BaseTableColumn<BaseTableRow>) {
620
1196
  }
621
1197
 
622
1198
  async function handleHeaderSearchReset(column: BaseTableColumn<BaseTableRow>) {
623
- updateHeaderSearchValue(column, '')
1199
+ updateHeaderSearchValue(column, getHeaderSearchEmptyValue(column))
624
1200
  closeHeaderSearch(column)
625
1201
  paginationState.currentPage = 1
626
1202
  emitPagination()
@@ -634,7 +1210,9 @@ async function handleHeaderSearchReset(column: BaseTableColumn<BaseTableRow>) {
634
1210
  }
635
1211
 
636
1212
  function closeHeaderSearch(column: BaseTableColumn<BaseTableRow>) {
637
- headerSearchDropdownRefs.value[getHeaderSearchStateKey(column)]?.handleClose?.()
1213
+ const stateKey = getHeaderSearchStateKey(column)
1214
+ headerSearchVisible[stateKey] = false
1215
+ headerSearchSelectOpen[stateKey] = false
638
1216
  }
639
1217
 
640
1218
  function buildHeaderSearchParams() {
@@ -644,13 +1222,13 @@ function buildHeaderSearchParams() {
644
1222
  }
645
1223
 
646
1224
  const paramKey = resolveHeaderSearchParamKey(column)
647
- const value = getHeaderSearchValue(column).trim()
1225
+ const value = getHeaderSearchValue(column)
648
1226
 
649
- if (!paramKey || !value) {
1227
+ if (!paramKey || !isHeaderSearchValueFilled(value)) {
650
1228
  return result
651
1229
  }
652
1230
 
653
- result[paramKey] = value
1231
+ result[paramKey] = normalizeHeaderSearchRequestValue(column, value)
654
1232
  return result
655
1233
  }, {})
656
1234
  }
@@ -667,7 +1245,7 @@ function filterLocalRows(data: BaseTableRow[]) {
667
1245
  .filter((column) => hasHeaderSearch(column) && hasHeaderSearchValue(column))
668
1246
  .map((column) => ({
669
1247
  column,
670
- keyword: getHeaderSearchValue(column).trim().toLowerCase()
1248
+ value: getHeaderSearchValue(column)
671
1249
  }))
672
1250
 
673
1251
  if (!activeColumns.length) {
@@ -675,24 +1253,144 @@ function filterLocalRows(data: BaseTableRow[]) {
675
1253
  }
676
1254
 
677
1255
  return normalizeRows(data).filter((row) => {
678
- return activeColumns.every(({ column, keyword }) => {
1256
+ return activeColumns.every(({ column, value: searchValue }) => {
679
1257
  const columnProp = getColumnProp(column)
680
1258
  if (!columnProp) {
681
1259
  return true
682
1260
  }
683
1261
 
684
1262
  const value = getCellValue(row, column)
685
- return toSearchableText(value).includes(keyword)
1263
+ if (isHeaderSearchSelect(column)) {
1264
+ return matchesHeaderSearchSelectValue(value, searchValue)
1265
+ }
1266
+
1267
+ if (isHeaderSearchCascader(column)) {
1268
+ return matchesHeaderSearchCascaderValue(value, searchValue)
1269
+ }
1270
+
1271
+ return toSearchableText(value).includes(toHeaderSearchKeyword(searchValue))
686
1272
  })
687
1273
  })
688
1274
  }
689
1275
 
690
- function normalizeHeaderSearchValue(value: unknown) {
1276
+ function getHeaderSearchEmptyValue(column: BaseTableColumn<BaseTableRow>): BaseTableHeaderSearchValue {
1277
+ return isHeaderSearchMultiple(column) || isHeaderSearchCascader(column) ? [] : ''
1278
+ }
1279
+
1280
+ function normalizeHeaderSearchValue(
1281
+ column: BaseTableColumn<BaseTableRow>,
1282
+ value: unknown
1283
+ ): BaseTableHeaderSearchValue {
1284
+ if (isHeaderSearchCascader(column)) {
1285
+ return normalizeHeaderSearchPathValue(value)
1286
+ }
1287
+
1288
+ if (!isHeaderSearchSelect(column)) {
1289
+ if (value === undefined || value === null) {
1290
+ return ''
1291
+ }
1292
+
1293
+ return String(value)
1294
+ }
1295
+
1296
+ if (isHeaderSearchMultiple(column)) {
1297
+ const values = Array.isArray(value) ? value : [value]
1298
+ return values
1299
+ .filter((item) => isHeaderSearchValueFilled(item))
1300
+ .map((item) => normalizeHeaderSearchPrimitiveValue(item))
1301
+ }
1302
+
1303
+ if (Array.isArray(value)) {
1304
+ const firstFilledValue = value.find((item) => isHeaderSearchValueFilled(item))
1305
+ return firstFilledValue === undefined ? '' : normalizeHeaderSearchPrimitiveValue(firstFilledValue)
1306
+ }
1307
+
691
1308
  if (value === undefined || value === null) {
692
1309
  return ''
693
1310
  }
694
1311
 
695
- return String(value)
1312
+ return normalizeHeaderSearchPrimitiveValue(value)
1313
+ }
1314
+
1315
+ function normalizeHeaderSearchPrimitiveValue(value: unknown): BaseTableHeaderSearchPrimitiveValue {
1316
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
1317
+ return value
1318
+ }
1319
+
1320
+ return String(value ?? '')
1321
+ }
1322
+
1323
+ function normalizeHeaderSearchPathValue(value: unknown): BaseTableHeaderSearchPrimitiveValue[] {
1324
+ const values = Array.isArray(value) ? value : [value]
1325
+
1326
+ return values
1327
+ .filter((item) => !Array.isArray(item) && isHeaderSearchValueFilled(item))
1328
+ .map((item) => normalizeHeaderSearchPrimitiveValue(item))
1329
+ }
1330
+
1331
+ function normalizeHeaderSearchRequestValue(
1332
+ column: BaseTableColumn<BaseTableRow>,
1333
+ value: BaseTableHeaderSearchValue
1334
+ ) {
1335
+ if (Array.isArray(value)) {
1336
+ return [...value]
1337
+ }
1338
+
1339
+ if (isHeaderSearchSelect(column)) {
1340
+ return value
1341
+ }
1342
+
1343
+ return String(value).trim()
1344
+ }
1345
+
1346
+ function isHeaderSearchValueFilled(value: unknown): boolean {
1347
+ if (Array.isArray(value)) {
1348
+ return value.some((item) => isHeaderSearchValueFilled(item))
1349
+ }
1350
+
1351
+ return value !== undefined && value !== null && String(value).trim() !== ''
1352
+ }
1353
+
1354
+ function toHeaderSearchKeyword(value: BaseTableHeaderSearchValue) {
1355
+ if (Array.isArray(value)) {
1356
+ return value.map((item) => String(item).trim()).join(' ').toLowerCase()
1357
+ }
1358
+
1359
+ return String(value).trim().toLowerCase()
1360
+ }
1361
+
1362
+ function matchesHeaderSearchSelectValue(rowValue: unknown, searchValue: BaseTableHeaderSearchValue) {
1363
+ const selectedValues = toComparableValues(searchValue)
1364
+
1365
+ if (!selectedValues.length) {
1366
+ return true
1367
+ }
1368
+
1369
+ const rowValues = toComparableValues(rowValue)
1370
+ return rowValues.some((item) => selectedValues.includes(item))
1371
+ }
1372
+
1373
+ function matchesHeaderSearchCascaderValue(rowValue: unknown, searchValue: BaseTableHeaderSearchValue) {
1374
+ const selectedPath = normalizeHeaderSearchPathValue(searchValue)
1375
+
1376
+ if (!selectedPath.length) {
1377
+ return true
1378
+ }
1379
+
1380
+ const rowPath = normalizeHeaderSearchPathValue(rowValue)
1381
+
1382
+ if (!rowPath.length) {
1383
+ return false
1384
+ }
1385
+
1386
+ return selectedPath.every((item, index) => String(rowPath[index]) === String(item))
1387
+ }
1388
+
1389
+ function toComparableValues(value: unknown) {
1390
+ const values = Array.isArray(value) ? value : [value]
1391
+ return values
1392
+ .filter((item) => isHeaderSearchValueFilled(item))
1393
+ .map((item) => String(item))
696
1394
  }
697
1395
 
698
1396
  function toSearchableText(value: unknown): string {
@@ -979,11 +1677,13 @@ function findHeaderSearchColumn(key: string) {
979
1677
  })
980
1678
  }
981
1679
 
982
- async function setHeaderSearchValue(key: string, value: string, shouldReload = true) {
1680
+ async function setHeaderSearchValue(key: string, value: BaseTableHeaderSearchValue, shouldReload = true) {
983
1681
  const matchedColumn = findHeaderSearchColumn(key)
984
1682
  const stateKey = matchedColumn ? getHeaderSearchStateKey(matchedColumn) : key
985
1683
 
986
- headerSearchValues[stateKey] = normalizeHeaderSearchValue(value)
1684
+ headerSearchValues[stateKey] = matchedColumn
1685
+ ? normalizeHeaderSearchValue(matchedColumn, value)
1686
+ : value
987
1687
 
988
1688
  if (!shouldReload) {
989
1689
  return
@@ -1017,8 +1717,11 @@ async function resetHeaderSearch(key?: string) {
1017
1717
  Object.keys(headerSearchValues).forEach((stateKey) => {
1018
1718
  delete headerSearchValues[stateKey]
1019
1719
  })
1020
- Object.keys(headerSearchDropdownRefs.value).forEach((stateKey) => {
1021
- headerSearchDropdownRefs.value[stateKey]?.handleClose?.()
1720
+ Object.keys(headerSearchVisible).forEach((stateKey) => {
1721
+ headerSearchVisible[stateKey] = false
1722
+ })
1723
+ Object.keys(headerSearchSelectOpen).forEach((stateKey) => {
1724
+ headerSearchSelectOpen[stateKey] = false
1022
1725
  })
1023
1726
  }
1024
1727
 
@@ -1060,6 +1763,13 @@ defineExpose<BaseTableExpose>({
1060
1763
  min-height: 0;
1061
1764
  }
1062
1765
 
1766
+ .base-table__svg-sprite {
1767
+ position: absolute;
1768
+ width: 0;
1769
+ height: 0;
1770
+ overflow: hidden;
1771
+ }
1772
+
1063
1773
  .base-table__toolbar {
1064
1774
  display: flex;
1065
1775
  align-items: center;
@@ -1094,21 +1804,75 @@ defineExpose<BaseTableExpose>({
1094
1804
  min-width: 0;
1095
1805
  }
1096
1806
 
1807
+ .base-table__header--center.base-table__header--has-actions::before {
1808
+ content: '';
1809
+ display: block;
1810
+ width: 24px;
1811
+ flex: 0 0 24px;
1812
+ }
1813
+
1814
+ .base-table__header--center {
1815
+ justify-content: center;
1816
+ }
1817
+
1818
+ .base-table__header--right {
1819
+ justify-content: flex-end;
1820
+ }
1821
+
1822
+ .base-table__header-main {
1823
+ display: inline-flex;
1824
+ align-items: center;
1825
+ gap: 4px;
1826
+ min-width: 0;
1827
+ }
1828
+
1097
1829
  .base-table__header-label {
1098
- flex: 1 1 auto;
1099
1830
  min-width: 0;
1100
1831
  overflow: hidden;
1101
1832
  text-overflow: ellipsis;
1102
1833
  white-space: nowrap;
1103
1834
  }
1104
1835
 
1836
+ .base-table__header--left .base-table__header-main {
1837
+ flex: 1 1 auto;
1838
+ }
1839
+
1840
+ .base-table__header--center .base-table__header-main,
1841
+ .base-table__header--right .base-table__header-main {
1842
+ flex: 0 1 auto;
1843
+ }
1844
+
1845
+ .base-table__header-sort {
1846
+ display: inline-flex;
1847
+ align-items: center;
1848
+ justify-content: center;
1849
+ flex: 0 0 auto;
1850
+ width: 16px;
1851
+ height: 16px;
1852
+ color: #8c8c8c;
1853
+ }
1854
+
1855
+ .base-table__header-sort.is-active {
1856
+ color: #0938f7;
1857
+ }
1858
+
1859
+ .base-table__header-sort-icon {
1860
+ width: 15.351px;
1861
+ height: 16.031px;
1862
+ font-size: 16px;
1863
+ vertical-align: middle;
1864
+ }
1865
+
1105
1866
  .base-table__header-actions {
1106
1867
  display: flex;
1107
1868
  align-items: center;
1108
- margin-left: auto;
1109
1869
  flex-shrink: 0;
1110
1870
  }
1111
1871
 
1872
+ .base-table__header--left .base-table__header-actions {
1873
+ margin-left: auto;
1874
+ }
1875
+
1112
1876
  .base-table__header-icon {
1113
1877
  display: inline-flex;
1114
1878
  align-items: center;
@@ -1130,48 +1894,268 @@ defineExpose<BaseTableExpose>({
1130
1894
  background: rgba(64, 158, 255, 0.12);
1131
1895
  }
1132
1896
 
1133
- .base-table__header-icon-mark {
1897
+ .base-table__header-search-icon {
1898
+ width: 17.814px;
1899
+ height: 14.035px;
1900
+ font-size: 16px;
1901
+ vertical-align: middle;
1902
+ }
1903
+
1904
+ .base-table :deep(.el-table__cell.is-sortable .caret-wrapper) {
1905
+ display: none;
1906
+ }
1907
+
1908
+ .base-table__header-search-panel {
1909
+ position: fixed;
1910
+ z-index: 3000;
1911
+ display: flex;
1912
+ flex-direction: column;
1913
+ gap: 12px;
1914
+ padding: var(--el-popover-padding, 12px);
1915
+ border: 1px solid var(--el-border-color-light);
1916
+ border-radius: var(--el-popover-border-radius, 4px);
1917
+ background: var(--el-bg-color-overlay);
1918
+ box-shadow: var(--el-box-shadow-light);
1919
+ box-sizing: border-box;
1920
+ }
1921
+
1922
+ .base-table__header-search-input {
1923
+ width: 100%;
1924
+ height: 32px;
1925
+ padding: 1px 11px;
1926
+ border: none;
1927
+ border-radius: var(--el-border-radius-base);
1928
+ outline: none;
1929
+ color: var(--el-text-color-regular);
1930
+ background: var(--el-fill-color-blank);
1931
+ box-shadow: 0 0 0 1px var(--el-border-color) inset;
1932
+ box-sizing: border-box;
1933
+ font-size: var(--el-font-size-base);
1934
+ line-height: 30px;
1935
+ transition: box-shadow var(--el-transition-duration);
1936
+ }
1937
+
1938
+ .base-table__header-search-input:hover {
1939
+ box-shadow: 0 0 0 1px var(--el-border-color-hover) inset;
1940
+ }
1941
+
1942
+ .base-table__header-search-input:focus {
1943
+ box-shadow: 0 0 0 1px var(--el-color-primary) inset;
1944
+ }
1945
+
1946
+ .base-table__header-search-input::placeholder {
1947
+ color: var(--el-text-color-placeholder);
1948
+ }
1949
+
1950
+ .base-table__header-select,
1951
+ .base-table__header-cascader {
1134
1952
  position: relative;
1135
- display: inline-block;
1136
- width: 14px;
1137
- height: 14px;
1953
+ width: 100%;
1954
+ }
1955
+
1956
+ .base-table__header-select-trigger {
1957
+ display: flex;
1958
+ align-items: center;
1959
+ justify-content: space-between;
1960
+ gap: 8px;
1961
+ width: 100%;
1962
+ min-height: 32px;
1963
+ padding: 4px 11px;
1964
+ border: none;
1965
+ border-radius: var(--el-border-radius-base);
1966
+ outline: none;
1967
+ color: var(--el-text-color-regular);
1968
+ background: var(--el-fill-color-blank);
1969
+ box-shadow: 0 0 0 1px var(--el-border-color) inset;
1138
1970
  box-sizing: border-box;
1139
- border: 2px solid currentColor;
1140
- border-radius: 999px;
1971
+ cursor: pointer;
1972
+ font-size: var(--el-font-size-base);
1973
+ line-height: 24px;
1974
+ text-align: left;
1975
+ transition: box-shadow var(--el-transition-duration);
1141
1976
  }
1142
1977
 
1143
- .base-table__header-icon-mark::after {
1144
- content: '';
1145
- position: absolute;
1146
- right: -6px;
1147
- bottom: -4px;
1148
- width: 7px;
1149
- height: 2px;
1150
- border-radius: 999px;
1151
- background: currentColor;
1152
- transform: rotate(45deg);
1153
- transform-origin: center;
1978
+ .base-table__header-select-trigger:hover,
1979
+ .base-table__header-select-trigger:focus {
1980
+ box-shadow: 0 0 0 1px var(--el-color-primary) inset;
1981
+ }
1982
+
1983
+ .base-table__header-select-text {
1984
+ flex: 1 1 auto;
1985
+ min-width: 0;
1986
+ overflow: hidden;
1987
+ text-overflow: ellipsis;
1988
+ white-space: nowrap;
1989
+ }
1990
+
1991
+ .base-table__header-select-text.is-placeholder {
1992
+ color: var(--el-text-color-placeholder);
1154
1993
  }
1155
1994
 
1156
- .base-table__header-dropdown {
1995
+ .base-table__header-select-tags {
1996
+ display: flex;
1997
+ align-items: center;
1998
+ gap: 4px;
1999
+ min-width: 0;
2000
+ flex: 1 1 auto;
2001
+ overflow: hidden;
2002
+ }
2003
+
2004
+ .base-table__header-select-tag {
1157
2005
  display: inline-flex;
1158
2006
  align-items: center;
2007
+ max-width: 92px;
2008
+ min-height: 22px;
2009
+ padding: 0 6px;
2010
+ border-radius: 4px;
2011
+ background: var(--el-fill-color-light);
2012
+ color: var(--el-text-color-regular);
2013
+ box-sizing: border-box;
2014
+ font-size: 12px;
2015
+ line-height: 22px;
2016
+ overflow: hidden;
2017
+ text-overflow: ellipsis;
2018
+ white-space: nowrap;
1159
2019
  }
1160
2020
 
1161
- .base-table__header-search-panel {
2021
+ .base-table__header-select-tag--count {
2022
+ flex: 0 0 auto;
2023
+ color: var(--el-color-primary);
2024
+ }
2025
+
2026
+ .base-table__header-select-arrow {
2027
+ width: 8px;
2028
+ height: 8px;
2029
+ border-right: 1px solid currentColor;
2030
+ border-bottom: 1px solid currentColor;
2031
+ color: var(--el-text-color-placeholder);
2032
+ transform: rotate(45deg);
2033
+ transition: transform var(--el-transition-duration);
2034
+ flex: 0 0 auto;
2035
+ }
2036
+
2037
+ .base-table__header-select-arrow.is-open {
2038
+ transform: rotate(225deg);
2039
+ }
2040
+
2041
+ .base-table__header-select-options {
2042
+ max-height: 180px;
2043
+ margin-top: 6px;
2044
+ padding: 4px 0;
2045
+ border: 1px solid var(--el-border-color-light);
2046
+ border-radius: var(--el-border-radius-base);
2047
+ background: var(--el-bg-color-overlay);
2048
+ box-shadow: var(--el-box-shadow-light);
2049
+ overflow-y: auto;
2050
+ }
2051
+
2052
+ .base-table__header-select-option {
1162
2053
  display: flex;
1163
- flex-direction: column;
1164
- gap: 12px;
1165
- padding: 8px;
2054
+ align-items: center;
2055
+ width: 100%;
2056
+ min-height: 32px;
2057
+ padding: 0 12px;
2058
+ border: none;
2059
+ color: var(--el-text-color-regular);
2060
+ background: transparent;
2061
+ cursor: pointer;
2062
+ font-size: var(--el-font-size-base);
2063
+ line-height: 32px;
2064
+ text-align: left;
1166
2065
  }
1167
2066
 
1168
- :deep(.base-table__header-search-menu) {
1169
- padding: 0;
2067
+ .base-table__header-select-option-label {
2068
+ min-width: 0;
2069
+ overflow: hidden;
2070
+ text-overflow: ellipsis;
2071
+ white-space: nowrap;
1170
2072
  }
1171
2073
 
1172
- :deep(.base-table__header-search-dropdown) {
1173
- padding: 0;
1174
- border-radius: 12px;
2074
+ .base-table__header-select-option:hover {
2075
+ background: var(--el-fill-color-light);
2076
+ }
2077
+
2078
+ .base-table__header-select-option.is-selected {
2079
+ color: var(--el-color-primary);
2080
+ font-weight: 700;
2081
+ }
2082
+
2083
+ .base-table__header-select-option:disabled {
2084
+ color: var(--el-text-color-placeholder);
2085
+ cursor: not-allowed;
2086
+ }
2087
+
2088
+ .base-table__header-cascader-panel {
2089
+ display: flex;
2090
+ max-width: calc(100vw - 24px);
2091
+ min-height: 180px;
2092
+ max-height: 260px;
2093
+ margin-top: 6px;
2094
+ border: 1px solid var(--el-border-color-light);
2095
+ border-radius: var(--el-border-radius-base);
2096
+ background: var(--el-bg-color-overlay);
2097
+ box-shadow: var(--el-box-shadow-light);
2098
+ overflow: auto;
2099
+ }
2100
+
2101
+ .base-table__header-cascader-menu {
2102
+ min-width: 132px;
2103
+ padding: 4px 0;
2104
+ border-right: 1px solid var(--el-border-color-lighter);
2105
+ box-sizing: border-box;
2106
+ }
2107
+
2108
+ .base-table__header-cascader-menu:last-child {
2109
+ border-right: none;
2110
+ }
2111
+
2112
+ .base-table__header-cascader-option {
2113
+ display: flex;
2114
+ align-items: center;
2115
+ justify-content: space-between;
2116
+ gap: 8px;
2117
+ width: 100%;
2118
+ min-height: 32px;
2119
+ padding: 0 10px 0 12px;
2120
+ border: none;
2121
+ color: var(--el-text-color-regular);
2122
+ background: transparent;
2123
+ cursor: pointer;
2124
+ font-size: var(--el-font-size-base);
2125
+ line-height: 32px;
2126
+ text-align: left;
2127
+ }
2128
+
2129
+ .base-table__header-cascader-option:hover,
2130
+ .base-table__header-cascader-option.is-active {
2131
+ background: var(--el-fill-color-light);
2132
+ }
2133
+
2134
+ .base-table__header-cascader-option.is-selected {
2135
+ color: var(--el-color-primary);
2136
+ font-weight: 700;
2137
+ }
2138
+
2139
+ .base-table__header-cascader-option:disabled {
2140
+ color: var(--el-text-color-placeholder);
2141
+ cursor: not-allowed;
2142
+ }
2143
+
2144
+ .base-table__header-cascader-label {
2145
+ min-width: 0;
2146
+ overflow: hidden;
2147
+ text-overflow: ellipsis;
2148
+ white-space: nowrap;
2149
+ }
2150
+
2151
+ .base-table__header-cascader-arrow {
2152
+ width: 7px;
2153
+ height: 7px;
2154
+ border-right: 1px solid currentColor;
2155
+ border-bottom: 1px solid currentColor;
2156
+ color: var(--el-text-color-placeholder);
2157
+ transform: rotate(-45deg);
2158
+ flex: 0 0 auto;
1175
2159
  }
1176
2160
 
1177
2161
  .base-table__header-search-footer {