openxiangda-cli 2.0.0-alpha.68 → 2.0.0-alpha.69
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 +4 -4
- package/template/AGENTS.md +12 -7
- package/template/README.md +7 -7
- package/template/apps/server/package.json +1 -1
- package/template/apps/web/package.json +1 -1
- package/template/apps/web/scripts/check.mjs +18 -13
- package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +339 -163
- package/template/apps/web/src/components/resource/ResourceBatchActions.tsx +319 -0
- package/template/apps/web/src/components/resource/resource-import.ts +205 -0
- package/template/apps/web/src/platform-client.ts +188 -40
- package/template/apps/web/src/styles.css +37 -8
- package/template/apps/web/test/contracts.test.ts +16 -6
- package/template/apps/web/test/resource-import.test.ts +142 -0
- package/template/openxiangda.config.ts +2 -1
- package/template/package.json +4 -3
- package/template/scripts/verify-template-budget.mjs +5 -0
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
EditOutlined,
|
|
6
6
|
EyeOutlined,
|
|
7
7
|
ExportOutlined,
|
|
8
|
-
ImportOutlined,
|
|
9
8
|
PlusOutlined,
|
|
10
9
|
ReloadOutlined,
|
|
11
10
|
SearchOutlined,
|
|
@@ -60,6 +59,7 @@ import {
|
|
|
60
59
|
import { createNativeResourceClient, type GenericResourceQuery } from '../../platform-client';
|
|
61
60
|
import { ResolvedValueText } from '../../AuthoritativeSelector';
|
|
62
61
|
import { useRuntime } from '../../runtime';
|
|
62
|
+
import { ResourceBatchActions, ResourceImportButton } from './ResourceBatchActions';
|
|
63
63
|
|
|
64
64
|
type ResourceRecord = Record<string, unknown> & {
|
|
65
65
|
id: string;
|
|
@@ -70,7 +70,12 @@ type ResourceRecord = Record<string, unknown> & {
|
|
|
70
70
|
type GeneratedDefinition = {
|
|
71
71
|
code: string;
|
|
72
72
|
name: string;
|
|
73
|
-
capabilities: {
|
|
73
|
+
capabilities: {
|
|
74
|
+
read: string;
|
|
75
|
+
create: string;
|
|
76
|
+
update: string;
|
|
77
|
+
delete: string;
|
|
78
|
+
};
|
|
74
79
|
surface: DataResourceSurface;
|
|
75
80
|
};
|
|
76
81
|
|
|
@@ -92,16 +97,17 @@ function fieldsBySection(surface: DataResourceSurface) {
|
|
|
92
97
|
const section = field.section || 'default';
|
|
93
98
|
groups.set(section, [...(groups.get(section) || []), { key, ...field }]);
|
|
94
99
|
}
|
|
95
|
-
return [...groups.entries()].map(([section, fields]) => ({
|
|
100
|
+
return [...groups.entries()].map(([section, fields]) => ({
|
|
101
|
+
section,
|
|
102
|
+
fields,
|
|
103
|
+
}));
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
function normalizeRecordForForm(record: ResourceRecord, surface: DataResourceSurface) {
|
|
99
107
|
return Object.fromEntries(
|
|
100
108
|
Object.entries(record).map(([key, value]) => {
|
|
101
109
|
const widget = surface.fields[key]?.widget;
|
|
102
|
-
return widget === 'date' || widget === 'datetime'
|
|
103
|
-
? [key, value ? dayjs(String(value)) : undefined]
|
|
104
|
-
: [key, value];
|
|
110
|
+
return widget === 'date' || widget === 'datetime' ? [key, value ? dayjs(String(value)) : undefined] : [key, value];
|
|
105
111
|
})
|
|
106
112
|
);
|
|
107
113
|
}
|
|
@@ -111,22 +117,20 @@ function normalizeFormValues(values: Record<string, unknown>, surface: DataResou
|
|
|
111
117
|
Object.entries(values).map(([key, value]) => {
|
|
112
118
|
const widget = surface.fields[key]?.widget;
|
|
113
119
|
return widget === 'date' || widget === 'datetime'
|
|
114
|
-
? [
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
? [
|
|
121
|
+
key,
|
|
122
|
+
value && typeof value === 'object' && 'format' in value
|
|
123
|
+
? widget === 'datetime'
|
|
124
|
+
? dayjs(value as never).toISOString()
|
|
125
|
+
: (value as { format: (pattern: string) => string }).format('YYYY-MM-DD')
|
|
126
|
+
: value,
|
|
127
|
+
]
|
|
119
128
|
: [key, value];
|
|
120
129
|
})
|
|
121
130
|
);
|
|
122
131
|
}
|
|
123
132
|
|
|
124
|
-
function fieldWritable(
|
|
125
|
-
field: SurfaceField,
|
|
126
|
-
mode: 'create' | 'edit',
|
|
127
|
-
hasCapability: (code: string) => boolean,
|
|
128
|
-
isAppSuperAdmin: boolean
|
|
129
|
-
) {
|
|
133
|
+
function fieldWritable(field: SurfaceField, mode: 'create' | 'edit', hasCapability: (code: string) => boolean, isAppSuperAdmin: boolean) {
|
|
130
134
|
const capability = mode === 'create' ? field.createCapability : field.updateCapability;
|
|
131
135
|
if (isAppSuperAdmin) return true;
|
|
132
136
|
if (capability) return hasCapability(capability);
|
|
@@ -134,11 +138,7 @@ function fieldWritable(
|
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
function auditOperationLabel(operation: DataAuditEntry['operation']) {
|
|
137
|
-
return operation === 'created'
|
|
138
|
-
? '创建'
|
|
139
|
-
: operation === 'deleted'
|
|
140
|
-
? '删除'
|
|
141
|
-
: '更新';
|
|
141
|
+
return operation === 'created' ? '创建' : operation === 'deleted' ? '删除' : '更新';
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
function auditValue(field: SurfaceField, value: unknown) {
|
|
@@ -149,19 +149,19 @@ function auditValue(field: SurfaceField, value: unknown) {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
function auditChangeFields(entry: DataAuditEntry, surface: DataResourceSurface) {
|
|
152
|
-
const fields = fieldsBySection(surface).flatMap(group => group.fields);
|
|
152
|
+
const fields = fieldsBySection(surface).flatMap((group) => group.fields);
|
|
153
153
|
const source = entry.changes || entry.record || entry.before || {};
|
|
154
|
-
return fields.filter(field => Object.prototype.hasOwnProperty.call(source, field.key));
|
|
154
|
+
return fields.filter((field) => Object.prototype.hasOwnProperty.call(source, field.key));
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
function exportCellValue(field: SurfaceField, value: unknown) {
|
|
158
158
|
if (value === undefined || value === null || value === '') return '';
|
|
159
159
|
if (field.widget === 'boolean') return value ? '是' : '否';
|
|
160
160
|
if (field.options && !Array.isArray(value)) {
|
|
161
|
-
return field.options.find(option => option.value === value)?.label || String(value);
|
|
161
|
+
return field.options.find((option) => option.value === value)?.label || String(value);
|
|
162
162
|
}
|
|
163
163
|
if (Array.isArray(value)) {
|
|
164
|
-
return value.map(item => field.options?.find(option => option.value === item)?.label || String(item)).join('、');
|
|
164
|
+
return value.map((item) => field.options?.find((option) => option.value === item)?.label || String(item)).join('、');
|
|
165
165
|
}
|
|
166
166
|
if (field.widget === 'date' || field.widget === 'datetime') {
|
|
167
167
|
const parsed = new Date(String(value));
|
|
@@ -180,7 +180,7 @@ async function exportResourceRows(
|
|
|
180
180
|
name: string,
|
|
181
181
|
surface: DataResourceSurface,
|
|
182
182
|
query: Partial<GenericResourceQuery>,
|
|
183
|
-
sort: { field: string; order: 'asc' | 'desc' }
|
|
183
|
+
sort: { field: string; order: 'asc' | 'desc' }
|
|
184
184
|
) {
|
|
185
185
|
const client = createNativeResourceClient(code, surface);
|
|
186
186
|
const fields = listSurfaceFields(surface.fields);
|
|
@@ -201,9 +201,11 @@ async function exportResourceRows(
|
|
|
201
201
|
total = result.total;
|
|
202
202
|
page += 1;
|
|
203
203
|
} while (rows.length < total && rows.length < 10000 && page <= 20);
|
|
204
|
-
const header = fields.map(field => csvCell(field.label)).join(',');
|
|
205
|
-
const body = rows.map(row => fields.map(field => csvCell(exportCellValue(field, row[field.key]))).join(','));
|
|
206
|
-
const blob = new Blob([`\uFEFF${[header, ...body].join('\r\n')}`], {
|
|
204
|
+
const header = fields.map((field) => csvCell(field.label)).join(',');
|
|
205
|
+
const body = rows.map((row) => fields.map((field) => csvCell(exportCellValue(field, row[field.key]))).join(','));
|
|
206
|
+
const blob = new Blob([`\uFEFF${[header, ...body].join('\r\n')}`], {
|
|
207
|
+
type: 'text/csv;charset=utf-8',
|
|
208
|
+
});
|
|
207
209
|
const url = URL.createObjectURL(blob);
|
|
208
210
|
const anchor = document.createElement('a');
|
|
209
211
|
anchor.href = url;
|
|
@@ -225,7 +227,10 @@ function AuditEntry({ entry, surface }: { entry: DataAuditEntry; surface: DataRe
|
|
|
225
227
|
<div className="oxa-audit-meta">
|
|
226
228
|
<span>操作人</span>
|
|
227
229
|
{entry.actor.principalType === 'application' ? (
|
|
228
|
-
<Typography.Text
|
|
230
|
+
<Typography.Text>
|
|
231
|
+
应用服务
|
|
232
|
+
{entry.actor.oauthClientId ? `(${entry.actor.oauthClientId})` : ''}
|
|
233
|
+
</Typography.Text>
|
|
229
234
|
) : (
|
|
230
235
|
<ResolvedValueText source="user" value={entry.actor.userId} />
|
|
231
236
|
)}
|
|
@@ -234,7 +239,7 @@ function AuditEntry({ entry, surface }: { entry: DataAuditEntry; surface: DataRe
|
|
|
234
239
|
</div>
|
|
235
240
|
{fields.length ? (
|
|
236
241
|
<div className="oxa-audit-changes">
|
|
237
|
-
{fields.map(field => {
|
|
242
|
+
{fields.map((field) => {
|
|
238
243
|
const before = entry.operation === 'created' ? undefined : entry.before?.[field.key];
|
|
239
244
|
const after = entry.operation === 'deleted' ? undefined : changes[field.key];
|
|
240
245
|
return (
|
|
@@ -265,13 +270,19 @@ function useGeneratedList(definition: GeneratedDefinition) {
|
|
|
265
270
|
const [pageSize, setPageSize] = useState(defaultPageSize);
|
|
266
271
|
const [draft, setDraft] = useState<Partial<GenericResourceQuery>>({});
|
|
267
272
|
const [query, setQuery] = useState<Partial<GenericResourceQuery>>({});
|
|
268
|
-
const [sort, setSort] = useState({
|
|
273
|
+
const [sort, setSort] = useState({
|
|
274
|
+
field: defaultSortField,
|
|
275
|
+
order: defaultSortOrder as 'asc' | 'desc',
|
|
276
|
+
});
|
|
269
277
|
useEffect(() => {
|
|
270
278
|
setPage(1);
|
|
271
279
|
setPageSize(defaultPageSize);
|
|
272
280
|
setDraft({});
|
|
273
281
|
setQuery({});
|
|
274
|
-
setSort({
|
|
282
|
+
setSort({
|
|
283
|
+
field: defaultSortField,
|
|
284
|
+
order: defaultSortOrder as 'asc' | 'desc',
|
|
285
|
+
});
|
|
275
286
|
}, [code, defaultPageSize, defaultSortField, defaultSortOrder]);
|
|
276
287
|
const list = useList<ResourceRecord>({
|
|
277
288
|
resource: code,
|
|
@@ -279,7 +290,19 @@ function useGeneratedList(definition: GeneratedDefinition) {
|
|
|
279
290
|
sorters: [sort],
|
|
280
291
|
meta: { query },
|
|
281
292
|
});
|
|
282
|
-
return {
|
|
293
|
+
return {
|
|
294
|
+
draft,
|
|
295
|
+
list,
|
|
296
|
+
page,
|
|
297
|
+
pageSize,
|
|
298
|
+
query,
|
|
299
|
+
setDraft,
|
|
300
|
+
setPage,
|
|
301
|
+
setPageSize,
|
|
302
|
+
setQuery,
|
|
303
|
+
setSort,
|
|
304
|
+
sort,
|
|
305
|
+
};
|
|
283
306
|
}
|
|
284
307
|
|
|
285
308
|
export function GeneratedResourcePage({
|
|
@@ -302,36 +325,46 @@ export function GeneratedResourcePage({
|
|
|
302
325
|
}
|
|
303
326
|
|
|
304
327
|
function GeneratedResourceListPage({ definition, variant }: { definition: GeneratedDefinition; variant: 'desktop' | 'mobile' }) {
|
|
305
|
-
return variant === 'mobile'
|
|
306
|
-
? <GeneratedMobileList definition={definition} />
|
|
307
|
-
: <GeneratedDesktopList definition={definition} />;
|
|
328
|
+
return variant === 'mobile' ? <GeneratedMobileList definition={definition} /> : <GeneratedDesktopList definition={definition} />;
|
|
308
329
|
}
|
|
309
330
|
|
|
310
331
|
function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition }) {
|
|
311
332
|
const navigate = useNavigate();
|
|
312
333
|
const { message } = App.useApp();
|
|
313
|
-
const { hasCapability } = useRuntime();
|
|
334
|
+
const { hasCapability, identity } = useRuntime();
|
|
314
335
|
const state = useGeneratedList(definition);
|
|
315
336
|
const { code, name, surface, capabilities } = definition;
|
|
316
337
|
const fields = listSurfaceFields(surface.fields);
|
|
317
338
|
const [exporting, setExporting] = useState(false);
|
|
318
339
|
const [filtersExpanded, setFiltersExpanded] = useState(false);
|
|
319
340
|
const [density, setDensity] = useState<'small' | 'middle' | 'large'>('middle');
|
|
320
|
-
const [visibleColumnKeys, setVisibleColumnKeys] = useState<string[]>(() =>
|
|
321
|
-
|
|
322
|
-
);
|
|
341
|
+
const [visibleColumnKeys, setVisibleColumnKeys] = useState<string[]>(() => fields.map((field) => field.key));
|
|
342
|
+
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
|
|
323
343
|
const remove = useDelete();
|
|
324
344
|
const rows = (state.list.result.data || []) as unknown as ResourceRecord[];
|
|
325
345
|
useEffect(() => {
|
|
326
|
-
setVisibleColumnKeys(fields.map(field => field.key));
|
|
346
|
+
setVisibleColumnKeys(fields.map((field) => field.key));
|
|
327
347
|
setFiltersExpanded(false);
|
|
328
348
|
setDensity('middle');
|
|
349
|
+
setSelectedRowKeys([]);
|
|
329
350
|
}, [code]);
|
|
351
|
+
useEffect(() => {
|
|
352
|
+
setSelectedRowKeys([]);
|
|
353
|
+
}, [code, state.page, state.pageSize, state.query, state.sort.field, state.sort.order]);
|
|
354
|
+
const formFields = fieldsBySection(surface).flatMap((group) => group.fields);
|
|
355
|
+
const importWritableFieldCodes = formFields
|
|
356
|
+
.filter((field) => field.widget !== 'readonly' && fieldWritable(field, 'create', hasCapability, identity.isAppSuperAdmin))
|
|
357
|
+
.map((field) => field.key);
|
|
358
|
+
const batchWritableFields = formFields.filter(
|
|
359
|
+
(field) => !['file', 'readonly'].includes(field.widget || '') && fieldWritable(field, 'edit', hasCapability, identity.isAppSuperAdmin)
|
|
360
|
+
);
|
|
361
|
+
const selectedRows = rows.filter((row) => selectedRowKeys.includes(row.id));
|
|
362
|
+
const canBatch = hasCapability(capabilities.update) || hasCapability(capabilities.delete);
|
|
330
363
|
const columns = useMemo<TableColumnsType<ResourceRecord>>(
|
|
331
364
|
() => [
|
|
332
365
|
...fields
|
|
333
|
-
.filter(field => visibleColumnKeys.includes(field.key))
|
|
334
|
-
.map(field => ({
|
|
366
|
+
.filter((field) => visibleColumnKeys.includes(field.key))
|
|
367
|
+
.map((field) => ({
|
|
335
368
|
title: field.label,
|
|
336
369
|
dataIndex: field.key,
|
|
337
370
|
key: field.key,
|
|
@@ -343,9 +376,7 @@ function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition
|
|
|
343
376
|
renderers={{
|
|
344
377
|
renderValue: ({ value: current }) =>
|
|
345
378
|
field.key === fields[0]?.key ? (
|
|
346
|
-
<a onClick={() => navigate(`/${code}/${record.id}`)}>
|
|
347
|
-
{String(current || '-')}
|
|
348
|
-
</a>
|
|
379
|
+
<a onClick={() => navigate(`/${code}/${record.id}`)}>{String(current || '-')}</a>
|
|
349
380
|
) : undefined,
|
|
350
381
|
}}
|
|
351
382
|
/>
|
|
@@ -355,8 +386,7 @@ function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition
|
|
|
355
386
|
title: '更新时间',
|
|
356
387
|
dataIndex: 'updated_at',
|
|
357
388
|
key: 'updated_at',
|
|
358
|
-
render: value =>
|
|
359
|
-
value ? new Date(String(value)).toLocaleString('zh-CN') : '-',
|
|
389
|
+
render: (value) => (value ? new Date(String(value)).toLocaleString('zh-CN') : '-'),
|
|
360
390
|
},
|
|
361
391
|
{
|
|
362
392
|
title: '操作',
|
|
@@ -364,13 +394,7 @@ function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition
|
|
|
364
394
|
fixed: 'right' as const,
|
|
365
395
|
render: (_: unknown, record: ResourceRecord) => (
|
|
366
396
|
<Space size={4}>
|
|
367
|
-
<Button
|
|
368
|
-
aria-label="查看"
|
|
369
|
-
icon={<EyeOutlined />}
|
|
370
|
-
onClick={() => navigate(`/${code}/${record.id}`)}
|
|
371
|
-
size="small"
|
|
372
|
-
type="text"
|
|
373
|
-
/>
|
|
397
|
+
<Button aria-label="查看" icon={<EyeOutlined />} onClick={() => navigate(`/${code}/${record.id}`)} size="small" type="text" />
|
|
374
398
|
{hasCapability(capabilities.update) && (
|
|
375
399
|
<Button
|
|
376
400
|
aria-label="编辑"
|
|
@@ -394,18 +418,12 @@ function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition
|
|
|
394
418
|
message.success('已删除');
|
|
395
419
|
void state.list.query.refetch();
|
|
396
420
|
},
|
|
397
|
-
}
|
|
421
|
+
}
|
|
398
422
|
)
|
|
399
423
|
}
|
|
400
424
|
title={`确定删除${name}?`}
|
|
401
425
|
>
|
|
402
|
-
<Button
|
|
403
|
-
aria-label="删除"
|
|
404
|
-
danger
|
|
405
|
-
icon={<DeleteOutlined />}
|
|
406
|
-
size="small"
|
|
407
|
-
type="text"
|
|
408
|
-
/>
|
|
426
|
+
<Button aria-label="删除" danger icon={<DeleteOutlined />} size="small" type="text" />
|
|
409
427
|
</Popconfirm>
|
|
410
428
|
)}
|
|
411
429
|
</Space>
|
|
@@ -424,7 +442,7 @@ function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition
|
|
|
424
442
|
remove,
|
|
425
443
|
state.list.query,
|
|
426
444
|
visibleColumnKeys,
|
|
427
|
-
]
|
|
445
|
+
]
|
|
428
446
|
);
|
|
429
447
|
const handleExport = async () => {
|
|
430
448
|
setExporting(true);
|
|
@@ -440,12 +458,11 @@ function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition
|
|
|
440
458
|
const filterFields = surface.list?.filterFields || [];
|
|
441
459
|
const hasMoreFilters = filterFields.length > 2;
|
|
442
460
|
const columnMenu: MenuProps = {
|
|
443
|
-
items: fields.map(field => ({ key: field.key, label: field.label })),
|
|
461
|
+
items: fields.map((field) => ({ key: field.key, label: field.label })),
|
|
444
462
|
multiple: true,
|
|
445
463
|
selectable: true,
|
|
446
464
|
selectedKeys: visibleColumnKeys,
|
|
447
|
-
onSelect: ({ selectedKeys }) =>
|
|
448
|
-
setVisibleColumnKeys(selectedKeys.map(String)),
|
|
465
|
+
onSelect: ({ selectedKeys }) => setVisibleColumnKeys(selectedKeys.map(String)),
|
|
449
466
|
onDeselect: ({ selectedKeys }) => {
|
|
450
467
|
if (selectedKeys.length) setVisibleColumnKeys(selectedKeys.map(String));
|
|
451
468
|
else message.info('至少保留一列');
|
|
@@ -472,7 +489,11 @@ function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition
|
|
|
472
489
|
>
|
|
473
490
|
{state.list.query.isError && (
|
|
474
491
|
<Alert
|
|
475
|
-
action={
|
|
492
|
+
action={
|
|
493
|
+
<Button onClick={() => void state.list.query.refetch()} size="small">
|
|
494
|
+
重试
|
|
495
|
+
</Button>
|
|
496
|
+
}
|
|
476
497
|
description={state.list.query.error instanceof Error ? state.list.query.error.message : '请稍后重试或联系平台管理员'}
|
|
477
498
|
showIcon
|
|
478
499
|
title="数据读取失败"
|
|
@@ -484,22 +505,20 @@ function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition
|
|
|
484
505
|
<div className="oxa-filter-item">
|
|
485
506
|
<Select
|
|
486
507
|
allowClear
|
|
487
|
-
options={(surface.list?.searchableFields || []).map(key => ({
|
|
508
|
+
options={(surface.list?.searchableFields || []).map((key) => ({
|
|
488
509
|
label: fieldFor(surface, key).label,
|
|
489
510
|
value: key,
|
|
490
511
|
}))}
|
|
491
512
|
placeholder="搜索字段"
|
|
492
513
|
value={state.draft.searchField}
|
|
493
|
-
onChange={value =>
|
|
494
|
-
state.setDraft(item => ({ ...item, searchField: value }))
|
|
495
|
-
}
|
|
514
|
+
onChange={(value) => state.setDraft((item) => ({ ...item, searchField: value }))}
|
|
496
515
|
/>
|
|
497
516
|
</div>
|
|
498
517
|
<div className="oxa-filter-item">
|
|
499
518
|
<Input
|
|
500
519
|
allowClear
|
|
501
|
-
onChange={event =>
|
|
502
|
-
state.setDraft(item => ({
|
|
520
|
+
onChange={(event) =>
|
|
521
|
+
state.setDraft((item) => ({
|
|
503
522
|
...item,
|
|
504
523
|
keyword: event.target.value,
|
|
505
524
|
}))
|
|
@@ -516,14 +535,11 @@ function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition
|
|
|
516
535
|
{filterFields.map((key, index) => {
|
|
517
536
|
const field = fieldFor(surface, key);
|
|
518
537
|
return (
|
|
519
|
-
<div
|
|
520
|
-
className={`oxa-filter-item${index >= 2 && !filtersExpanded ? ' is-collapsed' : ''}`}
|
|
521
|
-
key={key}
|
|
522
|
-
>
|
|
538
|
+
<div className={`oxa-filter-item${index >= 2 && !filtersExpanded ? ' is-collapsed' : ''}`} key={key}>
|
|
523
539
|
<SurfaceFilterControl
|
|
524
540
|
field={field}
|
|
525
|
-
onChange={value =>
|
|
526
|
-
state.setDraft(item => ({
|
|
541
|
+
onChange={(value) =>
|
|
542
|
+
state.setDraft((item) => ({
|
|
527
543
|
...item,
|
|
528
544
|
filters: { ...item.filters, [key]: value },
|
|
529
545
|
}))
|
|
@@ -558,7 +574,7 @@ function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition
|
|
|
558
574
|
{hasMoreFilters && (
|
|
559
575
|
<Button
|
|
560
576
|
icon={filtersExpanded ? <UpOutlined /> : <DownOutlined />}
|
|
561
|
-
onClick={() => setFiltersExpanded(value => !value)}
|
|
577
|
+
onClick={() => setFiltersExpanded((value) => !value)}
|
|
562
578
|
type="link"
|
|
563
579
|
>
|
|
564
580
|
{filtersExpanded ? '收起筛选' : `更多筛选(${filterFields.length - 2})`}
|
|
@@ -569,26 +585,20 @@ function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition
|
|
|
569
585
|
<div className="oxa-list-toolbar">
|
|
570
586
|
<Space className="oxa-list-toolbar-primary" wrap>
|
|
571
587
|
{hasCapability(capabilities.create) && (
|
|
572
|
-
<Button
|
|
573
|
-
icon={<PlusOutlined />}
|
|
574
|
-
onClick={() => navigate(`/${code}/new`)}
|
|
575
|
-
type="primary"
|
|
576
|
-
>
|
|
588
|
+
<Button icon={<PlusOutlined />} onClick={() => navigate(`/${code}/new`)} type="primary">
|
|
577
589
|
新增{name}
|
|
578
590
|
</Button>
|
|
579
591
|
)}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
onClick={() => void handleExport()}
|
|
591
|
-
>
|
|
592
|
+
{hasCapability(capabilities.create) && (
|
|
593
|
+
<ResourceImportButton
|
|
594
|
+
code={code}
|
|
595
|
+
name={name}
|
|
596
|
+
surface={surface}
|
|
597
|
+
writableFieldCodes={importWritableFieldCodes}
|
|
598
|
+
onCompleted={() => state.list.query.refetch()}
|
|
599
|
+
/>
|
|
600
|
+
)}
|
|
601
|
+
<Button icon={<ExportOutlined />} loading={exporting} onClick={() => void handleExport()}>
|
|
592
602
|
导出
|
|
593
603
|
</Button>
|
|
594
604
|
</Space>
|
|
@@ -614,16 +624,54 @@ function GeneratedDesktopList({ definition }: { definition: GeneratedDefinition
|
|
|
614
624
|
</Dropdown>
|
|
615
625
|
</Space>
|
|
616
626
|
</div>
|
|
627
|
+
<ResourceBatchActions
|
|
628
|
+
canDelete={hasCapability(capabilities.delete)}
|
|
629
|
+
code={code}
|
|
630
|
+
name={name}
|
|
631
|
+
onClear={() => setSelectedRowKeys([])}
|
|
632
|
+
onCompleted={() => state.list.query.refetch()}
|
|
633
|
+
rows={selectedRows}
|
|
634
|
+
surface={surface}
|
|
635
|
+
writableFields={batchWritableFields}
|
|
636
|
+
/>
|
|
617
637
|
<Table<ResourceRecord>
|
|
618
638
|
columns={columns}
|
|
619
639
|
dataSource={rows}
|
|
620
640
|
loading={state.list.query.isFetching}
|
|
621
641
|
locale={{ emptyText: <Empty description={`暂无${name}`} /> }}
|
|
622
642
|
rowKey="id"
|
|
643
|
+
rowSelection={
|
|
644
|
+
canBatch
|
|
645
|
+
? {
|
|
646
|
+
selectedRowKeys,
|
|
647
|
+
onChange: (keys) => {
|
|
648
|
+
const next = keys.map(String);
|
|
649
|
+
if (next.length > 100) message.warning('单次最多选择 100 条记录');
|
|
650
|
+
setSelectedRowKeys(next.slice(0, 100));
|
|
651
|
+
},
|
|
652
|
+
}
|
|
653
|
+
: undefined
|
|
654
|
+
}
|
|
623
655
|
scroll={{ x: 'max-content' }}
|
|
624
656
|
size={density}
|
|
625
|
-
onChange={(_, __, sorter) => {
|
|
626
|
-
|
|
657
|
+
onChange={(_, __, sorter) => {
|
|
658
|
+
const item = Array.isArray(sorter) ? sorter[0] : sorter;
|
|
659
|
+
if (item?.field && item.order)
|
|
660
|
+
state.setSort({
|
|
661
|
+
field: String(item.field),
|
|
662
|
+
order: item.order === 'ascend' ? 'asc' : 'desc',
|
|
663
|
+
});
|
|
664
|
+
}}
|
|
665
|
+
pagination={{
|
|
666
|
+
current: state.page,
|
|
667
|
+
pageSize: state.pageSize,
|
|
668
|
+
total: state.list.result.total || 0,
|
|
669
|
+
showSizeChanger: true,
|
|
670
|
+
onChange: (next, size) => {
|
|
671
|
+
state.setPage(next);
|
|
672
|
+
state.setPageSize(size);
|
|
673
|
+
},
|
|
674
|
+
}}
|
|
627
675
|
/>
|
|
628
676
|
</ResourceListPage>
|
|
629
677
|
);
|
|
@@ -650,10 +698,23 @@ function GeneratedMobileList({ definition }: { definition: GeneratedDefinition }
|
|
|
650
698
|
}
|
|
651
699
|
};
|
|
652
700
|
return (
|
|
653
|
-
<MobileResourceListPage
|
|
701
|
+
<MobileResourceListPage
|
|
702
|
+
createCapability={capabilities.create}
|
|
703
|
+
createPath={`/${code}/new`}
|
|
704
|
+
description={`${code} 资源`}
|
|
705
|
+
onExport={() => void handleExport()}
|
|
706
|
+
readCapability={capabilities.read}
|
|
707
|
+
resource={code}
|
|
708
|
+
surface={surface}
|
|
709
|
+
title={name}
|
|
710
|
+
>
|
|
654
711
|
{state.list.query.isError && (
|
|
655
712
|
<Alert
|
|
656
|
-
action={
|
|
713
|
+
action={
|
|
714
|
+
<Button onClick={() => void state.list.query.refetch()} size="small">
|
|
715
|
+
重试
|
|
716
|
+
</Button>
|
|
717
|
+
}
|
|
657
718
|
description={state.list.query.error instanceof Error ? state.list.query.error.message : '请稍后重试或联系平台管理员'}
|
|
658
719
|
showIcon
|
|
659
720
|
title="数据读取失败"
|
|
@@ -661,16 +722,89 @@ function GeneratedMobileList({ definition }: { definition: GeneratedDefinition }
|
|
|
661
722
|
/>
|
|
662
723
|
)}
|
|
663
724
|
<div className="oxa-mobile-filter">
|
|
664
|
-
<Input
|
|
665
|
-
|
|
725
|
+
<Input
|
|
726
|
+
allowClear
|
|
727
|
+
placeholder="关键词"
|
|
728
|
+
value={state.draft.keyword}
|
|
729
|
+
onChange={(event) => state.setDraft((item) => ({ ...item, keyword: event.target.value }))}
|
|
730
|
+
onPressEnter={() => {
|
|
731
|
+
state.setPage(1);
|
|
732
|
+
state.setQuery(state.draft);
|
|
733
|
+
}}
|
|
734
|
+
/>
|
|
735
|
+
<Button
|
|
736
|
+
loading={state.list.query.isFetching}
|
|
737
|
+
icon={<SearchOutlined />}
|
|
738
|
+
onClick={() => {
|
|
739
|
+
state.setPage(1);
|
|
740
|
+
state.setQuery(state.draft);
|
|
741
|
+
}}
|
|
742
|
+
type="primary"
|
|
743
|
+
>
|
|
744
|
+
查询
|
|
745
|
+
</Button>
|
|
746
|
+
</div>
|
|
747
|
+
{state.list.query.isFetching ? (
|
|
748
|
+
<div className="oxa-page-loading">
|
|
749
|
+
<Spin />
|
|
750
|
+
</div>
|
|
751
|
+
) : rows.length ? (
|
|
752
|
+
<div className="oxa-mobile-record-list">
|
|
753
|
+
{rows.map((record) => (
|
|
754
|
+
<Card className="oxa-mobile-record-card" key={record.id} size="small">
|
|
755
|
+
<div className="oxa-mobile-record-heading">
|
|
756
|
+
<a onClick={() => navigate(`/${code}/${record.id}`)}>{String(record[fields[0]?.key || 'id'] || '-')}</a>
|
|
757
|
+
</div>
|
|
758
|
+
<div className="oxa-mobile-record-fields">
|
|
759
|
+
{fields.slice(1).map((field) => (
|
|
760
|
+
<div key={field.key}>
|
|
761
|
+
<Typography.Text type="secondary">{field.label}</Typography.Text>
|
|
762
|
+
<div>
|
|
763
|
+
<SurfaceFieldValue field={field} value={record[field.key]} />
|
|
764
|
+
</div>
|
|
765
|
+
</div>
|
|
766
|
+
))}
|
|
767
|
+
</div>
|
|
768
|
+
<Space>
|
|
769
|
+
<Button onClick={() => navigate(`/${code}/${record.id}`)} size="small">
|
|
770
|
+
查看
|
|
771
|
+
</Button>
|
|
772
|
+
{hasCapability(capabilities.update) && (
|
|
773
|
+
<Button onClick={() => navigate(`/${code}/${record.id}/edit`)} size="small">
|
|
774
|
+
编辑
|
|
775
|
+
</Button>
|
|
776
|
+
)}
|
|
777
|
+
</Space>
|
|
778
|
+
</Card>
|
|
779
|
+
))}
|
|
780
|
+
</div>
|
|
781
|
+
) : (
|
|
782
|
+
<Card>
|
|
783
|
+
<Empty description={`暂无${name}`} />
|
|
784
|
+
</Card>
|
|
785
|
+
)}
|
|
786
|
+
<div className="oxa-mobile-pagination">
|
|
787
|
+
<Button disabled={state.page <= 1} onClick={() => state.setPage((item) => item - 1)}>
|
|
788
|
+
上一页
|
|
789
|
+
</Button>
|
|
790
|
+
<Typography.Text>第 {state.page} 页</Typography.Text>
|
|
791
|
+
<Button disabled={rows.length < (surface.list?.defaultPageSize || 20)} onClick={() => state.setPage((item) => item + 1)}>
|
|
792
|
+
下一页
|
|
793
|
+
</Button>
|
|
666
794
|
</div>
|
|
667
|
-
{state.list.query.isFetching ? <div className="oxa-page-loading"><Spin /></div> : rows.length ? <div className="oxa-mobile-record-list">{rows.map(record => <Card className="oxa-mobile-record-card" key={record.id} size="small"><div className="oxa-mobile-record-heading"><a onClick={() => navigate(`/${code}/${record.id}`)}>{String(record[fields[0]?.key || 'id'] || '-')}</a></div><div className="oxa-mobile-record-fields">{fields.slice(1).map(field => <div key={field.key}><Typography.Text type="secondary">{field.label}</Typography.Text><div><SurfaceFieldValue field={field} value={record[field.key]} /></div></div>)}</div><Space><Button onClick={() => navigate(`/${code}/${record.id}`)} size="small">查看</Button>{hasCapability(capabilities.update) && <Button onClick={() => navigate(`/${code}/${record.id}/edit`)} size="small">编辑</Button>}</Space></Card>)}</div> : <Card><Empty description={`暂无${name}`} /></Card>}
|
|
668
|
-
<div className="oxa-mobile-pagination"><Button disabled={state.page <= 1} onClick={() => state.setPage(item => item - 1)}>上一页</Button><Typography.Text>第 {state.page} 页</Typography.Text><Button disabled={rows.length < (surface.list?.defaultPageSize || 20)} onClick={() => state.setPage(item => item + 1)}>下一页</Button></div>
|
|
669
795
|
</MobileResourceListPage>
|
|
670
796
|
);
|
|
671
797
|
}
|
|
672
798
|
|
|
673
|
-
function GeneratedResourceFormPage({
|
|
799
|
+
function GeneratedResourceFormPage({
|
|
800
|
+
definition,
|
|
801
|
+
mode,
|
|
802
|
+
variant,
|
|
803
|
+
}: {
|
|
804
|
+
definition: GeneratedDefinition;
|
|
805
|
+
mode: 'create' | 'edit';
|
|
806
|
+
variant: 'desktop' | 'mobile';
|
|
807
|
+
}) {
|
|
674
808
|
const { id = '' } = useParams();
|
|
675
809
|
const navigate = useNavigate();
|
|
676
810
|
const { message } = App.useApp();
|
|
@@ -678,32 +812,97 @@ function GeneratedResourceFormPage({ definition, mode, variant }: { definition:
|
|
|
678
812
|
const [form] = Form.useForm();
|
|
679
813
|
const create = useCreate<ResourceRecord>();
|
|
680
814
|
const update = useUpdate<ResourceRecord>();
|
|
681
|
-
const query = useOne<ResourceRecord>({
|
|
815
|
+
const query = useOne<ResourceRecord>({
|
|
816
|
+
resource: definition.code,
|
|
817
|
+
id,
|
|
818
|
+
queryOptions: { enabled: mode === 'edit' },
|
|
819
|
+
});
|
|
682
820
|
const record = query.result;
|
|
683
821
|
const { code, name, surface, capabilities } = definition;
|
|
684
|
-
useEffect(() => {
|
|
822
|
+
useEffect(() => {
|
|
823
|
+
if (record) form.setFieldsValue(normalizeRecordForForm(record, surface));
|
|
824
|
+
}, [form, record, surface]);
|
|
685
825
|
const listPath = `/${code}`;
|
|
686
826
|
const detailPath = `/${code}/${id}`;
|
|
687
827
|
const Page = variant === 'mobile' ? MobileResourceFormPage : ResourceFormPage;
|
|
688
828
|
const FieldControl = variant === 'mobile' ? MobileSurfaceFieldControl : SurfaceFieldControl;
|
|
689
|
-
if (!hasCapability(mode === 'create' ? capabilities.create : capabilities.update))
|
|
829
|
+
if (!hasCapability(mode === 'create' ? capabilities.create : capabilities.update))
|
|
830
|
+
return <Result status="403" title="当前平台用户无此操作权限" />;
|
|
690
831
|
const submit = async (values: Record<string, unknown>) => {
|
|
691
832
|
const next = normalizeFormValues(
|
|
692
833
|
Object.fromEntries(
|
|
693
834
|
Object.entries(values).filter(([key]) =>
|
|
694
|
-
fieldWritable(
|
|
835
|
+
fieldWritable(
|
|
836
|
+
surface.fields[key] ? { key, ...surface.fields[key] } : { key, label: key, widget: 'text' },
|
|
837
|
+
mode,
|
|
838
|
+
hasCapability,
|
|
839
|
+
identity.isAppSuperAdmin
|
|
840
|
+
)
|
|
695
841
|
)
|
|
696
842
|
),
|
|
697
843
|
surface
|
|
698
844
|
);
|
|
699
|
-
if (mode === 'create') {
|
|
700
|
-
|
|
845
|
+
if (mode === 'create') {
|
|
846
|
+
await create.mutateAsync({ resource: code, values: next });
|
|
847
|
+
message.success(`${name}已创建`);
|
|
848
|
+
navigate(listPath);
|
|
849
|
+
} else {
|
|
850
|
+
const result = await update.mutateAsync({
|
|
851
|
+
resource: code,
|
|
852
|
+
id,
|
|
853
|
+
values: next,
|
|
854
|
+
meta: { expectedRevision: record?.revision },
|
|
855
|
+
});
|
|
856
|
+
message.success('修改已保存');
|
|
857
|
+
navigate(`/${code}/${result.data.id}`);
|
|
858
|
+
}
|
|
701
859
|
};
|
|
702
860
|
return (
|
|
703
|
-
<Page
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
861
|
+
<Page
|
|
862
|
+
backLabel={mode === 'edit' ? `返回${name}详情` : `返回${name}`}
|
|
863
|
+
backPath={mode === 'edit' ? detailPath : listPath}
|
|
864
|
+
capability={mode === 'create' ? capabilities.create : capabilities.update}
|
|
865
|
+
detailPath={record ? detailPath : undefined}
|
|
866
|
+
loading={mode === 'edit' && query.query.isFetching}
|
|
867
|
+
mode={mode}
|
|
868
|
+
notFound={mode === 'edit' && !record}
|
|
869
|
+
resource={code}
|
|
870
|
+
surface={surface}
|
|
871
|
+
title={name}
|
|
872
|
+
>
|
|
873
|
+
<Form
|
|
874
|
+
className={variant === 'mobile' ? 'oxa-mobile-form' : 'oxa-form-full'}
|
|
875
|
+
form={form}
|
|
876
|
+
initialValues={undefined}
|
|
877
|
+
layout="vertical"
|
|
878
|
+
onFinish={(values) => void submit(values as Record<string, unknown>)}
|
|
879
|
+
>
|
|
880
|
+
<div className="oxa-sections">
|
|
881
|
+
{fieldsBySection(surface).map((group) => (
|
|
882
|
+
<Card className="oxa-section-card" key={group.section} title={group.section === 'default' ? '基本信息' : group.section}>
|
|
883
|
+
<div className="oxa-grid">
|
|
884
|
+
{group.fields.map((field) => (
|
|
885
|
+
<FieldControl
|
|
886
|
+
key={field.key}
|
|
887
|
+
disabled={!fieldWritable(field, mode, hasCapability, identity.isAppSuperAdmin)}
|
|
888
|
+
field={field}
|
|
889
|
+
operation={mode === 'create' ? 'create' : 'update'}
|
|
890
|
+
recordId={record?.id}
|
|
891
|
+
renderers={{
|
|
892
|
+
upload: (current, file, recordId) => createNativeResourceClient(code, surface).upload(current.key, file, recordId),
|
|
893
|
+
}}
|
|
894
|
+
/>
|
|
895
|
+
))}
|
|
896
|
+
</div>
|
|
897
|
+
</Card>
|
|
898
|
+
))}
|
|
899
|
+
</div>
|
|
900
|
+
<div className="oxa-actions">
|
|
901
|
+
<Button onClick={() => navigate(mode === 'edit' ? detailPath : listPath)}>取消</Button>
|
|
902
|
+
<Button htmlType="submit" loading={create.mutation.isPending || update.mutation.isPending} type="primary">
|
|
903
|
+
{mode === 'create' ? '保存' : '保存修改'}
|
|
904
|
+
</Button>
|
|
905
|
+
</div>
|
|
707
906
|
</Form>
|
|
708
907
|
</Page>
|
|
709
908
|
);
|
|
@@ -712,7 +911,11 @@ function GeneratedResourceFormPage({ definition, mode, variant }: { definition:
|
|
|
712
911
|
function GeneratedResourceDetailPage({ definition, variant }: { definition: GeneratedDefinition; variant: 'desktop' | 'mobile' }) {
|
|
713
912
|
const { id = '' } = useParams();
|
|
714
913
|
const navigate = useNavigate();
|
|
715
|
-
const query = useOne<ResourceRecord>({
|
|
914
|
+
const query = useOne<ResourceRecord>({
|
|
915
|
+
resource: definition.code,
|
|
916
|
+
id,
|
|
917
|
+
queryOptions: { retry: false },
|
|
918
|
+
});
|
|
716
919
|
const record = query.result;
|
|
717
920
|
const [auditEntries, setAuditEntries] = useState<DataAuditEntry[]>([]);
|
|
718
921
|
const [auditLoading, setAuditLoading] = useState(false);
|
|
@@ -724,14 +927,15 @@ function GeneratedResourceDetailPage({ definition, variant }: { definition: Gene
|
|
|
724
927
|
let active = true;
|
|
725
928
|
setAuditLoading(true);
|
|
726
929
|
setAuditError('');
|
|
727
|
-
void createNativeResourceClient(definition.code, definition.surface)
|
|
728
|
-
.
|
|
930
|
+
void createNativeResourceClient(definition.code, definition.surface)
|
|
931
|
+
.audit(record.id)
|
|
932
|
+
.then((page) => {
|
|
729
933
|
if (active) {
|
|
730
934
|
setAuditEntries(page.items || []);
|
|
731
935
|
setAuditLoadedFor(record.id);
|
|
732
936
|
}
|
|
733
937
|
})
|
|
734
|
-
.catch(reason => {
|
|
938
|
+
.catch((reason) => {
|
|
735
939
|
if (!active) return;
|
|
736
940
|
setAuditEntries([]);
|
|
737
941
|
setAuditError(reason instanceof Error ? reason.message : String(reason));
|
|
@@ -762,37 +966,25 @@ function GeneratedResourceDetailPage({ definition, variant }: { definition: Gene
|
|
|
762
966
|
type="error"
|
|
763
967
|
/>
|
|
764
968
|
) : auditLoading ? (
|
|
765
|
-
<div className="oxa-audit-loading"
|
|
969
|
+
<div className="oxa-audit-loading">
|
|
970
|
+
<Spin size="small" /> 正在读取操作记录
|
|
971
|
+
</div>
|
|
766
972
|
) : auditEntries.length ? (
|
|
767
|
-
auditEntries
|
|
768
|
-
.slice(0, 5)
|
|
769
|
-
.map(entry => (
|
|
770
|
-
<AuditEntry
|
|
771
|
-
entry={entry}
|
|
772
|
-
key={entry.id}
|
|
773
|
-
surface={definition.surface}
|
|
774
|
-
/>
|
|
775
|
-
))
|
|
973
|
+
auditEntries.slice(0, 5).map((entry) => <AuditEntry entry={entry} key={entry.id} surface={definition.surface} />)
|
|
776
974
|
) : (
|
|
777
975
|
<Typography.Text type="secondary">暂无变更记录</Typography.Text>
|
|
778
976
|
);
|
|
779
977
|
const content = record ? (
|
|
780
978
|
<div className="oxa-detail-layout">
|
|
781
979
|
<div className="oxa-detail-main">
|
|
782
|
-
{fieldsBySection(definition.surface).map(group => (
|
|
783
|
-
<Card
|
|
784
|
-
className="oxa-section-card"
|
|
785
|
-
key={group.section}
|
|
786
|
-
title={group.section === 'default' ? '基本信息' : group.section}
|
|
787
|
-
>
|
|
980
|
+
{fieldsBySection(definition.surface).map((group) => (
|
|
981
|
+
<Card className="oxa-section-card" key={group.section} title={group.section === 'default' ? '基本信息' : group.section}>
|
|
788
982
|
<Descriptions
|
|
789
983
|
column={{ xs: 1, sm: 2, lg: 3 }}
|
|
790
|
-
items={group.fields.map(field => ({
|
|
984
|
+
items={group.fields.map((field) => ({
|
|
791
985
|
key: field.key,
|
|
792
986
|
label: field.label,
|
|
793
|
-
children:
|
|
794
|
-
<SurfaceFieldValue field={field} value={record[field.key]} />
|
|
795
|
-
),
|
|
987
|
+
children: <SurfaceFieldValue field={field} value={record[field.key]} />,
|
|
796
988
|
}))}
|
|
797
989
|
/>
|
|
798
990
|
</Card>
|
|
@@ -814,11 +1006,7 @@ function GeneratedResourceDetailPage({ definition, variant }: { definition: Gene
|
|
|
814
1006
|
children: auditContent,
|
|
815
1007
|
},
|
|
816
1008
|
]}
|
|
817
|
-
onChange={keys =>
|
|
818
|
-
setAuditExpanded(
|
|
819
|
-
(Array.isArray(keys) ? keys : [keys]).includes('audit'),
|
|
820
|
-
)
|
|
821
|
-
}
|
|
1009
|
+
onChange={(keys) => setAuditExpanded((Array.isArray(keys) ? keys : [keys]).includes('audit'))}
|
|
822
1010
|
/>
|
|
823
1011
|
</div>
|
|
824
1012
|
</div>
|
|
@@ -826,9 +1014,7 @@ function GeneratedResourceDetailPage({ definition, variant }: { definition: Gene
|
|
|
826
1014
|
) : null;
|
|
827
1015
|
const hero = record ? (
|
|
828
1016
|
<div>
|
|
829
|
-
<Typography.Title level={variant === 'mobile' ? 3 : 2}>
|
|
830
|
-
{definition.name}详情
|
|
831
|
-
</Typography.Title>
|
|
1017
|
+
<Typography.Title level={variant === 'mobile' ? 3 : 2}>{definition.name}详情</Typography.Title>
|
|
832
1018
|
</div>
|
|
833
1019
|
) : null;
|
|
834
1020
|
const props = {
|
|
@@ -837,20 +1023,10 @@ function GeneratedResourceDetailPage({ definition, variant }: { definition: Gene
|
|
|
837
1023
|
backPath: `/${definition.code}`,
|
|
838
1024
|
editCapability: definition.capabilities.update,
|
|
839
1025
|
editPath: `/${definition.code}/${id}/edit`,
|
|
840
|
-
error:
|
|
841
|
-
query.query.isError || (!query.query.isFetching && !record)
|
|
842
|
-
? query.query.error?.message || '记录不存在'
|
|
843
|
-
: undefined,
|
|
1026
|
+
error: query.query.isError || (!query.query.isFetching && !record) ? query.query.error?.message || '记录不存在' : undefined,
|
|
844
1027
|
hero,
|
|
845
1028
|
loading: query.query.isFetching,
|
|
846
|
-
meta: record ? (
|
|
847
|
-
<span>
|
|
848
|
-
最近更新{' '}
|
|
849
|
-
{record.updated_at
|
|
850
|
-
? new Date(record.updated_at).toLocaleString('zh-CN')
|
|
851
|
-
: '-'}
|
|
852
|
-
</span>
|
|
853
|
-
) : null,
|
|
1029
|
+
meta: record ? <span>最近更新 {record.updated_at ? new Date(record.updated_at).toLocaleString('zh-CN') : '-'}</span> : null,
|
|
854
1030
|
onAuditOpen: () => setAuditExpanded(true),
|
|
855
1031
|
readCapability: definition.capabilities.read,
|
|
856
1032
|
resource: definition.code,
|