befly-admin 3.12.5 → 3.12.6

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.
@@ -2,16 +2,16 @@
2
2
  <div class="page-table-detail page-table">
3
3
  <div class="main-tool">
4
4
  <div class="left">
5
- <slot name="toolLeft" :reload="reload" :current-row="currentRow"></slot>
5
+ <slot name="toolLeft" :reload="reload" :current-row="$Data.currentRow"></slot>
6
6
  </div>
7
7
  <div class="right">
8
- <slot name="toolRight" :reload="reload" :current-row="currentRow"></slot>
8
+ <slot name="toolRight" :reload="reload" :current-row="$Data.currentRow"></slot>
9
9
  </div>
10
10
  </div>
11
11
 
12
12
  <div class="main-content">
13
13
  <div class="main-table">
14
- <TTable :data="rows" :columns="columns" :loading="loading" :active-row-keys="activeRowKeys" :row-key="rowKey" :height="tableHeight" active-row-type="single" @active-change="onActiveChange">
14
+ <TTable :data="$Data.rows" :columns="tableColumns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" :row-key="rowKey" :height="tableHeight" active-row-type="single" @active-change="onActiveChange">
15
15
  <template #operation="scope">
16
16
  <slot name="operation" v-bind="buildOperationSlotProps(scope)"></slot>
17
17
  </template>
@@ -23,26 +23,32 @@
23
23
  </div>
24
24
 
25
25
  <div class="main-detail">
26
- <slot name="detail" :row="currentRow" :reload="reload">
27
- <DetailPanel :data="currentRow" :fields="detailFields" />
26
+ <slot name="detail" :row="$Data.currentRow" :reload="reload">
27
+ <DetailPanel :data="$Data.currentRow" :fields="detailFields" />
28
28
  </slot>
29
29
  </div>
30
30
  </div>
31
31
 
32
32
  <div class="main-page">
33
- <TPagination :current-page="pager.currentPage" :page-size="pager.limit" :total="pager.total" :align="paginationAlign" :layout="paginationLayout" @current-change="onPageChange" @page-size-change="onPageSizeChange" />
33
+ <TPagination :current-page="$Data.pager.currentPage" :page-size="$Data.pager.limit" :total="$Data.pager.total" :align="paginationAlign" :layout="paginationLayout" @current-change="onPageChange" @page-size-change="onPageSizeChange" />
34
34
  </div>
35
35
 
36
- <slot name="dialogs" :reload="reload" :current-row="currentRow"></slot>
36
+ <slot name="dialogs" :reload="reload" :current-row="$Data.currentRow"></slot>
37
37
  </div>
38
38
  </template>
39
39
 
40
40
  <script setup lang="ts">
41
- import { computed, onMounted, ref, useSlots } from "vue";
42
- import { DialogPlugin, MessagePlugin, Pagination as TPagination, Table as TTable, type PrimaryTableCol, type TableRowData } from "tdesign-vue-next";
41
+ defineOptions({ name: "PagedTableDetail" });
43
42
 
44
- import DetailPanel from "@/components/DetailPanel.vue";
45
- import { $Http, type HttpApiResponse } from "@/plugins/http";
43
+ type PrimaryTableCol = import("tdesign-vue-next").PrimaryTableCol;
44
+ type TableRowData = import("tdesign-vue-next").TableRowData;
45
+
46
+ type PagedTableCol = PrimaryTableCol & {
47
+ // 页面自定义字段:是否为“详情专属列”(默认 false)
48
+ // - detail: false => 表格展示(同时也会出现在详情里,且顺序靠前)
49
+ // - detail: true => 仅在详情展示(顺序靠后)
50
+ detail?: boolean;
51
+ };
46
52
 
