befly-admin-ui 1.10.0 → 1.10.2

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 (34) hide show
  1. package/components/detailPanel.vue +52 -54
  2. package/components/pageDialog.vue +131 -117
  3. package/components/pageTableDetail.vue +381 -398
  4. package/layouts/default.vue +212 -215
  5. package/package.json +2 -2
  6. package/views/config/dict/components/edit.vue +83 -60
  7. package/views/config/dict/index.vue +53 -68
  8. package/views/config/dictType/components/edit.vue +81 -57
  9. package/views/config/dictType/index.vue +39 -51
  10. package/views/config/system/components/edit.vue +97 -86
  11. package/views/config/system/index.vue +37 -51
  12. package/views/index/components/addonList.vue +14 -11
  13. package/views/index/components/environmentInfo.vue +25 -22
  14. package/views/index/components/operationLogs.vue +20 -16
  15. package/views/index/components/performanceMetrics.vue +24 -21
  16. package/views/index/components/serviceStatus.vue +26 -22
  17. package/views/index/components/systemNotifications.vue +24 -19
  18. package/views/index/components/systemOverview.vue +368 -385
  19. package/views/index/components/systemResources.vue +22 -19
  20. package/views/index/components/userInfo.vue +56 -56
  21. package/views/log/email/index.vue +99 -100
  22. package/views/log/error/index.vue +112 -130
  23. package/views/log/login/index.vue +17 -28
  24. package/views/log/operate/index.vue +33 -46
  25. package/views/login_1/index.vue +50 -32
  26. package/views/people/admin/components/edit.vue +1 -1
  27. package/views/people/admin/index.vue +28 -29
  28. package/views/permission/api/index.vue +46 -58
  29. package/views/permission/menu/index.vue +46 -52
  30. package/views/permission/role/components/api.vue +143 -144
  31. package/views/permission/role/components/edit.vue +74 -52
  32. package/views/permission/role/components/menu.vue +119 -121
  33. package/views/permission/role/index.vue +49 -63
  34. package/views/resource/gallery/index.vue +78 -88
@@ -1,42 +1,42 @@
1
1
  <template>
2
- <div :class="['page-table-detail', 'page-table', { 'page-table--with-pagination': props.isPagination }]">
3
- <div class="main-tool">
2
+ <div :class="['page-table-detail', 'page-table', { 'page-table--with-pagination': $Prop.isPagination }]">
3
+ <div v-if="$Computed.hasToolBar" class="main-tool">
4
4
  <div class="left">
5
- <slot name="toolLeft" :reload="reload" :current-row="$Data.currentRow"></slot>
5
+ <slot name="toolLeft" :reload="$Method.reload" :current-row="$Data.currentRow"></slot>
6
6
  </div>
7
7
  <div class="right">
8
- <slot name="toolRight" :reload="reload" :current-row="$Data.currentRow"></slot>
8
+ <slot name="toolRight" :reload="$Method.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="$Data.rows" :columns="tableColumns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" :row-key="rowKey" :height="resolvedTableHeight" active-row-type="single" @active-change="onActiveChange">
14
+ <TTable :data="$Data.rows" :columns="$Computed.tableColumns" :loading="$Data.loading" :active-row-keys="$Data.activeRowKeys" :row-key="$Prop.rowKey" :height="$Computed.resolvedTableHeight" active-row-type="single" @active-change="$Method.onActiveChange">
15
15
  <template #operation="scope">
16
- <slot name="operation" v-bind="buildOperationSlotProps(scope)"></slot>
16
+ <slot name="operation" v-bind="$Method.buildOperationSlotProps(scope)"></slot>
17
17
  </template>
18
18
 
19
- <template v-for="name in tableRenderSlotNames" :key="name" v-slot:[name]="scope">
19
+ <template v-for="name in $Computed.tableRenderSlotNames" :key="name" v-slot:[name]="scope">
20
20
  <slot v-if="$slots[name]" :name="name" v-bind="scope"></slot>
21
21
  <template v-else>
22
- {{ formatTableCell(scope.row, name) }}
22
+ {{ $Method.formatTableCell(scope.row, name) }}
23
23
  </template>
24
24
  </template>
25
25
  </TTable>
26
26
  </div>
27
27
 
28
28
  <div class="main-detail">
