befly-admin-ui 1.10.1 → 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 -402
  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 +51 -55
  8. package/views/config/dictType/components/edit.vue +81 -57
  9. package/views/config/dictType/index.vue +37 -39
  10. package/views/config/system/components/edit.vue +97 -86
  11. package/views/config/system/index.vue +35 -37
  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 +96 -85
  22. package/views/log/error/index.vue +111 -117
  23. package/views/log/login/index.vue +15 -13
  24. package/views/log/operate/index.vue +31 -31
  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 +27 -28
  28. package/views/permission/api/index.vue +44 -45
  29. package/views/permission/menu/index.vue +35 -35
  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 +47 -49
  34. package/views/resource/gallery/index.vue +77 -78
@@ -1,42 +1,42 @@
1
1
  <template>
2
- <div :class="['page-table-detail', 'page-table', { 'page-table--with-pagination': props.isPagination }]">
3
- <div v-if="hasToolBar" 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,485 +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
-
193
- const detailFields = extraDetailColumns.length > 0 ? mergeDetailColumns(tableColumns, extraDetailColumns) : tableColumns;
194
-
195
- return {
196
- tableColumns: tableColumns,
197
- detailFields: detailFields,
198
- tableColumnMap: tableColumnMap,
199
- formatKeys: formatKeys
200
- };
201
- });
202
-
203
- const tableColumns = computed(() => columnsMeta.value.tableColumns);
204
-
205
- const detailFields = computed(() => columnsMeta.value.detailFields);
206
-
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
- });
213
211
 
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);
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
225
  }
226
- return out;
227
- }
228
226
 
229
- const names = [];
230
- const reserved = ["toolLeft", "toolRight", "detail", "dialogs", "operation", "default"]; // operation 单独处理
227
+ const preferKey = options?.keepSelection ? ($Data.currentRow ? $Method.getRowKeyValue($Data.currentRow) : null) : null;
231
228
 
232
- for (const key of Object.keys(slots)) {
233
- if (reserved.includes(key)) continue;
234
- names.push(key);
235
- }
229
+ $From.requestSeq += 1;
230
+ const seq = $From.requestSeq;
236
231
 
237
- return names;
238
- });
232
+ $Data.loading = true;
233
+ try {
234
+ const pageKey = listEndpoint.pageKey || "page";
235
+ const limitKey = listEndpoint.limitKey || "limit";
239
236
 
240
- const hasToolBar = computed(() => {
241
- return Boolean(slots.toolLeft) || Boolean(slots.toolRight);
242
- });
237
+ const data = {};
238
+ data[pageKey] = $Data.pager.currentPage;
239
+ data[limitKey] = $Data.pager.limit;
243
240
 
244
- const tableRenderSlotNames = computed(() => {
245
- const out = [];
246
- const set = new Set();
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
+ }
247
252
 
248
- for (const name of forwardedTableSlotNames.value) {
249
- if (!name || set.has(name)) {
250
- continue;
251
- }
252
- set.add(name);
253
- out.push(name);
254
- }
253
+ const res = await $Http(listEndpoint.path, data, listEndpoint.dropValues, listEndpoint.dropKeyValue);
255
254
 
256
- for (const key of columnsMeta.value.formatKeys) {
257
- if (!key || set.has(key)) {
258
- continue;
259
- }
260
- set.add(key);
261
- out.push(key);
262
- }
255
+ if (seq !== $From.requestSeq) {
256
+ return;
257
+ }
263
258
 
264
- return out;
265
- });
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
+ }
266
274
 
