@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.
- package/README.md +25 -2
- package/components.manifest.json +185 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +1140 -12
- package/dist/style.css +1 -0
- package/package.json +4 -3
- package/src/components/BaseCard/BaseCard.types.ts +38 -0
- package/src/components/BaseCard/BaseCard.vue +250 -0
- package/src/components/BaseCard/README.md +164 -0
- package/src/components/BaseCard/index.ts +5 -0
- package/src/components/BaseEChart/BaseEChart.types.ts +35 -0
- package/src/components/BaseEChart/BaseEChart.vue +327 -0
- package/src/components/BaseEChart/README.md +136 -0
- package/src/components/BaseEChart/index.ts +5 -0
- package/src/components/BaseExportButton/BaseExportButton.vue +1 -1
- package/src/components/BaseExportButton/README.md +104 -8
- package/src/components/BaseTable/BaseTable.types.ts +174 -0
- package/src/components/BaseTable/BaseTable.vue +809 -0
- package/src/components/BaseTable/README.md +398 -0
- package/src/components/BaseTable/index.ts +25 -0
- package/src/echarts/index.ts +12 -0
- package/src/excel/exportExcel.ts +36 -10
- package/src/index.ts +15 -0
|
@@ -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>
|