29
- <slot name="detail" :row="$Data.currentRow" :reload="reload">
30
- <DetailPanel :data="$Data.currentRow" :fields="detailFields" />
29
+ <slot name="detail" :row="$Data.currentRow" :reload="$Method.reload">
30
+ <DetailPanel :data="$Data.currentRow" :fields="$Computed.detailFields" />
31
31
  </slot>
32
32
  </div>
33
33
  </div>
34
34
 
35
- <div v-if="props.isPagination" class="main-page">
36
- <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" />
35
+ <div v-if="$Prop.isPagination" class="main-page">
36
+ <TPagination :current-page="$Data.pager.currentPage" :page-size="$Data.pager.limit" :total="$Data.pager.total" align="right" :layout="$Prop.paginationLayout" @current-change="$Method.onPageChange" @page-size-change="$Method.onPageSizeChange" />
37
37
  </div>
38
38
 
39
- <slot name="dialogs" :reload="reload" :current-row="$Data.currentRow"></slot>
39
+ <slot name="dialogs" :reload="$Method.reload" :current-row="$Data.currentRow"></slot>
40
40
  </div>
41
41
  </template>
42
42
 
@@ -44,10 +44,10 @@
44
44
  import { computed, onMounted, reactive, useSlots } from "vue";
45
45
  import { DialogPlugin, MessagePlugin, Pagination as TPagination, Table as TTable } from "tdesign-vue-next";
46
46
  import DetailPanel from "./detailPanel.vue";
47
- import { $Http } from "@/plugins/http";
47
+ import { $Http } from "@/plugins/http.js";
48
48
  import { formatFieldValue } from "../utils/formatFieldValue.js";
49
49
 