267
- function formatTableCell(row, colKey) {
268
- const record = row && typeof row === "object" ? row : {};
269
- const value = record[colKey];
270
- const field = columnsMeta.value.tableColumnMap[colKey] || {};
271
- return formatFieldValue(value, field);
272
- }
273
-
274
- function setCurrentRow(row) {
275
- $Data.currentRow = row;
276
- emit("row-change", { row: row });
277
- }
278
-
279
- function getRowKeyValue(row) {
280
- const key = props.rowKey;
281
- if (!key) return null;
282
-
283
- const record = row;
284
- const raw = record[key];
285
- if (typeof raw === "string") return raw;
286
- if (typeof raw === "number") return raw;
287
- return null;
288
- }
289
-
290
- function applyAutoSelection(list, preferKey) {
291
- if (!Array.isArray(list) || list.length === 0) {
292
- setCurrentRow(null);
293
- $Data.activeRowKeys = [];
294
- return;
295
- }
275
+ $Data.rows = lists;
276
+ $Data.pager.total = total;
296
277
 
297
- if (preferKey !== null) {
298
- for (const row of list) {
299
- const k = getRowKeyValue(row);
300
- if (k === preferKey) {
301
- setCurrentRow(row);
302
- $Data.activeRowKeys = [k];
278
+ $Method.applyAutoSelection(lists, preferKey);
279
+ $Emit("loaded", { rows: lists, total: total });
280
+ } catch (error) {
281
+ if (seq !== $From.requestSeq) {
303
282
  return;
304
283
  }
284
+ MessagePlugin.error("加载数据失败");
285
+ } finally {
286
+ if (seq === $From.requestSeq) {
287
+ $Data.loading = false;
288
+ }
305
289
  }
306
- }
307
-
308
- const first = list[0];
309
- const firstKey = getRowKeyValue(first);
310
- setCurrentRow(first);
311
-
312
- if (firstKey === null) {
313
- $Data.activeRowKeys = [];
314
- return;
315
- }
316
-
317
- $Data.activeRowKeys = [firstKey];
318
- }
319
-
320
- function getLastPage(total, limit) {
321
- if (total <= 0) return 1;
322
- if (limit <= 0) return 1;
323
- const pages = Math.ceil(total / limit);
324
- if (!Number.isFinite(pages) || pages <= 0) return 1;
325
- return pages;
326
- }
327
-
328
- async function loadList(options) {
329
- const listEndpoint = props.endpoints?.list;
330
- if (!listEndpoint) {
331
- return;
332
- }
333
-
334
- const preferKey = options?.keepSelection ? ($Data.currentRow ? getRowKeyValue($Data.currentRow) : null) : null;
335
-
336
- requestSeq += 1;
337
- const seq = requestSeq;
338
-
339
- $Data.loading = true;
340
- try {
341
- const pageKey = listEndpoint.pageKey || "page";
342
- const limitKey = listEndpoint.limitKey || "limit";
343
-
344
- const data = {};
345
- data[pageKey] = $Data.pager.currentPage;
346
- data[limitKey] = $Data.pager.limit;
347
-
348
- if (listEndpoint.buildData) {
349
- const extra = listEndpoint.buildData({
350
- currentPage: $Data.pager.currentPage,
351
- limit: $Data.pager.limit
352
- });
353
- if (extra && typeof extra === "object") {
354
- for (const k of Object.keys(extra)) {
355
- data[k] = extra[k];
356
- }
290
+ },
291
+ async reload(options) {
292
+ if (options?.resetPage) {
293
+ $Data.pager.currentPage = 1;
294
+ }
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) {
312
+ return;
357
313
  }
314
+
315
+ $Data.activeRowKeys = [];
316
+ $Method.setCurrentRow(null);
317
+ return;
358
318
  }
359
319
 
360
- const res = await $Http(listEndpoint.path, data, listEndpoint.dropValues, listEndpoint.dropKeyValue);
320
+ $Data.activeRowKeys = value;
361
321
 
362
- // 并发保护:旧请求返回后不应覆盖新请求的状态
363
- 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);
364
326
  return;
365
327
  }
366
328
 
367
- const lists = Array.isArray(res?.data?.lists) ? res.data.lists : [];
368
- const total = typeof res?.data?.total === "number" ? res.data.total : 0;
369
-
370
- // B:页码越界修正(删除/过滤等导致 total 变小后,当前页可能已不存在)
371
- const allowPageFallback = options?.allowPageFallback !== false;
372
- if (allowPageFallback && lists.length === 0 && total > 0) {
373
- const lastPage = getLastPage(total, $Data.pager.limit);
374
- if ($Data.pager.currentPage > lastPage) {
375
- $Data.pager.currentPage = lastPage;
376
- await loadList({
377
- keepSelection: false,
378
- allowPageFallback: false
379
- });
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);
380
334
  return;
381
335
  }
382
336
  }
