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.
- package/package.json +6 -7
- package/src/App.vue +2 -4
- package/src/components/{DetailPanel.vue → detailPanel.vue} +71 -36
- package/src/components/pageDialog.vue +223 -0
- package/src/components/{PagedTableDetailPage.vue → pagedTableDetail.vue} +157 -56
- package/src/layouts/default.vue +151 -166
- package/src/plugins/config.ts +6 -6
- package/src/plugins/http.ts +3 -4
- package/src/plugins/storage.ts +1 -1
- package/src/styles/global.scss +0 -11
- package/src/types/auto-imports.d.ts +14 -2
- package/src/types/components.d.ts +7 -3
- package/src/types/typed-router.d.ts +18 -13
- package/src/utils/tdesignPlugins.ts +57 -0
|
@@ -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="
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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:
|
|
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
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
314
|
+
$Data.activeRowKeys = [];
|
|
214
315
|
return;
|
|
215
316
|
}
|
|
216
317
|
|
|
217
|
-
activeRowKeys
|
|
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
|
|
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
|
|
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.
|
|
246
|
-
data[limitKey] = pager.
|
|
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.
|
|
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.
|
|
274
|
-
if (pager.
|
|
275
|
-
pager.
|
|
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
|
|
282
|
-
pager.
|
|
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
|
-
|
|
391
|
+
void showMessageError("加载数据失败");
|
|
291
392
|
} finally {
|
|
292
393
|
if (seq === requestSeq) {
|
|
293
|
-
loading
|
|
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.
|
|
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.
|
|
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.
|
|
312
|
-
pager.
|
|
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.
|
|
420
|
+
if ($Data.activeRowKeys.length > 0) {
|
|
320
421
|
return;
|
|
321
422
|
}
|
|
322
423
|
|
|
323
|
-
activeRowKeys
|
|
424
|
+
$Data.activeRowKeys = [];
|
|
324
425
|
setCurrentRow(null);
|
|
325
426
|
return;
|
|
326
427
|
}
|
|
327
428
|
|
|
328
|
-
activeRowKeys
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
524
|
+
await showMessageSuccess("删除成功");
|
|
424
525
|
destroy();
|
|
425
526
|
emit("deleted", { row: row });
|
|
426
527
|
await reload({ keepSelection: true });
|
|
427
528
|
} catch (error) {
|
|
428
|
-
|
|
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(() => {
|