47
53
  type PagerState = {
48
54
  currentPage: number;
@@ -93,8 +99,9 @@ type LoadListOptions = {
93
99
 
94
100
  const props = withDefaults(
95
101
  defineProps<{
96
- columns: PrimaryTableCol[];
102
+ columns: PagedTableCol[];
97
103
  detailFields?: PrimaryTableCol[];
104
+ detailColumns?: PrimaryTableCol[];
98
105
  rowKey?: string;
99
106
  tableHeight?: string;
100
107
  pageSize?: number;
@@ -106,6 +113,7 @@ const props = withDefaults(
106
113
  }>(),
107
114
  {
108
115
  detailFields: undefined,
116
+ detailColumns: undefined,
109
117
  rowKey: "id",
110
118
  tableHeight: "calc(100vh - var(--search-height) - var(--pagination-height) - var(--layout-gap) * 4)",
111
119
  pageSize: 30,
@@ -125,24 +133,117 @@ const emit = defineEmits<{
125
133
 
126
134
  const slots = useSlots();
127
135
 
128
- const rows = ref<TableRowData[]>([]);
129
- const loading = ref<boolean>(false);
130
- const pager = ref<PagerState>({
131
- currentPage: 1,
132
- limit: props.pageSize,
133
- total: 0
136
+ const $Data = $ref({
137
+ rows: [] as TableRowData[],
138
+ loading: false,
139
+ pager: {
140
+ currentPage: 1,
141
+ limit: props.pageSize,
142
+ total: 0
143
+ } as PagerState,
144
+ currentRow: null as TableRowData | null,
145
+ activeRowKeys: [] as Array<string | number>
134
146
  });
135
147
 
136
- const currentRow = ref<TableRowData | null>(null);
137
- const activeRowKeys = ref<Array<string | number>>([]);
138
-
139
148
  let requestSeq = 0;
140
149
 
150
+ function getColKey(col: PrimaryTableCol): string {
151
+ const record = col as unknown as Record<string, unknown>;
152
+ const rawColKey = record["colKey"];
153
+ if (typeof rawColKey === "string") return rawColKey;
154
+ if (typeof rawColKey === "number") return String(rawColKey);
155
+ return "";
156
+ }
157
+
158
+ function filterValidColumns(input: unknown): PrimaryTableCol[] {
159
+ if (!Array.isArray(input)) {
160
+ return [];
161
+ }
162
+
163
+ const out: PrimaryTableCol[] = [];
164
+
165
+ for (const col of input as Array<unknown>) {
166
+ if (!col || typeof col !== "object") {
167
+ continue;
168
+ }
169
+ const key = getColKey(col as PrimaryTableCol);
170
+ if (key.length === 0) {
171
+ continue;
172
+ }
173
+ out.push(col as PrimaryTableCol);
174
+ }
175
+
176
+ return out;
177
+ }
178
+
179
+ function mergeDetailColumns(mainColumns: PrimaryTableCol[], extraColumns: PrimaryTableCol[]): PrimaryTableCol[] {
180
+ const out: PrimaryTableCol[] = [];
181
+ const set = new Set<string>();
182
+
183
+ for (const col of mainColumns) {
184
+ out.push(col);
185
+ const key = getColKey(col);
186
+ if (key) {
187
+ set.add(key);
188
+ }
189
+ }
190
+
191
+ for (const col of extraColumns) {
192
+ const key = getColKey(col);
193
+ if (key && set.has(key)) {
194
+ continue;
195
+ }
196
+ out.push(col);
197
+ if (key) {
198
+ set.add(key);
199
+ }
200
+ }
201
+
202
+ return out;
203
+ }
204
+
205
+ function isDetail(col: PagedTableCol): boolean {
206
+ const record = col as unknown as Record<string, unknown>;
207
+ if (record["detail"] === true) return true;
208
+ return false;
209
+ }
210
+
211
+ const tableColumns = computed(() => {
212
+ const out: PagedTableCol[] = [];
213
+ const cols = filterValidColumns(props.columns) as PagedTableCol[];
214
+ for (const col of cols) {
215
+ if (isDetail(col)) continue;
216
+ out.push(col);
217
+ }
218
+ return out;
219
+ });
220
+
141
221
  const detailFields = computed(() => {
222
+ // A:完全自定义(兼容存量)
142
223
  if (props.detailFields && Array.isArray(props.detailFields)) {
143
- return props.detailFields;
224
+ return filterValidColumns(props.detailFields);
144
225
  }
145
- return props.columns;
226
+
227
+ // B:表格列只展示关键字段,详情列 = columns(关键字段) + detailColumns(其他字段)
228
+ if (props.detailColumns && Array.isArray(props.detailColumns)) {
229
+ const extras = filterValidColumns(props.detailColumns);
230
+ return mergeDetailColumns(tableColumns.value, extras);
231
+ }
232
+
233
+ // C:单列定义:只维护一个 columns:detail 字段为详情专属列,详情 = 表格列(在前) + 详情列(在后)
234
+ const extras: PagedTableCol[] = [];
235
+ const cols = filterValidColumns(props.columns) as PagedTableCol[];
236
+ for (const col of cols) {
237
+ if (!isDetail(col)) continue;
238
+ extras.push(col);
239
+ }
240
+
241
+ if (extras.length > 0) {
242
+ return mergeDetailColumns(tableColumns.value, extras);
243
+ }
244
+
245
+ // D:默认复用表格列
246
+ return tableColumns.value;
146
247
  });
147
248
 
148
249
  const forwardedTableSlotNames = computed(() => {
@@ -172,7 +273,7 @@ const forwardedTableSlotNames = computed(() => {
172
273
  });
173
274
 
174
275
  function setCurrentRow(row: TableRowData | null): void {
175
- currentRow.value = row;
276
+ $Data.currentRow = row;
176
277
  emit("row-change", { row: row });
177
278
  }
178
279
 
@@ -190,7 +291,7 @@ function getRowKeyValue(row: TableRowData): string | number | null {
190
291
  function applyAutoSelection(list: TableRowData[], preferKey: string | number | null): void {
191
292
  if (!Array.isArray(list) || list.length === 0) {
192
293
  setCurrentRow(null);
193
- activeRowKeys.value = [];
294
+ $Data.activeRowKeys = [];
194
295
  return;
195
296
  }
196
297
 
@@ -199,7 +300,7 @@ function applyAutoSelection(list: TableRowData[], preferKey: string | number | n
199
300
  const k = getRowKeyValue(row);
200
301
  if (k === preferKey) {
201
302
  setCurrentRow(row);
202
- activeRowKeys.value = [k];
303
+ $Data.activeRowKeys = [k];
203
304
  return;
204
305
  }
205
306
  }
@@ -210,11 +311,11 @@ function applyAutoSelection(list: TableRowData[], preferKey: string | number | n
210
311
  setCurrentRow(first);
211
312
 
212
313
  if (firstKey === null) {
213
- activeRowKeys.value = [];
314
+ $Data.activeRowKeys = [];
214
315
  return;
215
316
  }
216
317
 
217
- activeRowKeys.value = [firstKey];
318
+ $Data.activeRowKeys = [firstKey];
218
319
  }
219
320
 
220
321
  function getLastPage(total: number, limit: number): number {
@@ -231,22 +332,22 @@ async function loadList(options?: LoadListOptions): Promise<void> {
231
332
  return;
232
333
  }
233
334
 
234
- const preferKey = options?.keepSelection ? (currentRow.value ? getRowKeyValue(currentRow.value) : null) : null;
335
+ const preferKey = options?.keepSelection ? ($Data.currentRow ? getRowKeyValue($Data.currentRow) : null) : null;
235
336
 
236
337
  requestSeq += 1;
237
338
  const seq = requestSeq;
238
339
 
239
- loading.value = true;
340
+ $Data.loading = true;
240
341
  try {
241
342
  const pageKey = listEndpoint.pageKey || "page";
242
343
  const limitKey = listEndpoint.limitKey || "limit";
243
344
 
244
345
  const data: Record<string, unknown> = {};
245
- data[pageKey] = pager.value.currentPage;
246
- data[limitKey] = pager.value.limit;
346
+ data[pageKey] = $Data.pager.currentPage;
347
+ data[limitKey] = $Data.pager.limit;
247
348
 
248
349
  if (listEndpoint.buildData) {
249
- const extra = listEndpoint.buildData({ currentPage: pager.value.currentPage, limit: pager.value.limit });
350
+ const extra = listEndpoint.buildData({ currentPage: $Data.pager.currentPage, limit: $Data.pager.limit });
250
351
  if (extra && typeof extra === "object") {
251
352
  for (const k of Object.keys(extra)) {
252
353
  data[k] = extra[k];
@@ -270,16 +371,16 @@ async function loadList(options?: LoadListOptions): Promise<void> {
270
371
  // B:页码越界修正(删除/过滤等导致 total 变小后,当前页可能已不存在)
271
372
  const allowPageFallback = options?.allowPageFallback !== false;
272
373
  if (allowPageFallback && lists.length === 0 && total > 0) {
273
- const lastPage = getLastPage(total, pager.value.limit);
274
- if (pager.value.currentPage > lastPage) {
275
- pager.value.currentPage = lastPage;
374
+ const lastPage = getLastPage(total, $Data.pager.limit);
375
+ if ($Data.pager.currentPage > lastPage) {
376
+ $Data.pager.currentPage = lastPage;
276
377
  await loadList({ keepSelection: false, allowPageFallback: false });
277
378
  return;
278
379
  }
279
380
  }
280
381
 
281
- rows.value = lists;
282
- pager.value.total = total;
382
+ $Data.rows = lists;
383
+ $Data.pager.total = total;
283
384
 
284
385
  applyAutoSelection(lists, preferKey);
285
386
  emit("loaded", { rows: lists, total: total });
@@ -287,45 +388,45 @@ async function loadList(options?: LoadListOptions): Promise<void> {
287
388
  if (seq !== requestSeq) {
288
389
  return;
289
390
  }
290
- MessagePlugin.error("加载数据失败");
391
+ void showMessageError("加载数据失败");
291
392
  } finally {
292
393
  if (seq === requestSeq) {
293
- loading.value = false;
394
+ $Data.loading = false;
294
395
  }
295
396
  }
296
397
  }
297
398
 
298
399
  async function reload(options?: { keepSelection?: boolean; resetPage?: boolean }): Promise<void> {
299
400
  if (options?.resetPage) {
300
- pager.value.currentPage = 1;
401
+ $Data.pager.currentPage = 1;
301
402
  }
302
403
  await loadList({ keepSelection: options?.keepSelection, allowPageFallback: true });
303
404
  }
304
405
 
305
406
  function onPageChange(info: { currentPage: number }): void {
306
- pager.value.currentPage = info.currentPage;
407
+ $Data.pager.currentPage = info.currentPage;
307
408
  void reload({ keepSelection: true });
308
409
  }
309
410
 
310
411
  function onPageSizeChange(info: { pageSize: number }): void {
311
- pager.value.limit = info.pageSize;
312
- pager.value.currentPage = 1;
412
+ $Data.pager.limit = info.pageSize;
413
+ $Data.pager.currentPage = 1;
313
414
  void reload({ keepSelection: false });
314
415
  }
315
416
 
316
417
  function onActiveChange(value: Array<string | number>, context: { activeRowList?: Array<{ row: TableRowData }> }): void {
317
418
  // 禁止取消高亮:如果新值为空,保持当前选中
318
419
  if (value.length === 0) {
319
- if (activeRowKeys.value.length > 0) {
420
+ if ($Data.activeRowKeys.length > 0) {
320
421
  return;
321
422
  }
322
423
 
323
- activeRowKeys.value = [];
424
+ $Data.activeRowKeys = [];
324
425
  setCurrentRow(null);
325
426
  return;
326
427
  }
327
428
 
328
- activeRowKeys.value = value;
429
+ $Data.activeRowKeys = value;
329
430
 
330
431
  const list = context.activeRowList;
331
432
  if (list && Array.isArray(list) && list.length > 0) {
@@ -336,7 +437,7 @@ function onActiveChange(value: Array<string | number>, context: { activeRowList?
336
437
 
337
438
  // C1:兜底回查(activeRowList 缺失时按 key 在 rows 中回查)
338
439
  const activeKey = value[0];
339
- for (const row of rows.value) {
440
+ for (const row of $Data.rows) {
340
441
  const key = getRowKeyValue(row);
341
442
  if (key === activeKey) {
342
443
  setCurrentRow(row);
@@ -365,7 +466,7 @@ function getDeleteConfirmContent(ep: DeleteEndpoint<TableRowData>, row: TableRow
365
466
  async function deleteRow(row: TableRowData): Promise<void> {
366
467
  const ep = props.endpoints?.delete;
367
468
  if (!ep) {
368
- MessagePlugin.error("未配置删除接口");
469
+ await showMessageError("未配置删除接口");
369
470
  return;
370
471
  }
371
472
 
@@ -374,7 +475,7 @@ async function deleteRow(row: TableRowData): Promise<void> {
374
475
  const rawId = record[idKey];
375
476
 
376
477
  if (rawId === null || rawId === undefined || rawId === "") {
377
- MessagePlugin.error("删除失败:缺少主键");
478
+ await showMessageError("删除失败:缺少主键");
378
479
  return;
379
480
  }
380
481
 
@@ -391,7 +492,7 @@ async function deleteRow(row: TableRowData): Promise<void> {
391
492
  }
392
493
  };
393
494
 
394
- dialog = DialogPlugin.confirm({
495
+ dialog = await openConfirmDialog({
395
496
  header: confirmContent.header,
396
497
  body: confirmContent.body,
397
498
  status: confirmContent.status || "warning",
@@ -420,12 +521,12 @@ async function deleteRow(row: TableRowData): Promise<void> {
420
521
  dropKeyValue: ep.dropKeyValue
421
522
  });
422
523
 
423
- MessagePlugin.success("删除成功");
524
+ await showMessageSuccess("删除成功");
424
525
  destroy();
425
526
  emit("deleted", { row: row });
426
527
  await reload({ keepSelection: true });
427
528
  } catch (error) {
428
- MessagePlugin.error("删除失败");
529
+ await showMessageError("删除失败");
429
530
  } finally {
430
531
  if (dialog && typeof dialog.setConfirmLoading === "function") {
431
532
  dialog.setConfirmLoading(false);
@@ -454,9 +555,9 @@ function buildOperationSlotProps(scope: Record<string, unknown>): Record<string,
454
555
  defineExpose({
455
556
  reload: reload,
456
557
  deleteRow: deleteRow,
457
- rows: rows,
458
- pager: pager,
459
- currentRow: currentRow
558
+ rows: $Data.rows,
559
+ pager: $Data.pager,
560
+ currentRow: $Data.currentRow
460
561
  });
461
562
 
462
563
  onMounted(() => {