50
- const props = defineProps({
50
+ const $Prop = defineProps({
51
51
  columns: {
52
52
  type: Array,
53
53
  required: true
@@ -86,481 +86,464 @@ const props = defineProps({
86
86
  }
87
87
  });
88
88
 
89
- const emit = defineEmits(["loaded", "row-change", "deleted"]);
89
+ const $Emit = defineEmits(["loaded", "row-change", "deleted"]);
90
90
 
91
- const slots = useSlots();
91
+ const $From = {
92
+ slots: useSlots(),
93
+ requestSeq: 0
94
+ };
95
+
96
+ const $Const = {
97
+ reservedSlotNames: ["toolLeft", "toolRight", "detail", "dialogs", "operation", "default"]
98
+ };
92
99
 
93
100
  const $Data = reactive({
94
101
  rows: [],
95
102
  loading: false,
96
103
  pager: {
97
104
  currentPage: 1,
98
- limit: props.pageSize,
105
+ limit: $Prop.pageSize,
99
106
  total: 0
100
107
  },
101
108
  currentRow: null,
102
109
  activeRowKeys: []
103
110
  });
104
111
 
105
- let requestSeq = 0;
106
-
107
- function getColKey(col) {
108
- const record = col;
109
- const rawColKey = record["colKey"];
110
- if (typeof rawColKey === "string") return rawColKey;
111
- if (typeof rawColKey === "number") return String(rawColKey);
112
- return "";
113
- }
114
-
115
- function filterValidColumns(input) {
116
- if (!Array.isArray(input)) {
117
- return [];
118
- }
112
+ const $Method = {
113
+ getColKey(col) {
114
+ const record = col;
115
+ const rawColKey = record["colKey"];
116
+ if (typeof rawColKey === "string") return rawColKey;
117
+ if (typeof rawColKey === "number") return String(rawColKey);
118
+ return "";
119
+ },
120
+ filterValidColumns(input) {
121
+ if (!Array.isArray(input)) {
122
+ return [];
123
+ }
119
124
 
120
- const out = [];
125
+ const out = [];
121
126
 
122
- for (const col of input) {
123
- if (!col || typeof col !== "object") {
124
- continue;
125
- }
126
- const key = getColKey(col);
127
- if (key.length === 0) {
128
- continue;
127
+ for (const col of input) {
128
+ if (!col || typeof col !== "object") {
129
+ continue;
130
+ }
131
+ const key = $Method.getColKey(col);
132
+ if (key.length === 0) {
133
+ continue;
134
+ }
135
+ out.push(col);
129
136
  }
130
- out.push(col);
131
- }
132
-
133
- return out;
134
- }
135
137
 
136
- function mergeDetailColumns(mainColumns, extraColumns) {
137
- const out = [];
138
- const set = new Set();
138
+ return out;
139
+ },
140
+ mergeDetailColumns(mainColumns, extraColumns) {
141
+ const out = [];
142
+ const set = new Set();
139
143
 
140
- for (const col of mainColumns) {
141
- out.push(col);
142
- const key = getColKey(col);
143
- if (key) {
144
- set.add(key);
144
+ for (const col of mainColumns) {
145
+ out.push(col);
146
+ const key = $Method.getColKey(col);
147
+ if (key) {
148
+ set.add(key);
149
+ }
145
150
  }
146
- }
147
151
 
148
- for (const col of extraColumns) {
149
- const key = getColKey(col);
150
- if (key && set.has(key)) {
151
- continue;
152
- }
153
- out.push(col);
154
- if (key) {
155
- set.add(key);
152
+ for (const col of extraColumns) {
153
+ const key = $Method.getColKey(col);
154
+ if (key && set.has(key)) {
155
+ continue;
156
+ }
157
+ out.push(col);
158
+ if (key) {
159
+ set.add(key);
160
+ }
156
161
  }
157
- }
158
162
 
159
- return out;
160
- }
161
-
162
- const columnsMeta = computed(() => {
163
- // 只维护一个 columns:
164
- // - detail: false(默认)=> 表格展示(同时也会出现在详情里,且顺序靠前)
165
- // - detail: true => 仅在详情展示(顺序靠后)
166
- const tableColumns = [];
167
- const extraDetailColumns = [];
168
- const tableColumnMap = {};
169
- const formatKeys = [];
170
-
171
- const cols = filterValidColumns(props.columns);
172
- for (const col of cols) {
173
- if (col?.detail === true) {
174
- extraDetailColumns.push(col);
175
- continue;
163
+ return out;
164
+ },
165
+ formatTableCell(row, colKey) {
166
+ const record = row && typeof row === "object" ? row : {};
167
+ const value = record[colKey];
168
+ const field = $Computed.columnsMeta.tableColumnMap[colKey] || {};
169
+ return formatFieldValue(value, field);
170
+ },
171
+ setCurrentRow(row) {
172
+ $Data.currentRow = row;
173
+ $Emit("row-change", { row: row });
174
+ },
175
+ getRowKeyValue(row) {
176
+ const key = $Prop.rowKey;
177
+ if (!key) return null;
178
+
179
+ const record = row;
180
+ const raw = record[key];
181
+ if (typeof raw === "string") return raw;
182
+ if (typeof raw === "number") return raw;
183
+ return null;
184
+ },
185
+ applyAutoSelection(list, preferKey) {
186
+ if (!Array.isArray(list) || list.length === 0) {
187
+ $Method.setCurrentRow(null);
188
+ $Data.activeRowKeys = [];
189
+ return;
176
190
  }
177
191
 
178
- tableColumns.push(col);
179
-
180
- const key = getColKey(col);
181
- if (!key) {
182
- continue;
192
+ if (preferKey !== null) {
193
+ for (const row of list) {
194
+ const key = $Method.getRowKeyValue(row);
195
+ if (key === preferKey) {
196
+ $Method.setCurrentRow(row);
197
+ $Data.activeRowKeys = [key];
198
+ return;
199
+ }
200
+ }
183
201
  }
184
202
 
185
- tableColumnMap[key] = col;
203
+ const first = list[0];
204
+ const firstKey = $Method.getRowKeyValue(first);
205
+ $Method.setCurrentRow(first);
186
206
 
187
- const format = col.format;
188
- if ((typeof format === "string" && format.trim().length > 0) || typeof format === "function") {
189
- formatKeys.push(key);
207
+ if (firstKey === null) {
208
+ $Data.activeRowKeys = [];
209
+ return;
190
210
  }
191
- }
192
211
 
193
- const detailFields = extraDetailColumns.length > 0 ? mergeDetailColumns(tableColumns, extraDetailColumns) : tableColumns;
212
+ $Data.activeRowKeys = [firstKey];
213
+ },
214
+ getLastPage(total, limit) {
215
+ if (total <= 0) return 1;
216
+ if (limit <= 0) return 1;
217
+ const pages = Math.ceil(total / limit);
218
+ if (!Number.isFinite(pages) || pages <= 0) return 1;
219
+ return pages;
220
+ },
221
+ async loadList(options) {
222
+ const listEndpoint = $Prop.endpoints?.list;
223
+ if (!listEndpoint) {
224
+ return;
225
+ }
194
226
 
195
- return {
196
- tableColumns: tableColumns,
197
- detailFields: detailFields,
198
- tableColumnMap: tableColumnMap,
199
- formatKeys: formatKeys
200
- };
201
- });
227
+ const preferKey = options?.keepSelection ? ($Data.currentRow ? $Method.getRowKeyValue($Data.currentRow) : null) : null;
202
228
 
203
- const tableColumns = computed(() => columnsMeta.value.tableColumns);
229
+ $From.requestSeq += 1;
230
+ const seq = $From.requestSeq;
204
231
 
205
- const detailFields = computed(() => columnsMeta.value.detailFields);
232
+ $Data.loading = true;
233
+ try {
234
+ const pageKey = listEndpoint.pageKey || "page";
235
+ const limitKey = listEndpoint.limitKey || "limit";
206
236
 
207
- const resolvedTableHeight = computed(() => {
208
- if (props.isPagination) {
209
- return props.tableHeight;
210
- }
211
- return "calc(100vh - var(--search-height) - var(--layout-gap) * 2)";
212
- });
237
+ const data = {};
238
+ data[pageKey] = $Data.pager.currentPage;
239
+ data[limitKey] = $Data.pager.limit;
213
240
 
214
- const forwardedTableSlotNames = computed(() => {
215
- if (Array.isArray(props.tableSlotNames) && props.tableSlotNames.length > 0) {
216
- const out = [];
217
- const set = new Set();
218
- for (const name of props.tableSlotNames) {
219
- if (typeof name !== "string") continue;
220
- const trimmed = name.trim();
221
- if (trimmed.length === 0) continue;
222
- if (set.has(trimmed)) continue;
223
- set.add(trimmed);
224
- out.push(trimmed);
225
- }
226
- return out;
227
- }
241
+ if (listEndpoint.buildData) {
242
+ const extra = listEndpoint.buildData({
243
+ currentPage: $Data.pager.currentPage,
244
+ limit: $Data.pager.limit
245
+ });
246
+ if (extra && typeof extra === "object") {
247
+ for (const key of Object.keys(extra)) {
248
+ data[key] = extra[key];
249
+ }
250
+ }
251
+ }
228
252
 
229
- const names = [];
230
- const reserved = ["toolLeft", "toolRight", "detail", "dialogs", "operation", "default"]; // operation 单独处理
253
+ const res = await $Http(listEndpoint.path, data, listEndpoint.dropValues, listEndpoint.dropKeyValue);
231
254
 
232
- for (const key of Object.keys(slots)) {
233
- if (reserved.includes(key)) continue;
234
- names.push(key);
235
- }
255
+ if (seq !== $From.requestSeq) {
256
+ return;
257
+ }
236
258
 
237
- return names;
238
- });
259
+ const lists = Array.isArray(res?.data?.lists) ? res.data.lists : [];
260
+ const total = typeof res?.data?.total === "number" ? res.data.total : 0;
261
+
262
+ const allowPageFallback = options?.allowPageFallback !== false;
263
+ if (allowPageFallback && lists.length === 0 && total > 0) {
264
+ const lastPage = $Method.getLastPage(total, $Data.pager.limit);
265
+ if ($Data.pager.currentPage > lastPage) {
266
+ $Data.pager.currentPage = lastPage;
267
+ await $Method.loadList({
268
+ keepSelection: false,
269
+ allowPageFallback: false
270
+ });
271
+ return;
272
+ }
273
+ }
239
274
 
240
- const tableRenderSlotNames = computed(() => {
241
- const out = [];
242
- const set = new Set();
275
+ $Data.rows = lists;
276
+ $Data.pager.total = total;
243
277
 
244
- for (const name of forwardedTableSlotNames.value) {
245
- if (!name || set.has(name)) {
246
- continue;
278
+ $Method.applyAutoSelection(lists, preferKey);
279
+ $Emit("loaded", { rows: lists, total: total });
280
+ } catch (error) {
281
+ if (seq !== $From.requestSeq) {
282
+ return;
283
+ }
284
+ MessagePlugin.error("加载数据失败");
285
+ } finally {
286
+ if (seq === $From.requestSeq) {
287
+ $Data.loading = false;
288
+ }
247
289
  }
248
- set.add(name);
249
- out.push(name);
250
- }
251
-
252
- for (const key of columnsMeta.value.formatKeys) {
253
- if (!key || set.has(key)) {
254
- continue;
290
+ },
291
+ async reload(options) {
292
+ if (options?.resetPage) {
293
+ $Data.pager.currentPage = 1;
255
294
  }
256
- set.add(key);
257
- out.push(key);
258
- }
259
-
260
- return out;
261
- });
262
-
263
- function formatTableCell(row, colKey) {
264
- const record = row && typeof row === "object" ? row : {};
265
- const value = record[colKey];
266
- const field = columnsMeta.value.tableColumnMap[colKey] || {};
267
- return formatFieldValue(value, field);
268
- }
269
-
270
- function setCurrentRow(row) {
271
- $Data.currentRow = row;
272
- emit("row-change", { row: row });
273
- }
274
-
275
- function getRowKeyValue(row) {
276
- const key = props.rowKey;
277
- if (!key) return null;
278
-
279
- const record = row;
280
- const raw = record[key];
281
- if (typeof raw === "string") return raw;
282
- if (typeof raw === "number") return raw;
283
- return null;
284
- }
285
-
286
- function applyAutoSelection(list, preferKey) {
287
- if (!Array.isArray(list) || list.length === 0) {
288
- setCurrentRow(null);
289
- $Data.activeRowKeys = [];
290
- return;
291
- }
292
-
293
- if (preferKey !== null) {
294
- for (const row of list) {
295
- const k = getRowKeyValue(row);
296
- if (k === preferKey) {
297
- setCurrentRow(row);
298
- $Data.activeRowKeys = [k];
295
+ await $Method.loadList({
296
+ keepSelection: options?.keepSelection,
297
+ allowPageFallback: true
298
+ });
299
+ },
300
+ onPageChange(info) {
301
+ $Data.pager.currentPage = info.currentPage;
302
+ $Method.reload({ keepSelection: true });
303
+ },
304
+ onPageSizeChange(info) {
305
+ $Data.pager.limit = info.pageSize;
306
+ $Data.pager.currentPage = 1;
307
+ $Method.reload({ keepSelection: false });
308
+ },
309
+ onActiveChange(value, context) {
310
+ if (value.length === 0) {
311
+ if ($Data.activeRowKeys.length > 0) {
299
312
  return;
300
313
  }
301
- }
302
- }
303
-
304
- const first = list[0];
305
- const firstKey = getRowKeyValue(first);
306
- setCurrentRow(first);
307
-
308
- if (firstKey === null) {
309
- $Data.activeRowKeys = [];
310
- return;
311
- }
312
-
313
- $Data.activeRowKeys = [firstKey];
314
- }
315
-
316
- function getLastPage(total, limit) {
317
- if (total <= 0) return 1;
318
- if (limit <= 0) return 1;
319
- const pages = Math.ceil(total / limit);
320
- if (!Number.isFinite(pages) || pages <= 0) return 1;
321
- return pages;
322
- }
323
-
324
- async function loadList(options) {
325
- const listEndpoint = props.endpoints?.list;
326
- if (!listEndpoint) {
327
- return;
328
- }
329
-
330
- const preferKey = options?.keepSelection ? ($Data.currentRow ? getRowKeyValue($Data.currentRow) : null) : null;
331
-
332
- requestSeq += 1;
333
- const seq = requestSeq;
334
-
335
- $Data.loading = true;
336
- try {
337
- const pageKey = listEndpoint.pageKey || "page";
338
- const limitKey = listEndpoint.limitKey || "limit";
339
314
 
340
- const data = {};
341
- data[pageKey] = $Data.pager.currentPage;
342
- data[limitKey] = $Data.pager.limit;
343
-
344
- if (listEndpoint.buildData) {
345
- const extra = listEndpoint.buildData({
346
- currentPage: $Data.pager.currentPage,
347
- limit: $Data.pager.limit
348
- });
349
- if (extra && typeof extra === "object") {
350
- for (const k of Object.keys(extra)) {
351
- data[k] = extra[k];
352
- }
353
- }
315
+ $Data.activeRowKeys = [];
316
+ $Method.setCurrentRow(null);
317
+ return;
354
318
  }
355
319
 
356
- const res = await $Http(listEndpoint.path, data, listEndpoint.dropValues, listEndpoint.dropKeyValue);
320
+ $Data.activeRowKeys = value;
357
321
 
358
- // 并发保护:旧请求返回后不应覆盖新请求的状态
359
- if (seq !== requestSeq) {
322
+ const list = context.activeRowList;
323
+ if (list && Array.isArray(list) && list.length > 0) {
324
+ const row = list[0]?.row || null;
325
+ $Method.setCurrentRow(row);
360
326
  return;
361
327
  }
362
328
 
363
- const lists = Array.isArray(res?.data?.lists) ? res.data.lists : [];
364
- const total = typeof res?.data?.total === "number" ? res.data.total : 0;
365
-
366
- // B:页码越界修正(删除/过滤等导致 total 变小后,当前页可能已不存在)
367
- const allowPageFallback = options?.allowPageFallback !== false;
368
- if (allowPageFallback && lists.length === 0 && total > 0) {
369
- const lastPage = getLastPage(total, $Data.pager.limit);
370
- if ($Data.pager.currentPage > lastPage) {
371
- $Data.pager.currentPage = lastPage;
372
- await loadList({
373
- keepSelection: false,
374
- allowPageFallback: false
375
- });
329
+ const activeKey = value[0];
330
+ for (const row of $Data.rows) {
331
+ const key = $Method.getRowKeyValue(row);
332
+ if (key === activeKey) {
333
+ $Method.setCurrentRow(row);
376
334
  return;
377
335
  }
378
336
  }
337
+ },
338
+ getDeleteConfirmContent(ep, row) {
339
+ const confirm = ep.confirm;
340
+ if (!confirm) {
341
+ return {
342
+ header: "确认删除",
343
+ body: "确认删除该记录吗?",
344
+ confirmBtn: "删除"
345
+ };
346
+ }
379
347
 
380
- $Data.rows = lists;
381
- $Data.pager.total = total;
348
+ if (typeof confirm === "function") {
349
+ return confirm(row);
350
+ }
382
351
 
383
- applyAutoSelection(lists, preferKey);
384
- emit("loaded", { rows: lists, total: total });
385
- } catch (error) {
386
- if (seq !== requestSeq) {
352
+ return confirm;
353
+ },
354
+ async deleteRow(row) {
355
+ const ep = $Prop.endpoints?.delete;
356
+ if (!ep) {
357
+ MessagePlugin.error("未配置删除接口");
387
358
  return;
388
359
  }
389
- MessagePlugin.error("加载数据失败");
390
- } finally {
391
- if (seq === requestSeq) {
392
- $Data.loading = false;
393
- }
394
- }
395
- }
396
360
 
397
- async function reload(options) {
398
- if (options?.resetPage) {
399
- $Data.pager.currentPage = 1;
400
- }
401
- await loadList({
402
- keepSelection: options?.keepSelection,
403
- allowPageFallback: true
404
- });
405
- }
406
-
407
- function onPageChange(info) {
408
- $Data.pager.currentPage = info.currentPage;
409
- reload({ keepSelection: true });
410
- }
411
-
412
- function onPageSizeChange(info) {
413
- $Data.pager.limit = info.pageSize;
414
- $Data.pager.currentPage = 1;
415
- reload({ keepSelection: false });
416
- }
417
-
418
- function onActiveChange(value, context) {
419
- // 禁止取消高亮:如果新值为空,保持当前选中
420
- if (value.length === 0) {
421
- if ($Data.activeRowKeys.length > 0) {
361
+ const idKey = ep.idKey || "id";
362
+ const record = row;
363
+ const rawId = record[idKey];
364
+
365
+ if (rawId === null || rawId === undefined || rawId === "") {
366
+ MessagePlugin.error("删除失败:缺少主键");
422
367
  return;
423
368
  }
424
369
 
425
- $Data.activeRowKeys = [];
426
- setCurrentRow(null);
427
- return;
428
- }
370
+ const confirmContent = $Method.getDeleteConfirmContent(ep, row);
429
371
 
430
- $Data.activeRowKeys = value;
372
+ let dialog = null;
373
+ let destroyed = false;
431
374
 
432
- const list = context.activeRowList;
433
- if (list && Array.isArray(list) && list.length > 0) {
434
- const row = list[0]?.row || null;
435
- setCurrentRow(row);
436
- return;
437
- }
375
+ const destroy = () => {
376
+ if (destroyed) return;
377
+ destroyed = true;
378
+ if (dialog && typeof dialog.destroy === "function") {
379
+ dialog.destroy();
380
+ }
381
+ };
438
382
 
439
- // C1:兜底回查(activeRowList 缺失时按 key 在 rows 中回查)
440
- const activeKey = value[0];
441
- for (const row of $Data.rows) {
442
- const key = getRowKeyValue(row);
443
- if (key === activeKey) {
444
- setCurrentRow(row);
445
- return;
446
- }
447
- }
448
- }
383
+ dialog = DialogPlugin.confirm({
384
+ header: confirmContent.header,
385
+ body: confirmContent.body,
386
+ status: confirmContent.status || "warning",
387
+ confirmBtn: confirmContent.confirmBtn || "删除",
388
+ cancelBtn: confirmContent.cancelBtn || "取消",
389
+ onConfirm: async () => {
390
+ if (dialog && typeof dialog.setConfirmLoading === "function") {
391
+ dialog.setConfirmLoading(true);
392
+ }
449
393
 
450
- function getDeleteConfirmContent(ep, row) {
451
- const confirm = ep.confirm;
452
- if (!confirm) {
453
- return {
454
- header: "确认删除",
455
- body: "确认删除该记录吗?",
456
- confirmBtn: "删除"
457
- };
458
- }
394
+ try {
395
+ const data = {};
396
+ data[idKey] = rawId;
459
397
 
460
- if (typeof confirm === "function") {
461
- return confirm(row);
462
- }
398
+ if (ep.buildData) {
399
+ const extra = ep.buildData(row);
400
+ if (extra && typeof extra === "object") {
401
+ for (const key of Object.keys(extra)) {
402
+ data[key] = extra[key];
403
+ }
404
+ }
405
+ }
463
406
 
464
- return confirm;
465
- }
407
+ await $Http(ep.path, data, ep.dropValues, ep.dropKeyValue);
408
+
409
+ MessagePlugin.success("删除成功");
410
+ destroy();
411
+ $Emit("deleted", { row: row });
412
+ await $Method.reload({ keepSelection: true });
413
+ } catch (error) {
414
+ MessagePlugin.error("删除失败");
415
+ } finally {
416
+ if (dialog && typeof dialog.setConfirmLoading === "function") {
417
+ dialog.setConfirmLoading(false);
418
+ }
419
+ }
420
+ },
421
+ onClose: () => {
422
+ destroy();
423
+ }
424
+ });
425
+ },
426
+ buildOperationSlotProps(scope) {
427
+ const out = {};
466
428
 
467
- async function deleteRow(row) {
468
- const ep = props.endpoints?.delete;
469
- if (!ep) {
470
- MessagePlugin.error("未配置删除接口");
471
- return;
472
- }
429
+ for (const key of Object.keys(scope)) {
430
+ out[key] = scope[key];
431
+ }
473
432
 
474
- const idKey = ep.idKey || "id";
475
- const record = row;
476
- const rawId = record[idKey];
433
+ out["deleteRow"] = $Method.deleteRow;
434
+ out["reload"] = $Method.reload;
477
435
 
478
- if (rawId === null || rawId === undefined || rawId === "") {
479
- MessagePlugin.error("删除失败:缺少主键");
480
- return;
436
+ return out;
481
437
  }
438
+ };
439
+
440
+ const $Computed = reactive({
441
+ columnsMeta: computed(() => {
442
+ const tableColumns = [];
443
+ const extraDetailColumns = [];
444
+ const tableColumnMap = {};
445
+ const formatKeys = [];
446
+
447
+ const cols = $Method.filterValidColumns($Prop.columns);
448
+ for (const col of cols) {
449
+ if (col?.detail === true) {
450
+ extraDetailColumns.push(col);
451
+ continue;
452
+ }
482
453
 
483
- const confirmContent = getDeleteConfirmContent(ep, row);
484
-
485
- let dialog = null;
486
- let destroyed = false;
454
+ tableColumns.push(col);
487
455
 
488
- const destroy = () => {
489
- if (destroyed) return;
490
- destroyed = true;
491
- if (dialog && typeof dialog.destroy === "function") {
492
- dialog.destroy();
493
- }
494
- };
495
-
496
- dialog = DialogPlugin.confirm({
497
- header: confirmContent.header,
498
- body: confirmContent.body,
499
- status: confirmContent.status || "warning",
500
- confirmBtn: confirmContent.confirmBtn || "删除",
501
- cancelBtn: confirmContent.cancelBtn || "取消",
502
- onConfirm: async () => {
503
- if (dialog && typeof dialog.setConfirmLoading === "function") {
504
- dialog.setConfirmLoading(true);
456
+ const key = $Method.getColKey(col);
457
+ if (!key) {
458
+ continue;
505
459
  }
506
460
 
507
- try {
508
- const data = {};
509
- data[idKey] = rawId;
461
+ tableColumnMap[key] = col;
510
462
 
511
- if (ep.buildData) {
512
- const extra = ep.buildData(row);
513
- if (extra && typeof extra === "object") {
514
- for (const k of Object.keys(extra)) {
515
- data[k] = extra[k];
516
- }
517
- }
518
- }
463
+ const format = col.format;
464
+ if ((typeof format === "string" && format.trim().length > 0) || typeof format === "function") {
465
+ formatKeys.push(key);
466
+ }
467
+ }
519
468
 
520
- await $Http(ep.path, data, ep.dropValues, ep.dropKeyValue);
469
+ const detailFields = extraDetailColumns.length > 0 ? $Method.mergeDetailColumns(tableColumns, extraDetailColumns) : tableColumns;
521
470
 
522
- MessagePlugin.success("删除成功");
523
- destroy();
524
- emit("deleted", { row: row });
525
- await reload({ keepSelection: true });
526
- } catch (error) {
527
- MessagePlugin.error("删除失败");
528
- } finally {
529
- if (dialog && typeof dialog.setConfirmLoading === "function") {
530
- dialog.setConfirmLoading(false);
531
- }
471
+ return {
472
+ tableColumns: tableColumns,
473
+ detailFields: detailFields,
474
+ tableColumnMap: tableColumnMap,
475
+ formatKeys: formatKeys
476
+ };
477
+ }),
478
+ tableColumns: computed(() => $Computed.columnsMeta.tableColumns),
479
+ detailFields: computed(() => $Computed.columnsMeta.detailFields),
480
+ resolvedTableHeight: computed(() => {
481
+ if ($Prop.isPagination) {
482
+ return $Prop.tableHeight;
483
+ }
484
+ return "calc(100vh - var(--search-height) - var(--layout-gap) * 2)";
485
+ }),
486
+ forwardedTableSlotNames: computed(() => {
487
+ if (Array.isArray($Prop.tableSlotNames) && $Prop.tableSlotNames.length > 0) {
488
+ const out = [];
489
+ const set = new Set();
490
+ for (const name of $Prop.tableSlotNames) {
491
+ if (typeof name !== "string") continue;
492
+ const trimmed = name.trim();
493
+ if (trimmed.length === 0) continue;
494
+ if (set.has(trimmed)) continue;
495
+ set.add(trimmed);
496
+ out.push(trimmed);
532
497
  }
533
- },
534
- onClose: () => {
535
- destroy();
498
+ return out;
536
499
  }
537
- });
538
- }
539
500
 
540
- function buildOperationSlotProps(scope) {
541
- const out = {};
501
+ const names = [];
502
+ for (const key of Object.keys($From.slots)) {
503
+ if ($Const.reservedSlotNames.includes(key)) continue;
504
+ names.push(key);
505
+ }
542
506
 
543
- for (const k of Object.keys(scope)) {
544
- out[k] = scope[k];
545
- }
507
+ return names;
508
+ }),
509
+ hasToolBar: computed(() => {
510
+ return Boolean($From.slots.toolLeft) || Boolean($From.slots.toolRight);
511
+ }),
512
+ tableRenderSlotNames: computed(() => {
513
+ const out = [];
514
+ const set = new Set();
546
515
 
547
- out["deleteRow"] = deleteRow;
548
- out["reload"] = reload;
516
+ for (const name of $Computed.forwardedTableSlotNames) {
517
+ if (!name || set.has(name)) {
518
+ continue;
519
+ }
520
+ set.add(name);
521
+ out.push(name);
522
+ }
549
523
 
550
- return out;
551
- }
524
+ for (const key of $Computed.columnsMeta.formatKeys) {
525
+ if (!key || set.has(key)) {
526
+ continue;
527
+ }
528
+ set.add(key);
529
+ out.push(key);
530
+ }
531
+
532
+ return out;
533
+ })
534
+ });
552
535
 
553
536
  defineExpose({
554
- reload: reload,
555
- deleteRow: deleteRow,
537
+ reload: $Method.reload,
538
+ deleteRow: $Method.deleteRow,
556
539
  rows: $Data.rows,
557
540
  pager: $Data.pager,
558
541
  currentRow: $Data.currentRow
559
542
  });
560
543
 
561
544
  onMounted(() => {
562
- if (!props.autoLoad) return;
563
- reload({ keepSelection: false });
545
+ if (!$Prop.autoLoad) return;
546
+ $Method.reload({ keepSelection: false });
564
547
  });
565
548
  </script>
566
549