befly-admin 3.12.5 → 3.12.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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="right" :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,24 +99,20 @@ type LoadListOptions = {
93
99
 
94
100
  const props = withDefaults(
95
101
  defineProps<{
96
- columns: PrimaryTableCol[];
97
- detailFields?: PrimaryTableCol[];
102
+ columns: PagedTableCol[];
98
103
  rowKey?: string;
99
104
  tableHeight?: string;
100
105
  pageSize?: number;
101
106
  endpoints?: Endpoints<TableRowData>;
102
- paginationAlign?: "left" | "center" | "right";
103
107
  paginationLayout?: string;
104
108
  autoLoad?: boolean;
105
109
  tableSlotNames?: string[];
106
110
  }>(),
107
111
  {
108
- detailFields: undefined,
109
112
  rowKey: "id",
110
113
  tableHeight: "calc(100vh - var(--search-height) - var(--pagination-height) - var(--layout-gap) * 4)",
111
114
  pageSize: 30,
112
115
  endpoints: undefined,
113
- paginationAlign: "right",
114
116
  paginationLayout: "total, prev, pager, next, jumper",
115
117
  autoLoad: true,
116
118
  tableSlotNames: undefined
@@ -125,24 +127,108 @@ const emit = defineEmits<{
125
127
 
126
128
  const slots = useSlots();
127
129
 
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
130
+ const $Data = $ref({
131
+ rows: [] as TableRowData[],
132
+ loading: false,
133
+ pager: {
134
+ currentPage: 1,
135
+ limit: props.pageSize,
136
+ total: 0
137
+ } as PagerState,
138
+ currentRow: null as TableRowData | null,
139
+ activeRowKeys: [] as Array<string | number>
134
140
  });
135
141
 
136
- const currentRow = ref<TableRowData | null>(null);
137
- const activeRowKeys = ref<Array<string | number>>([]);
138
-
139
142
  let requestSeq = 0;
140
143
 
144
+ function getColKey(col: PrimaryTableCol): string {
145
+ const record = col as unknown as Record<string, unknown>;
146
+ const rawColKey = record["colKey"];
147
+ if (typeof rawColKey === "string") return rawColKey;
148
+ if (typeof rawColKey === "number") return String(rawColKey);
149
+ return "";
150
+ }
151
+
152
+ function filterValidColumns(input: unknown): PrimaryTableCol[] {
153
+ if (!Array.isArray(input)) {
154
+ return [];
155
+ }
156
+
157
+ const out: PrimaryTableCol[] = [];
158
+
159
+ for (const col of input as Array<unknown>) {
160
+ if (!col || typeof col !== "object") {
161
+ continue;
162
+ }
163
+ const key = getColKey(col as PrimaryTableCol);
164
+ if (key.length === 0) {
165
+ continue;
166
+ }
167
+ out.push(col as PrimaryTableCol);
168
+ }
169
+
170
+ return out;
171
+ }
172
+
173
+ function mergeDetailColumns(mainColumns: PrimaryTableCol[], extraColumns: PrimaryTableCol[]): PrimaryTableCol[] {
174
+ const out: PrimaryTableCol[] = [];
175
+ const set = new Set<string>();
176
+
177
+ for (const col of mainColumns) {
178
+ out.push(col);
179
+ const key = getColKey(col);
180
+ if (key) {
181
+ set.add(key);
182
+ }
183
+ }
184
+
185
+ for (const col of extraColumns) {
186
+ const key = getColKey(col);
187
+ if (key && set.has(key)) {
188
+ continue;
189
+ }
190
+ out.push(col);
191
+ if (key) {
192
+ set.add(key);
193
+ }
194
+ }
195
+
196
+ return out;
197
+ }
198
+
199
+ function isDetail(col: PagedTableCol): boolean {
200
+ const record = col as unknown as Record<string, unknown>;
201
+ if (record["detail"] === true) return true;
202
+ return false;
203
+ }
204
+
205
+ const tableColumns = computed(() => {
206
+ const out: PagedTableCol[] = [];
207
+ const cols = filterValidColumns(props.columns) as PagedTableCol[];
208
+ for (const col of cols) {
209
+ if (isDetail(col)) continue;
210
+ out.push(col);
211
+ }
212
+ return out;
213
+ });
214
+
141
215
  const detailFields = computed(() => {
142
- if (props.detailFields && Array.isArray(props.detailFields)) {
143
- return props.detailFields;
216
+ // 只维护一个 columns:
217
+ // - detail: false(默认)=> 表格展示(同时也会出现在详情里,且顺序靠前)
218
+ // - detail: true => 仅在详情展示(顺序靠后)
219
+ const extras: PagedTableCol[] = [];
220
+ const cols = filterValidColumns(props.columns) as PagedTableCol[];
221
+ for (const col of cols) {
222
+ if (!isDetail(col)) continue;
223
+ extras.push(col);
224
+ }
225
+
226
+ if (extras.length > 0) {
227
+ return mergeDetailColumns(tableColumns.value, extras);
144
228
  }
145
- return props.columns;
229
+
230
+ // 默认复用表格列
231
+ return tableColumns.value;
146
232
  });
147
233
 
148
234
  const forwardedTableSlotNames = computed(() => {
@@ -172,7 +258,7 @@ const forwardedTableSlotNames = computed(() => {
172
258
  });
173
259
 
174
260
  function setCurrentRow(row: TableRowData | null): void {
175
- currentRow.value = row;
261
+ $Data.currentRow = row;
176
262
  emit("row-change", { row: row });
177
263
  }
178
264
 
@@ -190,7 +276,7 @@ function getRowKeyValue(row: TableRowData): string | number | null {
190
276
  function applyAutoSelection(list: TableRowData[], preferKey: string | number | null): void {
191
277
  if (!Array.isArray(list) || list.length === 0) {
192
278
  setCurrentRow(null);
193
- activeRowKeys.value = [];
279
+ $Data.activeRowKeys = [];
194
280
  return;
195
281
  }
196
282
 
@@ -199,7 +285,7 @@ function applyAutoSelection(list: TableRowData[], preferKey: string | number | n
199
285
  const k = getRowKeyValue(row);
200
286
  if (k === preferKey) {
201
287
  setCurrentRow(row);
202
- activeRowKeys.value = [k];
288
+ $Data.activeRowKeys = [k];
203
289
  return;
204
290
  }
205
291
  }
@@ -210,11 +296,11 @@ function applyAutoSelection(list: TableRowData[], preferKey: string | number | n
210
296
  setCurrentRow(first);
211
297
 
212
298
  if (firstKey === null) {
213
- activeRowKeys.value = [];
299
+ $Data.activeRowKeys = [];
214
300
  return;
215
301
  }
216
302
 
217
- activeRowKeys.value = [firstKey];
303
+ $Data.activeRowKeys = [firstKey];
218
304
  }
219
305
 
220
306
  function getLastPage(total: number, limit: number): number {
@@ -231,22 +317,22 @@ async function loadList(options?: LoadListOptions): Promise<void> {
231
317
  return;
232
318
  }
233
319
 
234
- const preferKey = options?.keepSelection ? (currentRow.value ? getRowKeyValue(currentRow.value) : null) : null;
320
+ const preferKey = options?.keepSelection ? ($Data.currentRow ? getRowKeyValue($Data.currentRow) : null) : null;
235
321
 
236
322
  requestSeq += 1;
237
323
  const seq = requestSeq;
238
324
 
239
- loading.value = true;
325
+ $Data.loading = true;
240
326
  try {
241
327
  const pageKey = listEndpoint.pageKey || "page";
242
328
  const limitKey = listEndpoint.limitKey || "limit";
243
329
 
244
330
  const data: Record<string, unknown> = {};
245
- data[pageKey] = pager.value.currentPage;
246
- data[limitKey] = pager.value.limit;
331
+ data[pageKey] = $Data.pager.currentPage;
332
+ data[limitKey] = $Data.pager.limit;
247
333
 
248
334
  if (listEndpoint.buildData) {
249
- const extra = listEndpoint.buildData({ currentPage: pager.value.currentPage, limit: pager.value.limit });
335
+ const extra = listEndpoint.buildData({ currentPage: $Data.pager.currentPage, limit: $Data.pager.limit });
250
336
  if (extra && typeof extra === "object") {
251
337
  for (const k of Object.keys(extra)) {
252
338
  data[k] = extra[k];
@@ -270,16 +356,16 @@ async function loadList(options?: LoadListOptions): Promise<void> {
270
356
  // B:页码越界修正(删除/过滤等导致 total 变小后,当前页可能已不存在)
271
357
  const allowPageFallback = options?.allowPageFallback !== false;
272
358
  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;
359
+ const lastPage = getLastPage(total, $Data.pager.limit);
360
+ if ($Data.pager.currentPage > lastPage) {
361
+ $Data.pager.currentPage = lastPage;
276
362
  await loadList({ keepSelection: false, allowPageFallback: false });
277
363
  return;
278
364
  }
279
365
  }
280
366
 
281
- rows.value = lists;
282
- pager.value.total = total;
367
+ $Data.rows = lists;
368
+ $Data.pager.total = total;
283
369
 
284
370
  applyAutoSelection(lists, preferKey);
285
371
  emit("loaded", { rows: lists, total: total });
@@ -287,45 +373,45 @@ async function loadList(options?: LoadListOptions): Promise<void> {
287
373
  if (seq !== requestSeq) {
288
374
  return;
289
375
  }
290
- MessagePlugin.error("加载数据失败");
376
+ void showMessageError("加载数据失败");
291
377
  } finally {
292
378
  if (seq === requestSeq) {
293
- loading.value = false;
379
+ $Data.loading = false;
294
380
  }
295
381
  }
296
382
  }
297
383
 
298
384
  async function reload(options?: { keepSelection?: boolean; resetPage?: boolean }): Promise<void> {
299
385
  if (options?.resetPage) {
300
- pager.value.currentPage = 1;
386
+ $Data.pager.currentPage = 1;
301
387
  }
302
388
  await loadList({ keepSelection: options?.keepSelection, allowPageFallback: true });
303
389
  }
304
390
 
305
391
  function onPageChange(info: { currentPage: number }): void {
306
- pager.value.currentPage = info.currentPage;
392
+ $Data.pager.currentPage = info.currentPage;
307
393
  void reload({ keepSelection: true });
308
394
  }
309
395
 
310
396
  function onPageSizeChange(info: { pageSize: number }): void {
311
- pager.value.limit = info.pageSize;
312
- pager.value.currentPage = 1;
397
+ $Data.pager.limit = info.pageSize;
398
+ $Data.pager.currentPage = 1;
313
399
  void reload({ keepSelection: false });
314
400
  }
315
401
 
316
402
  function onActiveChange(value: Array<string | number>, context: { activeRowList?: Array<{ row: TableRowData }> }): void {
317
403
  // 禁止取消高亮:如果新值为空,保持当前选中
318
404
  if (value.length === 0) {
319
- if (activeRowKeys.value.length > 0) {
405
+ if ($Data.activeRowKeys.length > 0) {
320
406
  return;
321
407
  }
322
408
 
323
- activeRowKeys.value = [];
409
+ $Data.activeRowKeys = [];
324
410
  setCurrentRow(null);
325
411
  return;
326
412
  }
327
413
 
328
- activeRowKeys.value = value;
414
+ $Data.activeRowKeys = value;
329
415
 
330
416
  const list = context.activeRowList;
331
417
  if (list && Array.isArray(list) && list.length > 0) {
@@ -336,7 +422,7 @@ function onActiveChange(value: Array<string | number>, context: { activeRowList?
336
422
 
337
423
  // C1:兜底回查(activeRowList 缺失时按 key 在 rows 中回查)
338
424
  const activeKey = value[0];
339
- for (const row of rows.value) {
425
+ for (const row of $Data.rows) {
340
426
  const key = getRowKeyValue(row);
341
427
  if (key === activeKey) {
342
428
  setCurrentRow(row);
@@ -365,7 +451,7 @@ function getDeleteConfirmContent(ep: DeleteEndpoint<TableRowData>, row: TableRow
365
451
  async function deleteRow(row: TableRowData): Promise<void> {
366
452
  const ep = props.endpoints?.delete;
367
453
  if (!ep) {
368
- MessagePlugin.error("未配置删除接口");
454
+ await showMessageError("未配置删除接口");
369
455
  return;
370
456
  }
371
457
 
@@ -374,7 +460,7 @@ async function deleteRow(row: TableRowData): Promise<void> {
374
460
  const rawId = record[idKey];
375
461
 
376
462
  if (rawId === null || rawId === undefined || rawId === "") {
377
- MessagePlugin.error("删除失败:缺少主键");
463
+ await showMessageError("删除失败:缺少主键");
378
464
  return;
379
465
  }
380
466
 
@@ -391,7 +477,7 @@ async function deleteRow(row: TableRowData): Promise<void> {
391
477
  }
392
478
  };
393
479
 
394
- dialog = DialogPlugin.confirm({
480
+ dialog = await openConfirmDialog({
395
481
  header: confirmContent.header,
396
482
  body: confirmContent.body,
397
483
  status: confirmContent.status || "warning",
@@ -420,12 +506,12 @@ async function deleteRow(row: TableRowData): Promise<void> {
420
506
  dropKeyValue: ep.dropKeyValue
421
507
  });
422
508
 
423
- MessagePlugin.success("删除成功");
509
+ await showMessageSuccess("删除成功");
424
510
  destroy();
425
511
  emit("deleted", { row: row });
426
512
  await reload({ keepSelection: true });
427
513
  } catch (error) {
428
- MessagePlugin.error("删除失败");
514
+ await showMessageError("删除失败");
429
515
  } finally {
430
516
  if (dialog && typeof dialog.setConfirmLoading === "function") {
431
517
  dialog.setConfirmLoading(false);
@@ -454,9 +540,9 @@ function buildOperationSlotProps(scope: Record<string, unknown>): Record<string,
454
540
  defineExpose({
455
541
  reload: reload,
456
542
  deleteRow: deleteRow,
457
- rows: rows,
458
- pager: pager,
459
- currentRow: currentRow
543
+ rows: $Data.rows,
544
+ pager: $Data.pager,
545
+ currentRow: $Data.currentRow
460
546
  });
461
547
 
462
548
  onMounted(() => {