openxiangda-cli 2.0.0-alpha.59 → 2.0.0-alpha.61

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.
@@ -0,0 +1,431 @@
1
+ import {
2
+ ArrowLeftOutlined,
3
+ EditOutlined,
4
+ ExportOutlined,
5
+ EyeOutlined,
6
+ HistoryOutlined,
7
+ ImportOutlined,
8
+ PlusOutlined,
9
+ } from '@ant-design/icons';
10
+ import {
11
+ Alert,
12
+ App,
13
+ Button,
14
+ Card,
15
+ Result,
16
+ Space,
17
+ Spin,
18
+ Typography,
19
+ } from 'antd';
20
+ import type { DataResourceSurface } from 'openxiangda-contracts/browser';
21
+ import type { ReactNode } from 'react';
22
+ import { useNavigate } from 'react-router-dom';
23
+ import { Shell } from '../../Shell';
24
+ import { useRuntime } from '../../runtime';
25
+
26
+ export interface StandardResourcePageProps {
27
+ resource: string;
28
+ surface?: DataResourceSurface;
29
+ title: string;
30
+ description?: string;
31
+ }
32
+
33
+ interface ResourceListPageProps extends StandardResourcePageProps {
34
+ readCapability: string;
35
+ createCapability?: string;
36
+ createPath?: string;
37
+ onExport?: () => void;
38
+ importDisabledReason?: string;
39
+ children: ReactNode;
40
+ }
41
+
42
+ /**
43
+ * The standard admin list shell. Data fetching stays in the app's Refine
44
+ * provider; this component owns the stable page chrome and CRUD actions.
45
+ */
46
+ export function ResourceListPage({
47
+ resource,
48
+ surface,
49
+ title,
50
+ description,
51
+ readCapability,
52
+ createCapability,
53
+ createPath,
54
+ onExport,
55
+ importDisabledReason = '导入能力将在批量事务合同落地后启用',
56
+ children,
57
+ }: ResourceListPageProps) {
58
+ const navigate = useNavigate();
59
+ const { hasCapability } = useRuntime();
60
+ const { message } = App.useApp();
61
+ if (!hasCapability(readCapability)) {
62
+ return (
63
+ <Shell>
64
+ <Result status="403" title={`当前平台用户无${title}页面权限`} />
65
+ </Shell>
66
+ );
67
+ }
68
+ const canCreate = createCapability ? hasCapability(createCapability) : false;
69
+ return (
70
+ <Shell>
71
+ <Card
72
+ className="oxa-list-card"
73
+ data-resource={resource}
74
+ data-surface-layout={surface?.list ? 'standard' : undefined}
75
+ >
76
+ <Space orientation="vertical" size={16} style={{ width: '100%' }}>
77
+ <div className="oxa-page-heading">
78
+ <div>
79
+ <h2 className="oxa-title">{title}</h2>
80
+ {description && <div className="oxa-muted">{description}</div>}
81
+ </div>
82
+ <Space>
83
+ <Button
84
+ disabled
85
+ icon={<ImportOutlined />}
86
+ title={importDisabledReason}
87
+ >
88
+ 导入
89
+ </Button>
90
+ <Button
91
+ icon={<ExportOutlined />}
92
+ onClick={() => {
93
+ if (onExport) onExport();
94
+ else message.info('导出将使用当前 Data API 查询条件');
95
+ }}
96
+ >
97
+ 导出
98
+ </Button>
99
+ {canCreate && createPath && (
100
+ <Button
101
+ icon={<PlusOutlined />}
102
+ onClick={() => navigate(createPath)}
103
+ type="primary"
104
+ >
105
+ 新增
106
+ </Button>
107
+ )}
108
+ </Space>
109
+ </div>
110
+ {children}
111
+ </Space>
112
+ </Card>
113
+ </Shell>
114
+ );
115
+ }
116
+
117
+ /** Mobile list shell: the same capability gate with a compact, app-owned list. */
118
+ export function MobileResourceListPage({
119
+ resource,
120
+ surface,
121
+ title,
122
+ description,
123
+ readCapability,
124
+ createCapability,
125
+ createPath,
126
+ onExport,
127
+ children,
128
+ }: ResourceListPageProps) {
129
+ const navigate = useNavigate();
130
+ const { hasCapability } = useRuntime();
131
+ if (!hasCapability(readCapability)) {
132
+ return <div className="oxa-mobile-page"><Result status="403" title={`当前平台用户无${title}页面权限`} /></div>;
133
+ }
134
+ return (
135
+ <div className="oxa-mobile-page" data-resource={resource} data-surface-layout={surface?.list ? 'standard' : undefined}>
136
+ <header className="oxa-mobile-header">
137
+ <div>
138
+ <Typography.Title level={3}>{title}</Typography.Title>
139
+ {description && <Typography.Text type="secondary">{description}</Typography.Text>}
140
+ </div>
141
+ <Space>
142
+ {onExport && <Button icon={<ExportOutlined />} onClick={onExport}>导出</Button>}
143
+ {createCapability && createPath && hasCapability(createCapability) && (
144
+ <Button icon={<PlusOutlined />} onClick={() => navigate(createPath)} type="primary">新增</Button>
145
+ )}
146
+ </Space>
147
+ </header>
148
+ <Alert title="移动端使用独立列表交互;数据查询、权限和 PC 端保持一致。" showIcon type="info" />
149
+ {children}
150
+ </div>
151
+ );
152
+ }
153
+
154
+ interface ResourceFormPageProps extends StandardResourcePageProps {
155
+ mode: 'create' | 'edit';
156
+ capability: string;
157
+ loading?: boolean;
158
+ notFound?: boolean;
159
+ backPath: string;
160
+ backLabel: string;
161
+ detailPath?: string;
162
+ recordSummary?: ReactNode;
163
+ children: ReactNode;
164
+ }
165
+
166
+ /** The standard create/edit lifecycle: capability, loading, not-found, hero. */
167
+ export function ResourceFormPage({
168
+ resource,
169
+ surface,
170
+ title,
171
+ mode,
172
+ capability,
173
+ loading = false,
174
+ notFound = false,
175
+ backPath,
176
+ backLabel,
177
+ detailPath,
178
+ recordSummary,
179
+ children,
180
+ }: ResourceFormPageProps) {
181
+ const navigate = useNavigate();
182
+ const { hasCapability } = useRuntime();
183
+ if (!hasCapability(capability)) {
184
+ return (
185
+ <Shell>
186
+ <Result status="403" title="当前平台用户无此操作权限" />
187
+ </Shell>
188
+ );
189
+ }
190
+ if (loading) {
191
+ return (
192
+ <Shell>
193
+ <div className="oxa-page-loading">
194
+ <Spin />
195
+ </div>
196
+ </Shell>
197
+ );
198
+ }
199
+ if (notFound) {
200
+ return (
201
+ <Shell>
202
+ <Result status="404" title="记录不存在或不在数据范围内" />
203
+ </Shell>
204
+ );
205
+ }
206
+ return (
207
+ <Shell>
208
+ <Card
209
+ className="oxa-edit-hero"
210
+ data-resource={resource}
211
+ data-surface-layout={surface?.form?.layout || 'flat'}
212
+ >
213
+ <div className="oxa-edit-hero-title">
214
+ <div>
215
+ <Button icon={<ArrowLeftOutlined />} onClick={() => navigate(backPath)} type="link">
216
+ {backLabel}
217
+ </Button>
218
+ <Typography.Title level={2}>
219
+ {mode === 'create' ? `新增${title}` : `编辑${title}`}
220
+ </Typography.Title>
221
+ {recordSummary && <Typography.Text type="secondary">{recordSummary}</Typography.Text>}
222
+ </div>
223
+ {detailPath && (
224
+ <Button icon={<EyeOutlined />} onClick={() => navigate(detailPath)}>
225
+ 查看详情
226
+ </Button>
227
+ )}
228
+ </div>
229
+ <Alert
230
+ title="仅显示当前角色可编辑的字段;保存时将校验数据版本与权限范围。"
231
+ showIcon
232
+ type="info"
233
+ />
234
+ </Card>
235
+ {children}
236
+ </Shell>
237
+ );
238
+ }
239
+
240
+ export function MobileResourceFormPage({
241
+ resource,
242
+ surface,
243
+ title,
244
+ mode,
245
+ capability,
246
+ loading = false,
247
+ notFound = false,
248
+ backPath,
249
+ backLabel,
250
+ recordSummary,
251
+ children,
252
+ }: ResourceFormPageProps) {
253
+ const navigate = useNavigate();
254
+ const { hasCapability } = useRuntime();
255
+ if (!hasCapability(capability)) {
256
+ return <div className="oxa-mobile-page"><Result status="403" title="当前平台用户无此操作权限" /></div>;
257
+ }
258
+ if (loading) {
259
+ return <div className="oxa-mobile-page"><Spin /></div>;
260
+ }
261
+ if (notFound) {
262
+ return <div className="oxa-mobile-page"><Result status="404" title="记录不存在或不在数据范围内" /></div>;
263
+ }
264
+ return (
265
+ <div
266
+ className="oxa-mobile-page"
267
+ data-resource={resource}
268
+ data-surface-layout={surface?.form?.layout || 'flat'}
269
+ >
270
+ <header className="oxa-mobile-header">
271
+ <Button icon={<ArrowLeftOutlined />} onClick={() => navigate(backPath)} type="link">
272
+ {backLabel}
273
+ </Button>
274
+ <Typography.Title level={3}>{mode === 'create' ? `新增${title}` : `编辑${title}`}</Typography.Title>
275
+ {recordSummary && <Typography.Text type="secondary">{recordSummary}</Typography.Text>}
276
+ </header>
277
+ <Alert title="移动端使用独立控件,提交值与 PC 端保持一致。" showIcon type="info" />
278
+ {children}
279
+ </div>
280
+ );
281
+ }
282
+
283
+ interface ResourceDetailPageProps extends StandardResourcePageProps {
284
+ readCapability: string;
285
+ loading?: boolean;
286
+ error?: string;
287
+ backPath: string;
288
+ backLabel: string;
289
+ editCapability?: string;
290
+ editPath?: string;
291
+ auditTargetId?: string;
292
+ hero: ReactNode;
293
+ meta?: ReactNode;
294
+ children: ReactNode;
295
+ }
296
+
297
+ /** The standard detail lifecycle and audit/edit navigation. */
298
+ export function ResourceDetailPage({
299
+ resource,
300
+ surface,
301
+ title,
302
+ readCapability,
303
+ loading = false,
304
+ error,
305
+ backPath,
306
+ backLabel,
307
+ editCapability,
308
+ editPath,
309
+ auditTargetId,
310
+ hero,
311
+ meta,
312
+ children,
313
+ }: ResourceDetailPageProps) {
314
+ const navigate = useNavigate();
315
+ const { hasCapability } = useRuntime();
316
+ if (!hasCapability(readCapability)) {
317
+ return (
318
+ <Shell>
319
+ <Result status="403" title={`当前平台用户无${title}页面权限`} />
320
+ </Shell>
321
+ );
322
+ }
323
+ if (loading) {
324
+ return (
325
+ <Shell>
326
+ <div className="oxa-page-loading">
327
+ <Spin />
328
+ </div>
329
+ </Shell>
330
+ );
331
+ }
332
+ if (error) {
333
+ return (
334
+ <Shell>
335
+ <Result status="403" subTitle={error} title="无法访问" />
336
+ </Shell>
337
+ );
338
+ }
339
+ return (
340
+ <Shell>
341
+ <Card
342
+ className="oxa-detail-hero"
343
+ data-resource={resource}
344
+ data-surface-layout={surface?.detail?.layout || 'flat'}
345
+ >
346
+ <div className="oxa-detail-hero-top">
347
+ <Button
348
+ aria-label="返回列表"
349
+ icon={<ArrowLeftOutlined />}
350
+ onClick={() => navigate(backPath)}
351
+ type="link"
352
+ >
353
+ {backLabel}
354
+ </Button>
355
+ <Space>
356
+ {auditTargetId && (
357
+ <Button
358
+ icon={<HistoryOutlined />}
359
+ onClick={() =>
360
+ document
361
+ .getElementById(auditTargetId)
362
+ ?.scrollIntoView({ behavior: 'smooth', block: 'center' })
363
+ }
364
+ >
365
+ 操作记录
366
+ </Button>
367
+ )}
368
+ {editCapability && editPath && hasCapability(editCapability) && (
369
+ <Button
370
+ icon={<EditOutlined />}
371
+ onClick={() => navigate(editPath)}
372
+ type="primary"
373
+ >
374
+ 编辑
375
+ </Button>
376
+ )}
377
+ </Space>
378
+ </div>
379
+ <div className="oxa-detail-hero-main">
380
+ {hero}
381
+ {meta && <Space className="oxa-record-meta" separator={<span>·</span>}>{meta}</Space>}
382
+ </div>
383
+ </Card>
384
+ {children}
385
+ </Shell>
386
+ );
387
+ }
388
+
389
+ /** Mobile detail shell: keeps the resource lifecycle and audit/edit actions stable. */
390
+ export function MobileResourceDetailPage({
391
+ resource,
392
+ surface,
393
+ title,
394
+ readCapability,
395
+ loading = false,
396
+ error,
397
+ backPath,
398
+ backLabel,
399
+ editCapability,
400
+ editPath,
401
+ auditTargetId,
402
+ hero,
403
+ meta,
404
+ children,
405
+ }: ResourceDetailPageProps) {
406
+ const navigate = useNavigate();
407
+ const { hasCapability } = useRuntime();
408
+ if (!hasCapability(readCapability)) {
409
+ return <div className="oxa-mobile-page"><Result status="403" title={`当前平台用户无${title}页面权限`} /></div>;
410
+ }
411
+ if (loading) return <div className="oxa-mobile-page"><Spin /></div>;
412
+ if (error) return <div className="oxa-mobile-page"><Result status="403" subTitle={error} title="无法访问" /></div>;
413
+ return (
414
+ <div className="oxa-mobile-page" data-resource={resource} data-surface-layout={surface?.detail?.layout || 'flat'}>
415
+ <header className="oxa-mobile-header">
416
+ <Button aria-label="返回列表" icon={<ArrowLeftOutlined />} onClick={() => navigate(backPath)} type="link">
417
+ {backLabel}
418
+ </Button>
419
+ <Space>
420
+ {auditTargetId && <Button icon={<HistoryOutlined />} onClick={() => document.getElementById(auditTargetId)?.scrollIntoView({ behavior: 'smooth', block: 'center' })}>操作记录</Button>}
421
+ {editCapability && editPath && hasCapability(editCapability) && <Button icon={<EditOutlined />} onClick={() => navigate(editPath)} type="primary">编辑</Button>}
422
+ </Space>
423
+ </header>
424
+ <Card className="oxa-detail-hero" data-resource-hero="mobile">
425
+ {hero}
426
+ {meta && <Space className="oxa-record-meta" separator={<span>·</span>}>{meta}</Space>}
427
+ </Card>
428
+ {children}
429
+ </div>
430
+ );
431
+ }