337
+ },
338
+ getDeleteConfirmContent(ep, row) {
339
+ const confirm = ep.confirm;
340
+ if (!confirm) {
341
+ return {
342
+ header: "确认删除",
343
+ body: "确认删除该记录吗?",
344
+ confirmBtn: "删除"
345
+ };
346
+ }
383
347
 
384
- $Data.rows = lists;
385
- $Data.pager.total = total;
348
+ if (typeof confirm === "function") {
349
+ return confirm(row);
350
+ }
386
351
 
387
- applyAutoSelection(lists, preferKey);
388
- emit("loaded", { rows: lists, total: total });
389
- } catch (error) {
390
- if (seq !== requestSeq) {
352
+ return confirm;
353
+ },
354
+ async deleteRow(row) {
355
+ const ep = $Prop.endpoints?.delete;
356
+ if (!ep) {
357
+ MessagePlugin.error("未配置删除接口");
391
358
  return;
392
359
  }
393
- MessagePlugin.error("加载数据失败");
394
- } finally {
395
- if (seq === requestSeq) {
396
- $Data.loading = false;
397
- }
398
- }
399
- }
400
360
 
401
- async function reload(options) {
402
- if (options?.resetPage) {
403
- $Data.pager.currentPage = 1;
404
- }
405
- await loadList({
406
- keepSelection: options?.keepSelection,
407
- allowPageFallback: true
408
- });
409
- }
410
-
411
- function onPageChange(info) {
412
- $Data.pager.currentPage = info.currentPage;
413
- reload({ keepSelection: true });
414
- }
415
-
416
- function onPageSizeChange(info) {
417
- $Data.pager.limit = info.pageSize;
418
- $Data.pager.currentPage = 1;
419
- reload({ keepSelection: false });
420
- }
421
-
422
- function onActiveChange(value, context) {
423
- // 禁止取消高亮:如果新值为空,保持当前选中
424
- if (value.length === 0) {
425
- 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("删除失败:缺少主键");
426
367
  return;
427
368
  }
428
369
 
429
- $Data.activeRowKeys = [];
430
- setCurrentRow(null);
431
- return;
432
- }
370
+ const confirmContent = $Method.getDeleteConfirmContent(ep, row);
433
371
 
434
- $Data.activeRowKeys = value;
372
+ let dialog = null;
373
+ let destroyed = false;
435
374
 
436
- const list = context.activeRowList;
437
- if (list && Array.isArray(list) && list.length > 0) {
438
- const row = list[0]?.row || null;
439
- setCurrentRow(row);
440
- return;
441
- }
375
+ const destroy = () => {
376
+ if (destroyed) return;
377
+ destroyed = true;
378
+ if (dialog && typeof dialog.destroy === "function") {
379
+ dialog.destroy();
380
+ }
381
+ };
442
382
 
443
- // C1:兜底回查(activeRowList 缺失时按 key 在 rows 中回查)
444
- const activeKey = value[0];
445
- for (const row of $Data.rows) {
446
- const key = getRowKeyValue(row);
447
- if (key === activeKey) {
448
- setCurrentRow(row);
449
- return;
450
- }
451
- }
452
- }
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
+ }
453
393
 
454
- function getDeleteConfirmContent(ep, row) {
455
- const confirm = ep.confirm;
456
- if (!confirm) {
457
- return {
458
- header: "确认删除",
459
- body: "确认删除该记录吗?",
460
- confirmBtn: "删除"
461
- };
462
- }
394
+ try {
395
+ const data = {};
396
+ data[idKey] = rawId;
463
397
 
464
- if (typeof confirm === "function") {
465
- return confirm(row);
466
- }
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
+ }
467
406
 
468
- return confirm;
469
- }
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 = {};
470
428
 
