c-admin-kit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +232 -0
  2. package/dist/c-admin-kit.css +1 -0
  3. package/dist/c-admin-kit.js +2482 -0
  4. package/dist/c-admin-kit.umd.cjs +10 -0
  5. package/dist/components/AsyncButton/index.vue.d.ts +48 -0
  6. package/dist/components/CollapsibleContainer/index.vue.d.ts +73 -0
  7. package/dist/components/CustomDrawer/index.vue.d.ts +149 -0
  8. package/dist/components/ImagePreview/index.vue.d.ts +34 -0
  9. package/dist/components/SearchBox/index.vue.d.ts +122 -0
  10. package/dist/components/SelectWithAll/index.vue.d.ts +169 -0
  11. package/dist/components/SelectWithPage/index.vue.d.ts +78 -0
  12. package/dist/components/SimpleTable/index.vue.d.ts +337 -0
  13. package/dist/components/SimpleTable/useTableColumnConfig.d.ts +5 -0
  14. package/dist/components/diff/index.vue.d.ts +33 -0
  15. package/dist/components/index.d.ts +12 -0
  16. package/dist/composables/index.d.ts +6 -0
  17. package/dist/composables/useConfirmAction.d.ts +5 -0
  18. package/dist/composables/useConfirmSubmit.d.ts +5 -0
  19. package/dist/composables/useDialog.d.ts +5 -0
  20. package/dist/composables/useDownload.d.ts +5 -0
  21. package/dist/composables/useForm.d.ts +5 -0
  22. package/dist/composables/useListPage.d.ts +5 -0
  23. package/dist/index.d.ts +9 -0
  24. package/dist/types.d.ts +282 -0
  25. package/dist/utils/index.d.ts +4 -0
  26. package/dist/utils/scroll-to.d.ts +7 -0
  27. package/dist/utils/searchFieldFactory.d.ts +34 -0
  28. package/dist/utils/treeManager.d.ts +64 -0
  29. package/dist/utils/validate.d.ts +30 -0
  30. package/package.json +86 -0
  31. package/src/components/AsyncButton/index.vue +58 -0
  32. package/src/components/CollapsibleContainer/index.vue +240 -0
  33. package/src/components/CustomDrawer/index.vue +167 -0
  34. package/src/components/ImagePreview/index.vue +88 -0
  35. package/src/components/SearchBox/index.vue +582 -0
  36. package/src/components/SelectWithAll/index.vue +281 -0
  37. package/src/components/SelectWithPage/index.vue +204 -0
  38. package/src/components/SimpleTable/index.vue +781 -0
  39. package/src/components/SimpleTable/useTableColumnConfig.ts +139 -0
  40. package/src/components/diff/index.vue +265 -0
  41. package/src/components/index.ts +35 -0
  42. package/src/composables/index.ts +6 -0
  43. package/src/composables/useConfirmAction.ts +62 -0
  44. package/src/composables/useConfirmSubmit.ts +68 -0
  45. package/src/composables/useDialog.ts +48 -0
  46. package/src/composables/useDownload.ts +83 -0
  47. package/src/composables/useForm.ts +114 -0
  48. package/src/composables/useListPage.ts +243 -0
  49. package/src/env.d.ts +7 -0
  50. package/src/index.ts +44 -0
  51. package/src/types.ts +344 -0
  52. package/src/utils/index.ts +4 -0
  53. package/src/utils/scroll-to.ts +60 -0
  54. package/src/utils/searchFieldFactory.ts +302 -0
  55. package/src/utils/treeManager.ts +218 -0
  56. package/src/utils/validate.ts +64 -0
