@hbdlzy/ui-core 0.1.0 → 0.1.1

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.
@@ -0,0 +1,809 @@
1
+ <template>
2
+ <div class="base-table">
3
+ <div v-if="shouldRenderToolbar" class="base-table__toolbar">
4
+ <div class="base-table__toolbar-left">
5
+ <slot name="toolbar"></slot>
6
+ </div>
7
+ <div v-if="showPagination" class="base-table__toolbar-right">
8
+ <el-pagination
9
+ small
10
+ background
11
+ layout="total, prev, pager, next, sizes, jumper"
12
+ :total="paginationState.total"
13
+ :current-page="paginationState.currentPage"
14
+ :page-size="paginationState.pageSize"
15
+ :page-sizes="paginationState.pageSizes"
16
+ :pager-count="5"
17
+ :popper-append-to-body="false"
18
+ @size-change="handleSizeChange"
19
+ @current-change="handleCurrentChange"
20
+ />
21
+ </div>
22
+ </div>
23
+
24
+ <el-table
25
+ ref="tableRef"
26
+ v-loading="loading"
27
+ :data="rows"
28
+ :row-key="rowKey"
29
+ :height="normalizedHeight"
30
+ :border="border"
31
+ :stripe="stripe"
32
+ :default-sort="normalizedDefaultSort"
33
+ highlight-current-row
34
+ @selection-change="handleSelectionChange"
35
+ @sort-change="handleSortChange"
36
+ >
37
+ <template #empty>
38
+ <el-empty :description="emptyText"></el-empty>
39
+ </template>
40
+
41
+ <el-table-column
42
+ v-if="hasSelection"
43
+ type="selection"
44
+ :width="selectionWidth"
45
+ align="center"
46
+ :selectable="rowSelectable"
47
+ />
48
+
49
+ <el-table-column
50
+ v-if="hasIndex"
51
+ type="index"
52
+ :label="indexLabel"
53
+ :width="indexWidth"
54
+ fixed="left"
55
+ align="center"
56
+ />
57
+
58
+ <el-table-column
59
+ v-for="column in columns"
60
+ :key="getColumnKey(column)"
61
+ :prop="getColumnProp(column)"
62
+ :label="column.label"
63
+ :type="column.type || undefined"
64
+ :width="toCssValue(column.width)"
65
+ :min-width="toCssValue(column.minWidth)"
66
+ :fixed="column.fixed"
67
+ :align="column.align || 'left'"
68
+ :header-align="column.headerAlign || column.align || 'left'"
69
+ :sortable="resolveColumnSortable(column)"
70
+ :show-overflow-tooltip="column.showOverflowTooltip !== false"
71
+ :class-name="column.className"
72
+ :label-class-name="column.headerClassName"
73
+ >
74
+ <template v-if="hasHeaderSlot(column)" #header="headerScope">
75
+ <slot :name="resolveHeaderSlotName(column)" v-bind="headerScope" :column-config="column"></slot>
76
+ </template>
77
+
78
+ <template #default="scope">
79
+ <slot
80
+ v-if="hasCellSlot(column)"
81
+ :name="resolveCellSlotName(column)"
82
+ :row="scope.row"
83
+ :column-config="column"
84
+ :column-index="scope.$index"
85
+ :value="getCellValue(scope.row, column)"
86
+ ></slot>
87
+
88
+ <template v-else>
89
+ <div
90
+ v-if="isActionColumn(column)"
91
+ class="base-table__actions"
92
+ >
93
+ <el-button
94
+ v-for="action in getVisibleActions(column, scope.row)"
95
+ :key="`${action.type}-${action.label}`"
96
+ link
97
+ :type="action.buttonType || (action.type === 'delete' ? 'danger' : 'primary')"
98
+ :disabled="isActionDisabled(action, scope.row)"
99
+ @click="handleRowAction(scope.row, action, column)"
100
+ >
101
+ {{ action.label }}
102
+ </el-button>
103
+ </div>
104
+
105
+ <el-image
106
+ v-else-if="column.kind === 'image'"
107
+ class="base-table__image"
108
+ :style="getImageStyle(column)"
109
+ :src="toImageSrc(getCellValue(scope.row, column))"
110
+ :preview-src-list="getPreviewSrcList(scope.row, column)"
111
+ :fit="column.imageFit || 'cover'"
112
+ />
113
+
114
+ <div v-else-if="column.kind === 'tag'" class="base-table__tags">
115
+ <template v-if="getMatchedOptions(scope.row, column).length">
116
+ <el-tag
117
+ v-for="option in getMatchedOptions(scope.row, column)"
118
+ :key="String(option.value)"
119
+ :type="option.tagType || 'info'"
120
+ :style="option.color ? { color: option.color, borderColor: `${option.color}33`, backgroundColor: `${option.color}1a` } : undefined"
121
+ size="small"
122
+ >
123
+ {{ option.label }}
124
+ </el-tag>
125
+ </template>
126
+ <span v-else>{{ resolveEmptyText(column) }}</span>
127
+ </div>
128
+
129
+ <div v-else-if="column.kind === 'html'" v-html="getHtmlContent(scope.row, column)"></div>
130
+
131
+ <el-input
132
+ v-else-if="column.kind === 'input'"
133
+ :model-value="toDisplayValue(getCellValue(scope.row, column), '')"
134
+ :type="column.inputType || 'text'"
135
+ @update:model-value="handleInputChange($event, scope.row, column)"
136
+ />
137
+
138
+ <button
139
+ v-else-if="isLinkColumn(column)"
140
+ class="base-table__link"
141
+ type="button"
142
+ @click="handleCellClick(scope.row, column)"
143
+ >
144
+ {{ formatCellDisplay(scope.row, column) }}
145
+ </button>
146
+
147
+ <div v-else class="base-table__text">
148
+ {{ formatCellDisplay(scope.row, column) }}
149
+ </div>
150
+ </template>
151
+ </template>
152
+ </el-table-column>
153
+ </el-table>
154
+ </div>
155
+ </template>
156
+
157
+ <script setup lang="ts">
158
+ import { computed, onMounted, reactive, ref, useSlots, watch } from 'vue'
159
+ import type { TableInstance } from 'element-plus'
160
+ import type {
161
+ BaseTableAction,
162
+ BaseTableCellPayload,
163
+ BaseTableColumn,
164
+ BaseTableCssValue,
165
+ BaseTableExpose,
166
+ BaseTableLoadedPayload,
167
+ BaseTableOption,
168
+ BaseTablePagination,
169
+ BaseTableProps,
170
+ BaseTableRequestParams,
171
+ BaseTableRowActionPayload,
172
+ BaseTableSortDirection,
173
+ BaseTableSortOrder,
174
+ BaseTableSortPayload
175
+ } from './BaseTable.types'
176
+
177
+ defineOptions({
178
+ name: 'BaseTable'
179
+ })
180
+
181
+ type BaseTableRow = Record<string, any>
182
+
183
+ const props = withDefaults(defineProps<BaseTableProps>(), {
184
+ data: () => [],
185
+ requestParams: () => ({}),
186
+ autoLoad: true,
187
+ reloadOnParamsChange: true,
188
+ reloadOnSortChange: true,
189
+ rowKey: 'id',
190
+ height: '100%',
191
+ border: false,
192
+ stripe: false,
193
+ showToolbar: true,
194
+ showPagination: true,
195
+ hasSelection: false,
196
+ hasIndex: false,
197
+ indexLabel: '序号',
198
+ indexWidth: 60,
199
+ selectionWidth: 50,
200
+ pagination: () => ({}),
201
+ currentPageKey: 'pageNum',
202
+ pageSizeKey: 'pageSize',
203
+ defaultSort: () => ({
204
+ prop: undefined,
205
+ order: null
206
+ }),
207
+ sortFieldKey: 'sortField',
208
+ sortOrderKey: 'sortOrder',
209
+ sortOrderMap: () => ({
210
+ ascending: 'asc',
211
+ descending: 'desc'
212
+ }),
213
+ emptyText: '暂无数据',
214
+ loadingText: '加载中...'
215
+ })
216
+
217
+ const emit = defineEmits<{
218
+ (event: 'selection-change', rows: BaseTableRow[]): void
219
+ (event: 'sort-change', payload: BaseTableSortPayload): void
220
+ (event: 'row-action', payload: BaseTableRowActionPayload): void
221
+ (event: 'cell-click', payload: BaseTableCellPayload): void
222
+ (event: 'cell-input', payload: BaseTableCellPayload): void
223
+ (event: 'page-change', currentPage: number): void
224
+ (event: 'size-change', pageSize: number): void
225
+ (event: 'update:pagination', pagination: BaseTablePagination): void
226
+ (event: 'loaded', payload: BaseTableLoadedPayload): void
227
+ (event: 'request-error', error: unknown): void
228
+ }>()
229
+
230
+ const slots = useSlots()
231
+ const tableRef = ref<TableInstance>()
232
+ const loading = ref(false)
233
+ const rows = ref<BaseTableRow[]>(normalizeRows(props.data))
234
+ const selectionRows = ref<BaseTableRow[]>([])
235
+ const sortState = reactive({
236
+ prop: props.defaultSort?.prop,
237
+ order: props.defaultSort?.order || null
238
+ })
239
+ const paginationState = reactive<BaseTablePagination>({
240
+ currentPage: 1,
241
+ pageSize: 20,
242
+ total: 0,
243
+ pageSizes: [10, 20, 50, 100]
244
+ })
245
+
246
+ syncPagination(props.pagination)
247
+
248
+ const normalizedHeight = computed(() => toCssValue(props.height))
249
+ const shouldRenderToolbar = computed(() => props.showToolbar && (Boolean(slots.toolbar) || props.showPagination))
250
+ const normalizedDefaultSort = computed(() => {
251
+ if (!sortState.prop || !sortState.order) {
252
+ return undefined
253
+ }
254
+
255
+ return {
256
+ prop: sortState.prop,
257
+ order: sortState.order
258
+ }
259
+ })
260
+
261
+ watch(
262
+ () => props.data,
263
+ (value) => {
264
+ if (!props.request) {
265
+ setData(value || [])
266
+ }
267
+ },
268
+ { deep: true }
269
+ )
270
+
271
+ watch(
272
+ () => props.pagination,
273
+ (value) => {
274
+ syncPagination(value)
275
+ },
276
+ { deep: true }
277
+ )
278
+
279
+ watch(
280
+ () => props.requestParams,
281
+ () => {
282
+ if (props.request && props.reloadOnParamsChange && props.autoLoad) {
283
+ paginationState.currentPage = 1
284
+ void load()
285
+ }
286
+ },
287
+ { deep: true }
288
+ )
289
+
290
+ watch(
291
+ () => props.defaultSort,
292
+ (value) => {
293
+ sortState.prop = value?.prop
294
+ sortState.order = value?.order || null
295
+ },
296
+ { deep: true }
297
+ )
298
+
299
+ onMounted(() => {
300
+ if (props.request && props.autoLoad) {
301
+ void load()
302
+ return
303
+ }
304
+
305
+ if (!props.request) {
306
+ rows.value = normalizeRows(props.data)
307
+ if (!props.pagination?.total) {
308
+ paginationState.total = rows.value.length
309
+ emitPagination()
310
+ }
311
+ }
312
+ })
313
+
314
+ function syncPagination(value?: Partial<BaseTablePagination>) {
315
+ paginationState.currentPage = Number(value?.currentPage || paginationState.currentPage || 1)
316
+ paginationState.pageSize = Number(value?.pageSize || paginationState.pageSize || 20)
317
+ paginationState.total = Number(value?.total || value?.total === 0 ? value.total : paginationState.total || rows.value.length)
318
+ paginationState.pageSizes = Array.isArray(value?.pageSizes) && value.pageSizes.length
319
+ ? [...value.pageSizes]
320
+ : [...(paginationState.pageSizes.length ? paginationState.pageSizes : [10, 20, 50, 100])]
321
+ }
322
+
323
+ function emitPagination() {
324
+ emit('update:pagination', {
325
+ currentPage: paginationState.currentPage,
326
+ pageSize: paginationState.pageSize,
327
+ total: paginationState.total,
328
+ pageSizes: [...paginationState.pageSizes]
329
+ })
330
+ }
331
+
332
+ async function load(data?: BaseTableRow[]) {
333
+ if (Array.isArray(data)) {
334
+ setData(data)
335
+ return
336
+ }
337
+
338
+ if (!props.request) {
339
+ setData(props.data || [])
340
+ return
341
+ }
342
+
343
+ loading.value = true
344
+ const requestParams = buildRequestParams()
345
+
346
+ try {
347
+ const result = await props.request(requestParams)
348
+ const normalized = normalizeRequestResult(result, requestParams)
349
+ rows.value = normalized.rows
350
+ paginationState.total = normalized.total
351
+ emitPagination()
352
+ emit('loaded', {
353
+ rows: [...rows.value],
354
+ total: paginationState.total,
355
+ params: requestParams
356
+ })
357
+ } catch (error) {
358
+ rows.value = []
359
+ paginationState.total = 0
360
+ emitPagination()
361
+ emit('request-error', error)
362
+ } finally {
363
+ loading.value = false
364
+ }
365
+ }
366
+
367
+ async function refresh() {
368
+ await load()
369
+ }
370
+
371
+ async function resetPage() {
372
+ paginationState.currentPage = 1
373
+ emitPagination()
374
+ await load()
375
+ }
376
+
377
+ function setData(data: BaseTableRow[]) {
378
+ rows.value = normalizeRows(data)
379
+ if (!props.request) {
380
+ paginationState.total = Number(props.pagination?.total ?? rows.value.length)
381
+ emitPagination()
382
+ }
383
+ }
384
+
385
+ function clearSelection() {
386
+ tableRef.value?.clearSelection()
387
+ selectionRows.value = []
388
+ }
389
+
390
+ function toggleRowSelection(row: BaseTableRow, selected?: boolean) {
391
+ tableRef.value?.toggleRowSelection(row, selected)
392
+ }
393
+
394
+ function getSelectionRows() {
395
+ return [...selectionRows.value]
396
+ }
397
+
398
+ function getRows() {
399
+ return [...rows.value]
400
+ }
401
+
402
+ function handleSelectionChange(value: BaseTableRow[]) {
403
+ selectionRows.value = value
404
+ emit('selection-change', value)
405
+ }
406
+
407
+ function handleSortChange(payload: { prop?: string; order: BaseTableSortOrder }) {
408
+ const column = props.columns.find((item) => String(item.prop || '') === String(payload.prop || ''))
409
+
410
+ sortState.prop = payload.prop
411
+ sortState.order = payload.order
412
+
413
+ const sortPayload = buildSortPayload(column, payload.prop, payload.order)
414
+
415
+ emit('sort-change', sortPayload)
416
+
417
+ if (props.request && props.reloadOnSortChange) {
418
+ paginationState.currentPage = 1
419
+ emitPagination()
420
+ void load()
421
+ }
422
+ }
423
+
424
+ function handleCurrentChange(value: number) {
425
+ paginationState.currentPage = value
426
+ emitPagination()
427
+ emit('page-change', value)
428
+ if (props.request) {
429
+ void load()
430
+ }
431
+ }
432
+
433
+ function handleSizeChange(value: number) {
434
+ paginationState.pageSize = value
435
+ paginationState.currentPage = 1
436
+ emitPagination()
437
+ emit('size-change', value)
438
+ if (props.request) {
439
+ void load()
440
+ }
441
+ }
442
+
443
+ function handleRowAction(row: BaseTableRow, action: BaseTableAction<BaseTableRow>, column: BaseTableColumn<BaseTableRow>) {
444
+ emit('row-action', {
445
+ row,
446
+ action,
447
+ column
448
+ })
449
+ }
450
+
451
+ function handleCellClick(row: BaseTableRow, column: BaseTableColumn<BaseTableRow>) {
452
+ column.clickHandler?.(row, column)
453
+ emit('cell-click', {
454
+ row,
455
+ column,
456
+ value: getCellValue(row, column)
457
+ })
458
+ }
459
+
460
+ function handleInputChange(value: string, row: BaseTableRow, column: BaseTableColumn<BaseTableRow>) {
461
+ if (!column.prop) {
462
+ return
463
+ }
464
+
465
+ row[column.prop] = value
466
+ column.inputHandler?.(value, row, column)
467
+ emit('cell-input', {
468
+ row,
469
+ column,
470
+ value
471
+ })
472
+ }
473
+
474
+ function buildRequestParams(): BaseTableRequestParams {
475
+ const params: BaseTableRequestParams = {
476
+ ...props.requestParams,
477
+ currentPage: paginationState.currentPage,
478
+ pageSize: paginationState.pageSize,
479
+ [props.currentPageKey]: paginationState.currentPage,
480
+ [props.pageSizeKey]: paginationState.pageSize
481
+ }
482
+
483
+ const currentColumn = props.columns.find((item) => String(item.prop || '') === String(sortState.prop || ''))
484
+ const sortPayload = buildSortPayload(currentColumn, sortState.prop, sortState.order)
485
+ const sortParams = buildSortParams(sortPayload)
486
+
487
+ return {
488
+ ...params,
489
+ ...sortParams
490
+ }
491
+ }
492
+
493
+ function normalizeRequestResult(result: unknown, requestParams: BaseTableRequestParams) {
494
+ if (props.resultAdapter) {
495
+ const adaptedResult = props.resultAdapter(result, requestParams)
496
+ const adaptedRows = normalizeRows(adaptedResult?.rows)
497
+
498
+ return {
499
+ rows: adaptedRows,
500
+ total: Number(adaptedResult?.total ?? adaptedRows.length)
501
+ }
502
+ }
503
+
504
+ if (Array.isArray(result)) {
505
+ return {
506
+ rows: normalizeRows(result),
507
+ total: result.length
508
+ }
509
+ }
510
+
511
+ const payload = (result || {}) as Record<string, any>
512
+ const container = payload.data && typeof payload.data === 'object' ? payload.data : payload
513
+ const list = container.records || container.list || container.items || []
514
+ const normalizedRows = normalizeRows(list)
515
+
516
+ return {
517
+ rows: normalizedRows,
518
+ total: Number(container.total ?? normalizedRows.length)
519
+ }
520
+ }
521
+
522
+ function normalizeRows(data?: BaseTableRow[]) {
523
+ return Array.isArray(data) ? [...data] : []
524
+ }
525
+
526
+ function getColumnKey(column: BaseTableColumn<BaseTableRow>) {
527
+ return String(column.prop || column.label)
528
+ }
529
+
530
+ function getColumnProp(column: BaseTableColumn<BaseTableRow>) {
531
+ return column.prop ? String(column.prop) : undefined
532
+ }
533
+
534
+ function getCellValue(row: BaseTableRow, column: BaseTableColumn<BaseTableRow>) {
535
+ if (!column.prop) {
536
+ return undefined
537
+ }
538
+
539
+ return row[column.prop]
540
+ }
541
+
542
+ function formatCellDisplay(row: BaseTableRow, column: BaseTableColumn<BaseTableRow>) {
543
+ if (column.formatter) {
544
+ const formatted = column.formatter(row, column)
545
+ return toDisplayValue(formatted, resolveEmptyText(column))
546
+ }
547
+
548
+ return toDisplayValue(getCellValue(row, column), resolveEmptyText(column))
549
+ }
550
+
551
+ function getHtmlContent(row: BaseTableRow, column: BaseTableColumn<BaseTableRow>) {
552
+ if (column.html) {
553
+ return column.html(row, column)
554
+ }
555
+
556
+ const value = getCellValue(row, column)
557
+ return value === undefined || value === null ? '' : String(value)
558
+ }
559
+
560
+ function isActionColumn(column: BaseTableColumn<BaseTableRow>) {
561
+ return column.kind === 'actions' || Boolean(column.actions?.length)
562
+ }
563
+
564
+ function isLinkColumn(column: BaseTableColumn<BaseTableRow>) {
565
+ return column.kind === 'link' || Boolean(column.clickable)
566
+ }
567
+
568
+ function getVisibleActions(column: BaseTableColumn<BaseTableRow>, row: BaseTableRow) {
569
+ return (column.actions || []).filter((action) => {
570
+ if (typeof action.visible === 'function') {
571
+ return action.visible(row)
572
+ }
573
+
574
+ return action.visible !== false
575
+ })
576
+ }
577
+
578
+ function isActionDisabled(action: BaseTableAction<BaseTableRow>, row: BaseTableRow) {
579
+ if (typeof action.disabled === 'function') {
580
+ return action.disabled(row)
581
+ }
582
+
583
+ return Boolean(action.disabled)
584
+ }
585
+
586
+ function resolveCellSlotName(column: BaseTableColumn<BaseTableRow>) {
587
+ return column.slotName || (column.prop ? `cell-${String(column.prop)}` : '')
588
+ }
589
+
590
+ function resolveHeaderSlotName(column: BaseTableColumn<BaseTableRow>) {
591
+ return column.headerSlotName || (column.prop ? `header-${String(column.prop)}` : '')
592
+ }
593
+
594
+ function hasCellSlot(column: BaseTableColumn<BaseTableRow>) {
595
+ const slotName = resolveCellSlotName(column)
596
+ return Boolean(slotName && slots[slotName])
597
+ }
598
+
599
+ function hasHeaderSlot(column: BaseTableColumn<BaseTableRow>) {
600
+ const slotName = resolveHeaderSlotName(column)
601
+ return Boolean(slotName && slots[slotName])
602
+ }
603
+
604
+ function getMatchedOptions(row: BaseTableRow, column: BaseTableColumn<BaseTableRow>) {
605
+ const value = getCellValue(row, column)
606
+ const values = Array.isArray(value) ? value : [value]
607
+ const valueKey = column.optionValueKey || 'value'
608
+ const labelKey = column.optionLabelKey || 'label'
609
+ const tagTypeKey = column.optionTagTypeKey || 'tagType'
610
+
611
+ return values
612
+ .filter((item) => item !== undefined && item !== null && item !== '')
613
+ .map((item) => {
614
+ const matched = (column.options || []).find((option) => (option as Record<string, any>)[valueKey] === item)
615
+ if (!matched) {
616
+ return {
617
+ label: String(item),
618
+ value: item,
619
+ tagType: 'info'
620
+ } as BaseTableOption
621
+ }
622
+
623
+ return {
624
+ label: String((matched as Record<string, any>)[labelKey]),
625
+ value: (matched as Record<string, any>)[valueKey],
626
+ tagType: (matched as Record<string, any>)[tagTypeKey],
627
+ color: (matched as Record<string, any>).color
628
+ } as BaseTableOption
629
+ })
630
+ }
631
+
632
+ function getImageStyle(column: BaseTableColumn<BaseTableRow>) {
633
+ return {
634
+ width: toCssValue(column.imageWidth || 50),
635
+ height: toCssValue(column.imageHeight || 50)
636
+ }
637
+ }
638
+
639
+ function toImageSrc(value: unknown) {
640
+ if (typeof value === 'string') {
641
+ return value
642
+ }
643
+
644
+ if (value && typeof value === 'object' && 'url' in (value as Record<string, any>)) {
645
+ return String((value as Record<string, any>).url || '')
646
+ }
647
+
648
+ return ''
649
+ }
650
+
651
+ function getPreviewSrcList(row: BaseTableRow, column: BaseTableColumn<BaseTableRow>) {
652
+ const src = toImageSrc(getCellValue(row, column))
653
+ return src ? [src] : []
654
+ }
655
+
656
+ function resolveEmptyText(column: BaseTableColumn<BaseTableRow>) {
657
+ return column.emptyText || '--'
658
+ }
659
+
660
+ function resolveColumnSortable(column: BaseTableColumn<BaseTableRow>) {
661
+ if (!props.request) {
662
+ return column.sortable || false
663
+ }
664
+
665
+ if (column.sortable === true) {
666
+ return 'custom'
667
+ }
668
+
669
+ return column.sortable || false
670
+ }
671
+
672
+ function buildSortPayload(
673
+ column: BaseTableColumn<BaseTableRow> | undefined,
674
+ prop?: string,
675
+ order: BaseTableSortOrder = null
676
+ ): BaseTableSortPayload<BaseTableRow> {
677
+ return {
678
+ column,
679
+ prop,
680
+ order,
681
+ field: resolveSortField(column, prop),
682
+ direction: resolveSortDirection(order)
683
+ }
684
+ }
685
+
686
+ function buildSortParams(payload: BaseTableSortPayload<BaseTableRow>) {
687
+ if (!payload.field || !payload.direction) {
688
+ return {}
689
+ }
690
+
691
+ const mappedParams = props.sortMapper?.(payload)
692
+ if (mappedParams && typeof mappedParams === 'object') {
693
+ return mappedParams
694
+ }
695
+
696
+ return {
697
+ [props.sortFieldKey]: payload.field,
698
+ [props.sortOrderKey]: payload.direction
699
+ }
700
+ }
701
+
702
+ function resolveSortField(column: BaseTableColumn<BaseTableRow> | undefined, prop?: string) {
703
+ return column?.sortField || prop
704
+ }
705
+
706
+ function resolveSortDirection(order: BaseTableSortOrder): BaseTableSortDirection {
707
+ if (order === 'ascending') {
708
+ return props.sortOrderMap.ascending as BaseTableSortDirection
709
+ }
710
+
711
+ if (order === 'descending') {
712
+ return props.sortOrderMap.descending as BaseTableSortDirection
713
+ }
714
+
715
+ return null
716
+ }
717
+
718
+ function toDisplayValue(value: unknown, emptyText = '--') {
719
+ if (value === 0) {
720
+ return '0'
721
+ }
722
+
723
+ if (value === undefined || value === null || value === '') {
724
+ return emptyText
725
+ }
726
+
727
+ return String(value)
728
+ }
729
+
730
+ function toCssValue(value?: BaseTableCssValue) {
731
+ if (value === undefined || value === null || value === '') {
732
+ return undefined
733
+ }
734
+
735
+ return typeof value === 'number' ? `${value}px` : value
736
+ }
737
+
738
+ defineExpose<BaseTableExpose>({
739
+ load,
740
+ refresh,
741
+ setData,
742
+ resetPage,
743
+ clearSelection,
744
+ toggleRowSelection,
745
+ getSelectionRows,
746
+ getRows
747
+ })
748
+ </script>
749
+
750
+ <style scoped>
751
+ .base-table {
752
+ display: flex;
753
+ flex-direction: column;
754
+ height: 100%;
755
+ min-height: 0;
756
+ }
757
+
758
+ .base-table__toolbar {
759
+ display: flex;
760
+ align-items: center;
761
+ justify-content: space-between;
762
+ gap: 16px;
763
+ margin-bottom: 16px;
764
+ min-height: 32px;
765
+ flex-wrap: wrap;
766
+ }
767
+
768
+ .base-table__toolbar-left {
769
+ flex: 1 1 auto;
770
+ min-width: 0;
771
+ }
772
+
773
+ .base-table__toolbar-right {
774
+ flex: 0 1 auto;
775
+ }
776
+
777
+ .base-table__actions {
778
+ display: flex;
779
+ align-items: center;
780
+ gap: 4px;
781
+ flex-wrap: wrap;
782
+ }
783
+
784
+ .base-table__link {
785
+ padding: 0;
786
+ border: none;
787
+ background: transparent;
788
+ color: var(--el-color-primary);
789
+ cursor: pointer;
790
+ }
791
+
792
+ .base-table__text {
793
+ white-space: nowrap;
794
+ overflow: hidden;
795
+ text-overflow: ellipsis;
796
+ }
797
+
798
+ .base-table__tags {
799
+ display: flex;
800
+ align-items: center;
801
+ gap: 4px;
802
+ flex-wrap: wrap;
803
+ }
804
+
805
+ .base-table__image {
806
+ border-radius: 4px;
807
+ overflow: hidden;
808
+ }
809
+ </style>