471
- async function deleteRow(row) {
472
- const ep = props.endpoints?.delete;
473
- if (!ep) {
474
- MessagePlugin.error("未配置删除接口");
475
- return;
476
- }
429
+ for (const key of Object.keys(scope)) {
430
+ out[key] = scope[key];
431
+ }
477
432
 
478
- const idKey = ep.idKey || "id";
479
- const record = row;
480
- const rawId = record[idKey];
433
+ out["deleteRow"] = $Method.deleteRow;
434
+ out["reload"] = $Method.reload;
481
435
 
482
- if (rawId === null || rawId === undefined || rawId === "") {
483
- MessagePlugin.error("删除失败:缺少主键");
484
- return;
436
+ return out;
485
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
+ }
486
453
 
487
- const confirmContent = getDeleteConfirmContent(ep, row);
488
-
489
- let dialog = null;
490
- let destroyed = false;
454
+ tableColumns.push(col);
491
455
 
492
- const destroy = () => {
493
- if (destroyed) return;
494
- destroyed = true;
495
- if (dialog && typeof dialog.destroy === "function") {
496
- dialog.destroy();
497
- }
498
- };
499
-
500
- dialog = DialogPlugin.confirm({
501
- header: confirmContent.header,
502
- body: confirmContent.body,
503
- status: confirmContent.status || "warning",
504
- confirmBtn: confirmContent.confirmBtn || "删除",
505
- cancelBtn: confirmContent.cancelBtn || "取消",
506
- onConfirm: async () => {
507
- if (dialog && typeof dialog.setConfirmLoading === "function") {
508
- dialog.setConfirmLoading(true);
456
+ const key = $Method.getColKey(col);
457
+ if (!key) {
458
+ continue;
509
459
  }
510
460
 
511
- try {
512
- const data = {};
513
- data[idKey] = rawId;
461
+ tableColumnMap[key] = col;
514
462
 
515
- if (ep.buildData) {
516
- const extra = ep.buildData(row);
517
- if (extra && typeof extra === "object") {
518
- for (const k of Object.keys(extra)) {
519
- data[k] = extra[k];
520
- }
521
- }
522
- }
463
+ const format = col.format;
464
+ if ((typeof format === "string" && format.trim().length > 0) || typeof format === "function") {
465
+ formatKeys.push(key);
466
+ }
467
+ }
523
468
 
524
- await $Http(ep.path, data, ep.dropValues, ep.dropKeyValue);
469
+ const detailFields = extraDetailColumns.length > 0 ? $Method.mergeDetailColumns(tableColumns, extraDetailColumns) : tableColumns;
525
470
 
526
- MessagePlugin.success("删除成功");
527
- destroy();
528
- emit("deleted", { row: row });
529
- await reload({ keepSelection: true });
530
- } catch (error) {
531
- MessagePlugin.error("删除失败");
532
- } finally {
533
- if (dialog && typeof dialog.setConfirmLoading === "function") {
534
- dialog.setConfirmLoading(false);
535
- }
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);
536
497
  }
537
- },
538
- onClose: () => {
539
- destroy();
498
+ return out;
540
499
  }
541
- });
542
- }
543
500
 
544
- function buildOperationSlotProps(scope) {
545
- 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
+ }
546
506
 
547
- for (const k of Object.keys(scope)) {
548
- out[k] = scope[k];
549
- }
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();
550
515
 
551
- out["deleteRow"] = deleteRow;
552
- 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
+ }
553
523
 
554
- return out;
555
- }
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
+ });
556
535
 
557
536
  defineExpose({
558
- reload: reload,
559
- deleteRow: deleteRow,
537
+ reload: $Method.reload,
538
+ deleteRow: $Method.deleteRow,
560
539
  rows: $Data.rows,
561
540
  pager: $Data.pager,
562
541
  currentRow: $Data.currentRow
563
542
  });
564
543
 
565
544
  onMounted(() => {
566
- if (!props.autoLoad) return;
567
- reload({ keepSelection: false });
545
+ if (!$Prop.autoLoad) return;
546
+ $Method.reload({ keepSelection: false });
568
547
  });
569
548
  </script>
570
549