@@ -0,0 +1,781 @@
1
+ <template>
2
+ <div class="c-simple-table" ref="tableContainerRef">
3
+ <!-- 表格头部 -->
4
+ <div v-if="$slots.headerLeft || $slots.headerRight || tableKey" class="table-header">
5
+ <div class="header-left">
6
+ <slot name="headerLeft"></slot>
7
+ </div>
8
+ <div class="header-right">
9
+ <slot name="headerRight"></slot>
10
+ <!-- 动态列设置按钮 -->
11
+ <el-popover
12
+ v-if="tableKey"
13
+ ref="popoverRef"
14
+ placement="bottom-end"
15
+ title="自定义列"
16
+ :width="200"
17
+ trigger="click"
18
+ @show="handlePopoverShow"
19
+ >
20
+ <template #reference>
21
+ <el-button size="small">
22
+ <el-icon><Setting /></el-icon> 自定义列
23
+ </el-button>
24
+ </template>
25
+ <div class="column-setting-list">
26
+ <el-checkbox-group v-model="tempVisibleColKeys">
27
+ <div v-for="col in settingColumns" :key="col._key" class="column-setting-item">
28
+ <el-checkbox :value="col._key" :label="col._key">{{ col.label }}</el-checkbox>
29
+ </div>
30
+ </el-checkbox-group>
31
+ </div>
32
+ <div style="text-align: right; margin-top: 10px;">
33
+ <el-button size="small" @click="handleCancelSettings">取消</el-button>
34
+ <el-button size="small" type="primary" :loading="savingColumns" @click="handleConfirmSettings">确定</el-button>
35
+ </div>
36
+ </el-popover>
37
+ </div>
38
+ </div>
39
+
40
+ <!-- 表格主体 -->
41
+ <el-table
42
+ ref="tableRef"
43
+ :data="tableData"
44
+ :border="border"
45
+ v-loading="loading"
46
+ style="width: 100%"
47
+ :row-key="rowKey"
48
+ v-bind="$attrs"
49
+ @selection-change="handleSelectionChange"
50
+ :max-height="computedTableHeight"
51
+ :height="autoHeight ? computedTableHeight : scrollY"
52
+ >
53
+ <!-- 动态生成列 -->
54
+ <template v-for="(column, index) in computedColumns" :key="column._key || index">
55
+ <!-- 拖拽列 -->
56
+ <el-table-column
57
+ v-if="column.type === 'drag'"
58
+ :width="column.width || 55"
59
+ :align="column.align || 'center'"
60
+ :fixed="column.fixed"
61
+ >
62
+ <template #header>
63
+ <span>{{ column.label || "排序" }}</span>
64
+ </template>
65
+ <template #default>
66
+ <div class="drag-handle" style="cursor: move">
67
+ <el-icon><Rank /></el-icon>
68
+ </div>
69
+ </template>
70
+ </el-table-column>
71
+
72
+ <!-- 选择列 -->
73
+ <el-table-column
74
+ v-else-if="column.type === 'selection'"
75
+ :selectable="selectable"
76
+ :reserve-selection="keepSelection"
77
+ type="selection"
78
+ :width="column.width || 55"
79
+ :align="column.align || 'center'"
80
+ />
81
+
82
+ <!-- 索引列 -->
83
+ <el-table-column
84
+ v-else-if="column.type === 'index'"
85
+ type="index"
86
+ :label="column.label || '序号'"
87
+ :width="column.width || 60"
88
+ :align="column.align || 'center'"
89
+ />
90
+
91
+ <!-- 状态列 -->
92
+ <el-table-column
93
+ v-else-if="column.type === 'status'"
94
+ :prop="column.prop"
95
+ :label="column.label || '状态'"
96
+ :width="column.width || 100"
97
+ :align="column.align || 'center'"
98
+ :fixed="column.fixed"
99
+ :sortable="column.sortable"
100
+ >
101
+ <template #default="scope">
102
+ <slot v-if="column.slot" :name="column.slot" v-bind="scope"></slot>
103
+ <el-tag
104
+ v-else
105
+ :type="getStatusType(getNestedValue(scope.row, column.prop), column.statusMap)"
106
+ :effect="column.effect || 'light'"
107
+ :size="column.size || 'default'"
108
+ >
109
+ {{ getStatusText(getNestedValue(scope.row, column.prop), column.statusMap) }}
110
+ </el-tag>
111
+ </template>
112
+ </el-table-column>
113
+
114
+ <!-- 普通列 -->
115
+ <el-table-column
116
+ v-else
117
+ :prop="column.prop"
118
+ :label="column.label"
119
+ :width="column.width"
120
+ :min-width="column.minWidth"
121
+ :align="column.align || 'left'"
122
+ :fixed="column.fixed"
123
+ :sortable="column.sortable"
124
+ :show-overflow-tooltip="column.showOverflowTooltip !== false"
125
+ >
126
+ <template v-if="column.headerSlot" #header="scope">
127
+ <slot :name="column.headerSlot" v-bind="scope"></slot>
128
+ </template>
129
+ <template #default="scope">
130
+ <slot v-if="column.slot" :name="column.slot" v-bind="scope"></slot>
131
+ <component
132
+ v-else-if="column.render"
133
+ :is="column.render(scope.row, scope.column, scope.$index)"
134
+ />
135
+ <span v-else-if="column.formatter">
136
+ {{
137
+ column.formatter(
138
+ scope.row,
139
+ scope.column,
140
+ getNestedValue(scope.row, column.prop),
141
+ scope.$index
142
+ )
143
+ }}
144
+ </span>
145
+ <span v-else>{{ formatCellValue(scope.row, column) }}</span>
146
+ </template>
147
+ </el-table-column>
148
+ </template>
149
+
150
+ <slot></slot>
151
+ </el-table>
152
+
153
+ <!-- 分页 -->
154
+ <div v-if="showPagination" class="table-pagination">
155
+ <el-pagination
156
+ v-model:current-page="currentPage"
157
+ v-model:page-size="currentPageSize"
158
+ :page-sizes="pageSizes"
159
+ :total="total"
160
+ :layout="layout"
161
+ :background="background"
162
+ @size-change="handleSizeChange"
163
+ @current-change="handleCurrentChange"
164
+ />
165
+ </div>
166
+ </div>
167
+ </template>
168
+
169
+ <script setup lang="ts">
170
+ import { ref, computed, watch, onMounted, onUnmounted, nextTick, type PropType } from "vue";
171
+ import { Setting, Rank } from "@element-plus/icons-vue";
172
+ import Sortable from "sortablejs";
173
+ import type { TableInstance } from "element-plus";
174
+ import { useTableColumnConfig } from "./useTableColumnConfig";
175
+ import type { AnyRecord, TableColumn, TableColumnConfigProps } from "../../types";
176
+
177
+ defineOptions({ name: "CSimpleTable" });
178
+
179
+ const props = defineProps({
180
+ scrollY: {
181
+ type: [Number, String],
182
+ default: "",
183
+ },
184
+ autoHeight: {
185
+ type: Boolean,
186
+ default: false,
187
+ },
188
+ bottomOffset: {
189
+ type: Number,
190
+ default: 20,
191
+ },
192
+ minHeight: {
193
+ type: Number,
194
+ default: 300,
195
+ },
196
+ api: {
197
+ type: Function as PropType<(params: AnyRecord) => Promise<any>>,
198
+ required: false,
199
+ },
200
+ columns: {
201
+ type: Array as PropType<TableColumn[]>,
202
+ required: true,
203
+ default: () => [],
204
+ },
205
+ emptyCellText: {
206
+ type: String,
207
+ default: "-",
208
+ },
209
+ showPagination: {
210
+ type: Boolean,
211
+ default: true,
212
+ },
213
+ pageSizes: {
214
+ type: Array as PropType<number[]>,
215
+ default: () => [10, 20, 50, 100],
216
+ },
217
+ layout: {
218
+ type: String,
219
+ default: "total, sizes, prev, pager, next, jumper",
220
+ },
221
+ background: {
222
+ type: Boolean,
223
+ default: false,
224
+ },
225
+ dataKey: {
226
+ type: String,
227
+ default: "data",
228
+ },
229
+ totalKey: {
230
+ type: String,
231
+ default: "total",
232
+ },
233
+ immediate: {
234
+ type: Boolean,
235
+ default: true,
236
+ },
237
+ initialPage: {
238
+ type: Number,
239
+ default: 1,
240
+ },
241
+ initialPageSize: {
242
+ type: Number,
243
+ default: 10,
244
+ },
245
+ params: {
246
+ type: Object as PropType<AnyRecord>,
247
+ default: () => ({}),
248
+ },
249
+ border: {
250
+ type: Boolean,
251
+ default: true,
252
+ },
253
+ dataHandler: {
254
+ type: Function as PropType<(data: any[]) => any[]>,
255
+ default: null,
256
+ },
257
+ selectable: {
258
+ type: Function as PropType<(row: any, index: number) => boolean>,
259
+ default: null,
260
+ },
261
+ dragSort: {
262
+ type: Boolean,
263
+ default: false,
264
+ },
265
+ dragOptions: {
266
+ type: Object as PropType<Sortable.Options>,
267
+ default: () => ({}),
268
+ },
269
+ rowKey: {
270
+ type: String,
271
+ default: "id",
272
+ },
273
+ keepSelection: {
274
+ type: Boolean,
275
+ default: true,
276
+ },
277
+ defaultStatusMap: {
278
+ type: Object as PropType<Record<string | number, { text: string; type?: string }>>,
279
+ default: () => ({
280
+ 1: { text: "停用", type: "danger" },
281
+ 0: { text: "启用", type: "success" },
282
+ }),
283
+ },
284
+ tableKey: {
285
+ type: String,
286
+ default: "",
287
+ },
288
+ getColumnConfigApi: {
289
+ type: Function as PropType<() => Promise<any>>,
290
+ default: null,
291
+ },
292
+ saveColumnConfigApi: {
293
+ type: Function as PropType<(cols: string[]) => Promise<any>>,
294
+ default: null,
295
+ },
296
+ });
297
+
298
+ const emit = defineEmits([
299
+ "selection-change",
300
+ "refresh",
301
+ "error",
302
+ "drag-sort",
303
+ "drag-start",
304
+ "drag-end",
305
+ ]);
306
+
307
+ const tableRef = ref<TableInstance | null>(null);
308
+ const tableData = ref<AnyRecord[]>([]);
309
+ const loading = ref<boolean>(false);
310
+ const currentPage = ref<number>(props.initialPage);
311
+ const currentPageSize = ref<number>(props.initialPageSize);
312
+ const total = ref<number>(0);
313
+ const searchParams = ref<AnyRecord>({});
314
+ let sortableInstance: Sortable | null = null;
315
+
316
+ const tableContainerRef = ref<HTMLDivElement | null>(null);
317
+ const autoTableHeight = ref<number | null>(null);
318
+ let resizeObserver: ResizeObserver | null = null;
319
+ let mutationObserver: MutationObserver | null = null;
320
+
321
+ const computedTableHeight = computed(() => {
322
+ if (!props.autoHeight) {
323
+ return props.scrollY || null;
324
+ }
325
+ return autoTableHeight.value;
326
+ });
327
+
328
+ const calculateTableHeight = () => {
329
+ if (!props.autoHeight || !tableContainerRef.value) return;
330
+
331
+ const rect = tableContainerRef.value.getBoundingClientRect();
332
+ const viewportHeight = window.innerHeight;
333
+ const topOffset = rect.top;
334
+
335
+ let reservedSpace = props.bottomOffset;
336
+
337
+ if (props.showPagination) {
338
+ reservedSpace += 60;
339
+ }
340
+
341
+ const hasHeader = tableContainerRef.value.querySelector<HTMLElement>(".table-header");
342
+ if (hasHeader) {
343
+ reservedSpace += hasHeader.offsetHeight;
344
+ }
345
+
346
+ const availableHeight = viewportHeight - topOffset - reservedSpace;
347
+ const targetHeight = Math.max(props.minHeight, availableHeight);
348
+ if (autoTableHeight.value === null || Math.abs(autoTableHeight.value - targetHeight) > 2) {
349
+ autoTableHeight.value = targetHeight;
350
+ }
351
+ };
352
+
353
+ const getNestedValue = (obj: any, path?: string): any => {
354
+ if (!obj || !path) return undefined;
355
+ if (path in obj) return obj[path];
356
+ return path.split(".").reduce((acc, part) => (acc != null ? acc[part] : undefined), obj);
357
+ };
358
+
359
+ const formatCellValue = (row: any, column: TableColumn): string => {
360
+ const val = getNestedValue(row, column.prop);
361
+ if (val === null || val === undefined || val === "") {
362
+ return column.emptyText || props.emptyCellText;
363
+ }
364
+ return String(val);
365
+ };
366
+
367
+ const debounce = <T extends (...args: any[]) => any>(fn: T, delay = 150) => {
368
+ let timer: ReturnType<typeof setTimeout> | null = null;
369
+ const debounced = function (this: any, ...args: Parameters<T>) {
370
+ if (timer) clearTimeout(timer);
371
+ timer = setTimeout(() => {
372
+ fn.apply(this, args);
373
+ }, delay);
374
+ };
375
+ debounced.cancel = () => {
376
+ if (timer) {
377
+ clearTimeout(timer);
378
+ timer = null;
379
+ }
380
+ };
381
+ return debounced;
382
+ };
383
+
384
+ const debouncedCalculate = debounce(calculateTableHeight, 150);
385
+
386
+ const {
387
+ computedColumns,
388
+ settingColumns,
389
+ tempVisibleColKeys,
390
+ popoverRef,
391
+ savingColumns,
392
+ initTableConfig,
393
+ handlePopoverShow,
394
+ handleCancelSettings,
395
+ handleConfirmSettings,
396
+ } = useTableColumnConfig(props as unknown as TableColumnConfigProps, calculateTableHeight);
397
+
398
+ const getStatusType = (value: any, customStatusMap?: AnyRecord): string => {
399
+ const statusMap = customStatusMap || props.defaultStatusMap;
400
+ return statusMap[value]?.type || "info";
401
+ };
402
+
403
+ const getStatusText = (value: any, customStatusMap?: AnyRecord): any => {
404
+ const statusMap = customStatusMap || props.defaultStatusMap;
405
+ return statusMap[value]?.text || value;
406
+ };
407
+
408
+ const fetchData = async (extraParams: AnyRecord = {}) => {
409
+ if (!props.api) {
410
+ return;
411
+ }
412
+
413
+ loading.value = true;
414
+
415
+ try {
416
+ const requestParams: AnyRecord = {
417
+ pageNum: currentPage.value,
418
+ pageSize: currentPageSize.value,
419
+ ...searchParams.value,
420
+ ...props.params,
421
+ ...extraParams,
422
+ };
423
+
424
+ if (!props.showPagination) {
425
+ delete requestParams.pageNum;
426
+ delete requestParams.pageSize;
427
+ }
428
+
429
+ const result = await props.api(requestParams);
430
+ let response: any = null;
431
+
432
+ if (result && result.code === 200) {
433
+ if (Array.isArray(result.data)) {
434
+ response = { data: result.data };
435
+ } else if (result.data) {
436
+ response = result.data;
437
+ } else {
438
+ response = result;
439
+ }
440
+ } else if (Array.isArray(result)) {
441
+ response = { data: result };
442
+ } else if (result?.rows) {
443
+ response = { data: result.rows, total: result.total };
444
+ } else {
445
+ response = result;
446
+ }
447
+
448
+ if (response) {
449
+ tableData.value =
450
+ response[props.dataKey] || response.data?.[props.dataKey] || response.rows || [];
451
+ if (props.dataHandler) {
452
+ tableData.value = props.dataHandler(tableData.value);
453
+ }
454
+ total.value =
455
+ response[props.totalKey] || response.data?.[props.totalKey] || response.total || 0;
456
+ }
457
+ } catch (error) {
458
+ console.error("CSimpleTable fetchData error:", error);
459
+ tableData.value = [];
460
+ total.value = 0;
461
+ emit("error", error);
462
+ } finally {
463
+ loading.value = false;
464
+ nextTick(() => {
465
+ calculateTableHeight();
466
+ if (props.dragSort) {
467
+ initDragSort();
468
+ }
469
+ });
470
+ }
471
+ };
472
+
473
+ const initDragSort = () => {
474
+ if (!tableRef.value) return;
475
+
476
+ if (sortableInstance) {
477
+ sortableInstance.destroy();
478
+ }
479
+
480
+ const el = (tableRef.value.$el as HTMLElement)?.querySelector(".el-table__body-wrapper tbody") as HTMLElement | null;
481
+ if (!el) return;
482
+
483
+ const defaultOptions: Sortable.Options = {
484
+ animation: 150,
485
+ handle: ".drag-handle",
486
+ ghostClass: "sortable-ghost",
487
+ chosenClass: "sortable-chosen",
488
+ dragClass: "sortable-drag",
489
+ forceFallback: true,
490
+ onStart: (evt: Sortable.SortableEvent) => {
491
+ emit("drag-start", {
492
+ oldIndex: evt.oldIndex,
493
+ element: evt.item,
494
+ });
495
+ },
496
+ onEnd: (evt: Sortable.SortableEvent) => {
497
+ const { oldIndex, newIndex } = evt;
498
+
499
+ if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) {
500
+ emit("drag-end", {
501
+ oldIndex,
502
+ newIndex,
503
+ changed: false,
504
+ });
505
+ return;
506
+ }
507
+
508
+ const movedItem = tableData.value.splice(oldIndex, 1)[0];
509
+ tableData.value.splice(newIndex, 0, movedItem);
510
+
511
+ emit("drag-sort", {
512
+ oldIndex,
513
+ newIndex,
514
+ movedItem,
515
+ data: tableData.value,
516
+ });
517
+
518
+ emit("drag-end", {
519
+ oldIndex,
520
+ newIndex,
521
+ changed: true,
522
+ movedItem,
523
+ });
524
+ },
525
+ };
526
+
527
+ const options = { ...defaultOptions, ...props.dragOptions };
528
+ sortableInstance = Sortable.create(el, options);
529
+ };
530
+
531
+ const refresh = () => {
532
+ fetchData();
533
+ emit("refresh");
534
+ };
535
+
536
+ const reset = () => {
537
+ currentPage.value = props.initialPage;
538
+ searchParams.value = {};
539
+ fetchData();
540
+ };
541
+
542
+ const search = (params: AnyRecord = {}) => {
543
+ currentPage.value = props.initialPage;
544
+ searchParams.value = { ...params };
545
+ fetchData();
546
+ };
547
+
548
+ const handleCurrentChange = (val: number) => {
549
+ currentPage.value = val;
550
+ fetchData();
551
+ };
552
+
553
+ const handleSizeChange = (val: number) => {
554
+ currentPageSize.value = val;
555
+ currentPage.value = props.initialPage;
556
+ fetchData();
557
+ };
558
+
559
+ const handleSelectionChange = (selection: any[]) => {
560
+ emit("selection-change", selection);
561
+ };
562
+
563
+ const clearSelection = () => {
564
+ if (tableRef.value) {
565
+ tableRef.value.clearSelection();
566
+ }
567
+ };
568
+
569
+ const toggleRowSelection = (row: any, selected?: boolean) => {
570
+ if (tableRef.value) {
571
+ tableRef.value.toggleRowSelection(row, selected);
572
+ }
573
+ };
574
+
575
+ const toggleAllSelection = () => {
576
+ if (tableRef.value) {
577
+ tableRef.value.toggleAllSelection();
578
+ }
579
+ };
580
+
581
+ const getSelectionRows = () => {
582
+ if (tableRef.value) {
583
+ return tableRef.value.getSelectionRows();
584
+ }
585
+ return [];
586
+ };
587
+
588
+ const setSelectionByKeys = (keys: any[] = []) => {
589
+ if (!tableRef.value || !Array.isArray(keys)) return;
590
+ clearSelection();
591
+ nextTick(() => {
592
+ tableData.value.forEach((row) => {
593
+ if (keys.includes(row[props.rowKey])) {
594
+ tableRef.value?.toggleRowSelection(row, true);
595
+ }
596
+ });
597
+ });
598
+ };
599
+
600
+ watch(
601
+ () => props.params,
602
+ (newParams) => {
603
+ currentPage.value = props.initialPage;
604
+ searchParams.value = { ...newParams };
605
+ fetchData();
606
+ },
607
+ { deep: true }
608
+ );
609
+
610
+ watch(
611
+ () => props.dragSort,
612
+ (newVal) => {
613
+ if (newVal) {
614
+ nextTick(() => {
615
+ initDragSort();
616
+ });
617
+ } else if (sortableInstance) {
618
+ sortableInstance.destroy();
619
+ sortableInstance = null;
620
+ }
621
+ }
622
+ );
623
+
624
+ watch(
625
+ () => props.autoHeight,
626
+ (newVal) => {
627
+ if (newVal) {
628
+ initAutoHeight();
629
+ nextTick(calculateTableHeight);
630
+ } else {
631
+ cleanupAutoHeight();
632
+ }
633
+ }
634
+ );
635
+
636
+ const initAutoHeight = () => {
637
+ if (!props.autoHeight) return;
638
+
639
+ window.addEventListener("resize", debouncedCalculate);
640
+
641
+ if (!resizeObserver) {
642
+ resizeObserver = new ResizeObserver(debouncedCalculate);
643
+ }
644
+
645
+ if (tableContainerRef.value?.parentElement) {
646
+ resizeObserver.observe(tableContainerRef.value.parentElement);
647
+ }
648
+
649
+ const prevSibling = tableContainerRef.value?.previousElementSibling;
650
+ if (prevSibling) {
651
+ resizeObserver.observe(prevSibling);
652
+ }
653
+
654
+ if (!mutationObserver) {
655
+ mutationObserver = new MutationObserver(() => {
656
+ nextTick(debouncedCalculate);
657
+ });
658
+ }
659
+
660
+ if (tableContainerRef.value?.parentElement) {
661
+ mutationObserver.observe(tableContainerRef.value.parentElement, {
662
+ childList: true,
663
+ subtree: true,
664
+ attributes: true,
665
+ attributeFilter: ["style", "class"],
666
+ });
667
+ }
668
+ };
669
+
670
+ const cleanupAutoHeight = () => {
671
+ window.removeEventListener("resize", debouncedCalculate);
672
+ if (resizeObserver) {
673
+ resizeObserver.disconnect();
674
+ }
675
+ if (mutationObserver) {
676
+ mutationObserver.disconnect();
677
+ }
678
+ };
679
+
680
+ defineExpose({
681
+ refresh,
682
+ reset,
683
+ search,
684
+ fetchData,
685
+ clearSelection,
686
+ toggleRowSelection,
687
+ toggleAllSelection,
688
+ getSelectionRows,
689
+ setSelectionByKeys,
690
+ tableData,
691
+ tableRef,
692
+ recalculateHeight: calculateTableHeight,
693
+ });
694
+
695
+ onMounted(() => {
696
+ if (props.immediate) {
697
+ fetchData();
698
+ }
699
+ initTableConfig();
700
+ nextTick(() => {
701
+ calculateTableHeight();
702
+ });
703
+ if (props.autoHeight) {
704
+ initAutoHeight();
705
+ }
706
+ });
707
+
708
+ onUnmounted(() => {
709
+ cleanupAutoHeight();
710
+ debouncedCalculate.cancel();
711
+ if (sortableInstance) {
712
+ sortableInstance.destroy();
713
+ sortableInstance = null;
714
+ }
715
+ });
716
+ </script>
717
+
718
+ <style scoped>
719
+ .c-simple-table {
720
+ width: 100%;
721
+ }
722
+
723
+ .table-header {
724
+ display: flex;
725
+ justify-content: space-between;
726
+ align-items: center;
727
+ margin-bottom: 8px;
728
+ min-height: 32px;
729
+ }
730
+
731
+ .header-left {
732
+ flex: 1;
733
+ display: flex;
734
+ align-items: center;
735
+ gap: 12px;
736
+ }
737
+
738
+ .header-right {
739
+ display: flex;
740
+ align-items: center;
741
+ gap: 12px;
742
+ }
743
+
744
+ .table-pagination {
745
+ margin-top: 16px;
746
+ display: flex;
747
+ justify-content: flex-end;
748
+ }
749
+
750
+ .drag-handle {
751
+ display: inline-flex;
752
+ align-items: center;
753
+ justify-content: center;
754
+ cursor: move;
755
+ padding: 4px;
756
+ transition: all 0.2s;
757
+ }
758
+
759
+ .drag-handle:hover {
760
+ transform: scale(1.1);
761
+ }
762
+
763
+ :deep(.sortable-ghost) {
764
+ opacity: 0.4;
765
+ background: #f5f7fa;
766
+ }
767
+
768
+ :deep(.sortable-chosen) {
769
+ background: #ecf5ff;
770
+ }
771
+
772
+ :deep(.sortable-drag) {
773
+ opacity: 0.8;
774
+ background: #fff;
775
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
776
+ }
777
+
778
+ :deep(.el-table__body-wrapper) {
779
+ overflow-y: auto !important;
780
+ }
781
+ </style>