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.
- package/dist/create-workspace.d.ts +1 -0
- package/dist/create-workspace.d.ts.map +1 -1
- package/package.json +4 -4
- package/template/apps/server/package.json +1 -1
- package/template/apps/web/e2e/instruments.spec.ts +1 -1
- package/template/apps/web/package.json +1 -1
- package/template/apps/web/scripts/check.mjs +5 -2
- package/template/apps/web/src/InstrumentDetailPage.tsx +119 -77
- package/template/apps/web/src/InstrumentForm.tsx +119 -314
- package/template/apps/web/src/InstrumentFormPage.tsx +40 -75
- package/template/apps/web/src/InstrumentListPage.tsx +264 -192
- package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +285 -0
- package/template/apps/web/src/components/resource/StandardResourcePages.tsx +431 -0
- package/template/apps/web/src/components/resource/SurfaceFields.tsx +469 -0
- package/template/apps/web/src/data-provider.ts +85 -1
- package/template/apps/web/src/fields.ts +9 -74
- package/template/apps/web/src/instrument.ts +1 -0
- package/template/apps/web/src/main.tsx +32 -2
- package/template/apps/web/src/platform-client.ts +95 -2
- package/template/apps/web/src/styles.css +97 -0
- package/template/apps/web/test/contracts.test.ts +80 -4
- package/template/package.json +3 -3
- package/template/packages/contracts/src/generated.ts +674 -0
- package/template/platform/data/instrument-surface.ts +125 -0
- package/template/platform/data/instruments.ts +2 -0
|
@@ -2,9 +2,6 @@ import {
|
|
|
2
2
|
DeleteOutlined,
|
|
3
3
|
EditOutlined,
|
|
4
4
|
EyeOutlined,
|
|
5
|
-
ExportOutlined,
|
|
6
|
-
ImportOutlined,
|
|
7
|
-
PlusOutlined,
|
|
8
5
|
SearchOutlined,
|
|
9
6
|
} from '@ant-design/icons';
|
|
10
7
|
import { useDelete, useList } from '@refinedev/core';
|
|
@@ -12,16 +9,15 @@ import {
|
|
|
12
9
|
App,
|
|
13
10
|
Button,
|
|
14
11
|
Card,
|
|
15
|
-
|
|
12
|
+
Empty,
|
|
16
13
|
Input,
|
|
17
|
-
InputNumber,
|
|
18
14
|
Popconfirm,
|
|
19
|
-
Result,
|
|
20
15
|
Select,
|
|
21
16
|
Space,
|
|
17
|
+
Spin,
|
|
22
18
|
Table,
|
|
23
19
|
Tag,
|
|
24
|
-
|
|
20
|
+
Typography,
|
|
25
21
|
type TableColumnsType,
|
|
26
22
|
} from 'antd';
|
|
27
23
|
import { useMemo, useState } from 'react';
|
|
@@ -30,34 +26,62 @@ import {
|
|
|
30
26
|
AuthoritativeSelector,
|
|
31
27
|
ResolvedValueText,
|
|
32
28
|
} from './AuthoritativeSelector';
|
|
33
|
-
import {
|
|
34
|
-
|
|
29
|
+
import {
|
|
30
|
+
listSurfaceFields,
|
|
31
|
+
SurfaceFilterControl,
|
|
32
|
+
SurfaceFieldValue,
|
|
33
|
+
type SurfaceField,
|
|
34
|
+
} from './components/resource/SurfaceFields';
|
|
35
|
+
import {
|
|
36
|
+
MobileResourceListPage,
|
|
37
|
+
ResourceListPage,
|
|
38
|
+
} from './components/resource/StandardResourcePages';
|
|
35
39
|
import {
|
|
36
40
|
INSTRUMENT_CAPABILITIES,
|
|
37
41
|
type InstrumentListQuery,
|
|
38
42
|
type InstrumentRecord,
|
|
39
43
|
} from './instrument';
|
|
44
|
+
import { instrumentSurface } from '../../../platform/data/instrument-surface.js';
|
|
40
45
|
import { useRuntime } from './runtime';
|
|
41
46
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
]
|
|
47
|
+
const defaultSort = instrumentSurface.list?.defaultSort || {
|
|
48
|
+
field: 'instrumentCode',
|
|
49
|
+
order: 'asc' as const,
|
|
50
|
+
};
|
|
51
|
+
const listFields = listSurfaceFields(instrumentSurface.fields);
|
|
52
|
+
const searchableFields = (instrumentSurface.list?.searchableFields || []).map(
|
|
53
|
+
key => ({ label: instrumentSurface.fields[key]?.label || key, value: key })
|
|
54
|
+
);
|
|
55
|
+
const listWidths: Record<string, number> = {
|
|
56
|
+
image: 84,
|
|
57
|
+
chineseName: 210,
|
|
58
|
+
specModel: 160,
|
|
59
|
+
instrumentCode: 140,
|
|
60
|
+
assetCode: 140,
|
|
61
|
+
collegeId: 140,
|
|
62
|
+
instrumentAdminIds: 130,
|
|
63
|
+
instrumentStatus: 100,
|
|
64
|
+
openToOutside: 90,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function InstrumentListPage({ variant = 'desktop' }: { variant?: 'desktop' | 'mobile' }) {
|
|
68
|
+
return variant === 'mobile' ? <MobileInstrumentListPage /> : <DesktopInstrumentListPage />;
|
|
69
|
+
}
|
|
48
70
|
|
|
49
|
-
|
|
71
|
+
function DesktopInstrumentListPage() {
|
|
50
72
|
const navigate = useNavigate();
|
|
51
73
|
const { hasCapability } = useRuntime();
|
|
52
74
|
const { message } = App.useApp();
|
|
53
75
|
const [page, setPage] = useState(1);
|
|
54
|
-
const [pageSize, setPageSize] = useState(
|
|
76
|
+
const [pageSize, setPageSize] = useState(
|
|
77
|
+
instrumentSurface.list?.defaultPageSize || 20
|
|
78
|
+
);
|
|
55
79
|
const [draft, setDraft] = useState<Partial<InstrumentListQuery>>({});
|
|
56
80
|
const [query, setQuery] = useState<Partial<InstrumentListQuery>>({});
|
|
57
81
|
const [sort, setSort] = useState<{
|
|
58
82
|
field: string;
|
|
59
83
|
order: 'asc' | 'desc';
|
|
60
|
-
}>({ field:
|
|
84
|
+
}>({ field: defaultSort.field, order: defaultSort.order || 'asc' });
|
|
61
85
|
const list = useList<InstrumentRecord>({
|
|
62
86
|
resource: 'instruments',
|
|
63
87
|
pagination: { currentPage: page, pageSize },
|
|
@@ -67,70 +91,20 @@ export function InstrumentListPage() {
|
|
|
67
91
|
const remove = useDelete();
|
|
68
92
|
const data = (list.result.data || []) as InstrumentRecord[];
|
|
69
93
|
const total = list.result.total || 0;
|
|
70
|
-
const canCreate = hasCapability(INSTRUMENT_CAPABILITIES.create);
|
|
71
94
|
const canUpdate = hasCapability(INSTRUMENT_CAPABILITIES.update);
|
|
72
95
|
const canDelete = hasCapability(INSTRUMENT_CAPABILITIES.delete);
|
|
73
|
-
const columns = useMemo<TableColumnsType<InstrumentRecord>>(
|
|
74
|
-
(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
width: 210,
|
|
86
|
-
sorter: true,
|
|
87
|
-
render: (value, record) => (
|
|
88
|
-
<a onClick={() => navigate(`/instruments/${record.id}`)}>{value}</a>
|
|
89
|
-
),
|
|
90
|
-
},
|
|
91
|
-
{ title: '规格型号', dataIndex: 'specModel', width: 160 },
|
|
92
|
-
{ title: '仪器编码', dataIndex: 'instrumentCode', width: 140 },
|
|
93
|
-
{ title: '资产编码', dataIndex: 'assetCode', width: 140 },
|
|
94
|
-
{
|
|
95
|
-
title: '所属学院',
|
|
96
|
-
dataIndex: 'collegeId',
|
|
97
|
-
width: 140,
|
|
98
|
-
render: value => <ResolvedValueText source="college" value={value} />,
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
title: '仪器管理员',
|
|
102
|
-
dataIndex: 'instrumentAdminIds',
|
|
103
|
-
width: 130,
|
|
104
|
-
render: values => (
|
|
105
|
-
<ResolvedValueText source="user" value={values as string[]} />
|
|
106
|
-
),
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
title: '仪器状态',
|
|
110
|
-
dataIndex: 'instrumentStatus',
|
|
111
|
-
width: 100,
|
|
112
|
-
render: value => (
|
|
113
|
-
<Tag
|
|
114
|
-
color={
|
|
115
|
-
value === 'normal'
|
|
116
|
-
? 'green'
|
|
117
|
-
: value === 'maintenance'
|
|
118
|
-
? 'orange'
|
|
119
|
-
: 'default'
|
|
120
|
-
}
|
|
121
|
-
>
|
|
122
|
-
{statuses.find(item => item.value === value)?.label || value}
|
|
123
|
-
</Tag>
|
|
124
|
-
),
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
title: '对外开放',
|
|
128
|
-
dataIndex: 'openToOutside',
|
|
129
|
-
width: 90,
|
|
130
|
-
render: value => (
|
|
131
|
-
<Tag color={value ? 'green' : 'default'}>{value ? '是' : '否'}</Tag>
|
|
132
|
-
),
|
|
133
|
-
},
|
|
96
|
+
const columns = useMemo<TableColumnsType<InstrumentRecord>>(() => {
|
|
97
|
+
const standardColumns = listFields.map(field => ({
|
|
98
|
+
title: field.label,
|
|
99
|
+
dataIndex: field.key,
|
|
100
|
+
key: field.key,
|
|
101
|
+
width: listWidths[field.key] || 140,
|
|
102
|
+
sorter: Boolean(field.sortable),
|
|
103
|
+
render: (value: unknown, record: InstrumentRecord) =>
|
|
104
|
+
renderListValue(field, value, record, navigate),
|
|
105
|
+
}));
|
|
106
|
+
return [
|
|
107
|
+
...standardColumns,
|
|
134
108
|
{
|
|
135
109
|
title: '更新时间',
|
|
136
110
|
dataIndex: 'updated_at',
|
|
@@ -188,53 +162,30 @@ export function InstrumentListPage() {
|
|
|
188
162
|
</Space>
|
|
189
163
|
),
|
|
190
164
|
},
|
|
191
|
-
]
|
|
192
|
-
|
|
193
|
-
);
|
|
194
|
-
if (!hasCapability(INSTRUMENT_CAPABILITIES.read)) {
|
|
195
|
-
return (
|
|
196
|
-
<Shell>
|
|
197
|
-
<Result status="403" title="当前平台用户无仪器页面权限" />
|
|
198
|
-
</Shell>
|
|
199
|
-
);
|
|
200
|
-
}
|
|
165
|
+
];
|
|
166
|
+
}, [canDelete, canUpdate, list.query, message, navigate, remove]);
|
|
201
167
|
return (
|
|
202
|
-
<
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
</div>
|
|
214
|
-
<Space>
|
|
215
|
-
<Tooltip title="批量事务合同落地后启用,不用自定义接口绕过 Data API">
|
|
216
|
-
<Button disabled icon={<ImportOutlined />}>
|
|
217
|
-
导入
|
|
218
|
-
</Button>
|
|
219
|
-
</Tooltip>
|
|
220
|
-
<Button
|
|
221
|
-
icon={<ExportOutlined />}
|
|
222
|
-
onClick={() => message.info('导出将使用当前 Data API 查询条件')}
|
|
223
|
-
>
|
|
224
|
-
导出
|
|
225
|
-
</Button>
|
|
226
|
-
{canCreate && (
|
|
227
|
-
<Button
|
|
228
|
-
type="primary"
|
|
229
|
-
icon={<PlusOutlined />}
|
|
230
|
-
onClick={() => navigate('/instruments/new')}
|
|
231
|
-
>
|
|
232
|
-
新增仪器
|
|
233
|
-
</Button>
|
|
234
|
-
)}
|
|
235
|
-
</Space>
|
|
236
|
-
</div>
|
|
168
|
+
<ResourceListPage
|
|
169
|
+
createCapability={INSTRUMENT_CAPABILITIES.create}
|
|
170
|
+
createPath="/instruments/new"
|
|
171
|
+
description="统一管理仪器档案、归属、管理员与开放状态"
|
|
172
|
+
importDisabledReason="批量事务合同落地后启用,不用自定义接口绕过 Data API"
|
|
173
|
+
onExport={() => message.info('导出将使用当前 Data API 查询条件')}
|
|
174
|
+
readCapability={INSTRUMENT_CAPABILITIES.read}
|
|
175
|
+
resource="instruments"
|
|
176
|
+
surface={instrumentSurface}
|
|
177
|
+
title="仪器资源"
|
|
178
|
+
>
|
|
237
179
|
<div className="oxa-filter-grid">
|
|
180
|
+
<Select
|
|
181
|
+
allowClear
|
|
182
|
+
options={searchableFields}
|
|
183
|
+
placeholder="搜索字段"
|
|
184
|
+
value={draft.searchField}
|
|
185
|
+
onChange={value =>
|
|
186
|
+
setDraft(item => ({ ...item, searchField: value }))
|
|
187
|
+
}
|
|
188
|
+
/>
|
|
238
189
|
<Input
|
|
239
190
|
allowClear
|
|
240
191
|
prefix={<SearchOutlined />}
|
|
@@ -244,92 +195,67 @@ export function InstrumentListPage() {
|
|
|
244
195
|
setDraft(value => ({ ...value, keyword: event.target.value }))
|
|
245
196
|
}
|
|
246
197
|
/>
|
|
247
|
-
<
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
198
|
+
<SurfaceFilterControl
|
|
199
|
+
field={surfaceField('collegeId')}
|
|
200
|
+
renderers={{
|
|
201
|
+
renderFilter: context => (
|
|
202
|
+
<AuthoritativeSelector
|
|
203
|
+
operation="update"
|
|
204
|
+
placeholder="所属学院"
|
|
205
|
+
source="college"
|
|
206
|
+
value={context.value as string | undefined}
|
|
207
|
+
onChange={value => context.onChange(typeof value === 'string' ? value : undefined)}
|
|
208
|
+
/>
|
|
209
|
+
),
|
|
210
|
+
}}
|
|
252
211
|
onChange={value =>
|
|
253
212
|
setDraft(item => ({
|
|
254
213
|
...item,
|
|
255
214
|
collegeId: typeof value === 'string' ? value : undefined,
|
|
256
215
|
}))
|
|
257
216
|
}
|
|
217
|
+
value={draft.collegeId}
|
|
258
218
|
/>
|
|
259
|
-
<
|
|
260
|
-
|
|
261
|
-
placeholder="仪器管理员"
|
|
262
|
-
value={draft.instrumentAdminId}
|
|
219
|
+
<SurfaceFilterControl
|
|
220
|
+
field={surfaceField('instrumentAdminIds')}
|
|
263
221
|
onChange={value =>
|
|
264
222
|
setDraft(item => ({
|
|
265
223
|
...item,
|
|
266
|
-
instrumentAdminId:
|
|
267
|
-
typeof value === 'string' ? value : undefined,
|
|
224
|
+
instrumentAdminId: typeof value === 'string' ? value : undefined,
|
|
268
225
|
}))
|
|
269
226
|
}
|
|
227
|
+
value={draft.instrumentAdminId}
|
|
270
228
|
/>
|
|
271
|
-
<
|
|
272
|
-
|
|
273
|
-
options={statuses}
|
|
274
|
-
placeholder="仪器状态"
|
|
275
|
-
value={draft.instrumentStatus}
|
|
229
|
+
<SurfaceFilterControl
|
|
230
|
+
field={surfaceField('instrumentStatus')}
|
|
276
231
|
onChange={value =>
|
|
277
|
-
setDraft(item => ({ ...item, instrumentStatus: value }))
|
|
232
|
+
setDraft(item => ({ ...item, instrumentStatus: value as string | undefined }))
|
|
278
233
|
}
|
|
234
|
+
value={draft.instrumentStatus}
|
|
279
235
|
/>
|
|
280
|
-
<
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
{
|
|
284
|
-
|
|
285
|
-
]}
|
|
286
|
-
placeholder="是否对外开放"
|
|
236
|
+
<SurfaceFilterControl
|
|
237
|
+
field={surfaceField('openToOutside')}
|
|
238
|
+
onChange={value =>
|
|
239
|
+
setDraft(item => ({ ...item, openToOutside: value as boolean | undefined }))
|
|
240
|
+
}
|
|
287
241
|
value={draft.openToOutside}
|
|
242
|
+
/>
|
|
243
|
+
<SurfaceFilterControl
|
|
244
|
+
field={surfaceField('enabledDate')}
|
|
288
245
|
onChange={value =>
|
|
289
|
-
setDraft(item => ({ ...item,
|
|
246
|
+
setDraft(item => ({ ...item, enabledDate: value as [string, string] | undefined }))
|
|
290
247
|
}
|
|
248
|
+
range
|
|
249
|
+
value={draft.enabledDate}
|
|
291
250
|
/>
|
|
292
|
-
<
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
enabledDate:
|
|
297
|
-
values[0] && values[1]
|
|
298
|
-
? [values[0], values[1]]
|
|
299
|
-
: undefined,
|
|
300
|
-
}))
|
|
251
|
+
<SurfaceFilterControl
|
|
252
|
+
field={surfaceField('assetValue')}
|
|
253
|
+
onChange={value =>
|
|
254
|
+
setDraft(item => ({ ...item, assetValue: value as [number | undefined, number | undefined] | undefined }))
|
|
301
255
|
}
|
|
256
|
+
range
|
|
257
|
+
value={draft.assetValue}
|
|
302
258
|
/>
|
|
303
|
-
<Space.Compact>
|
|
304
|
-
<InputNumber
|
|
305
|
-
min={0}
|
|
306
|
-
placeholder="最低资产价值"
|
|
307
|
-
value={draft.assetValue?.[0]}
|
|
308
|
-
onChange={value =>
|
|
309
|
-
setDraft(item => ({
|
|
310
|
-
...item,
|
|
311
|
-
assetValue: [
|
|
312
|
-
value === null ? undefined : Number(value),
|
|
313
|
-
item.assetValue?.[1],
|
|
314
|
-
],
|
|
315
|
-
}))
|
|
316
|
-
}
|
|
317
|
-
/>
|
|
318
|
-
<InputNumber
|
|
319
|
-
min={0}
|
|
320
|
-
placeholder="最高资产价值"
|
|
321
|
-
value={draft.assetValue?.[1]}
|
|
322
|
-
onChange={value =>
|
|
323
|
-
setDraft(item => ({
|
|
324
|
-
...item,
|
|
325
|
-
assetValue: [
|
|
326
|
-
item.assetValue?.[0],
|
|
327
|
-
value === null ? undefined : Number(value),
|
|
328
|
-
],
|
|
329
|
-
}))
|
|
330
|
-
}
|
|
331
|
-
/>
|
|
332
|
-
</Space.Compact>
|
|
333
259
|
<Space>
|
|
334
260
|
<Button
|
|
335
261
|
type="primary"
|
|
@@ -378,8 +304,154 @@ export function InstrumentListPage() {
|
|
|
378
304
|
},
|
|
379
305
|
}}
|
|
380
306
|
/>
|
|
381
|
-
|
|
382
|
-
</Card>
|
|
383
|
-
</Shell>
|
|
307
|
+
</ResourceListPage>
|
|
384
308
|
);
|
|
385
309
|
}
|
|
310
|
+
|
|
311
|
+
function MobileInstrumentListPage() {
|
|
312
|
+
const navigate = useNavigate();
|
|
313
|
+
const { hasCapability } = useRuntime();
|
|
314
|
+
const { message } = App.useApp();
|
|
315
|
+
const [page, setPage] = useState(1);
|
|
316
|
+
const [keyword, setKeyword] = useState('');
|
|
317
|
+
const [query, setQuery] = useState<Partial<InstrumentListQuery>>({});
|
|
318
|
+
const list = useList<InstrumentRecord>({
|
|
319
|
+
resource: 'instruments',
|
|
320
|
+
pagination: { currentPage: page, pageSize: instrumentSurface.list?.defaultPageSize || 20 },
|
|
321
|
+
sorters: [{ field: defaultSort.field, order: defaultSort.order || 'asc' }],
|
|
322
|
+
meta: { query },
|
|
323
|
+
});
|
|
324
|
+
const records = (list.result.data || []) as unknown as InstrumentRecord[];
|
|
325
|
+
const canCreate = hasCapability(INSTRUMENT_CAPABILITIES.create);
|
|
326
|
+
const canUpdate = hasCapability(INSTRUMENT_CAPABILITIES.update);
|
|
327
|
+
const listFieldsForCard = listFields.filter(field =>
|
|
328
|
+
['instrumentCode', 'specModel', 'instrumentStatus', 'collegeId'].includes(field.key)
|
|
329
|
+
);
|
|
330
|
+
return (
|
|
331
|
+
<MobileResourceListPage
|
|
332
|
+
createCapability={INSTRUMENT_CAPABILITIES.create}
|
|
333
|
+
createPath="/m/instruments/new"
|
|
334
|
+
description="仪器档案、归属与开放状态"
|
|
335
|
+
onExport={() => message.info('导出将使用当前 Data API 查询条件')}
|
|
336
|
+
readCapability={INSTRUMENT_CAPABILITIES.read}
|
|
337
|
+
resource="instruments"
|
|
338
|
+
surface={instrumentSurface}
|
|
339
|
+
title="仪器资源"
|
|
340
|
+
>
|
|
341
|
+
<div className="oxa-mobile-filter">
|
|
342
|
+
<Select
|
|
343
|
+
options={searchableFields}
|
|
344
|
+
value={query.searchField || searchableFields[0]?.value}
|
|
345
|
+
onChange={value => setQuery(item => ({ ...item, searchField: value }))}
|
|
346
|
+
/>
|
|
347
|
+
<Input
|
|
348
|
+
allowClear
|
|
349
|
+
placeholder="搜索仪器名称、编号或资产编号"
|
|
350
|
+
value={keyword}
|
|
351
|
+
onChange={event => setKeyword(event.target.value)}
|
|
352
|
+
onPressEnter={() => {
|
|
353
|
+
setPage(1);
|
|
354
|
+
setQuery(item => ({ ...item, keyword: keyword || undefined }));
|
|
355
|
+
}}
|
|
356
|
+
/>
|
|
357
|
+
<Button
|
|
358
|
+
icon={<SearchOutlined />}
|
|
359
|
+
onClick={() => {
|
|
360
|
+
setPage(1);
|
|
361
|
+
setQuery(item => ({ ...item, keyword: keyword || undefined }));
|
|
362
|
+
}}
|
|
363
|
+
type="primary"
|
|
364
|
+
>
|
|
365
|
+
查询
|
|
366
|
+
</Button>
|
|
367
|
+
</div>
|
|
368
|
+
{list.query.isLoading ? (
|
|
369
|
+
<div className="oxa-page-loading"><Spin /></div>
|
|
370
|
+
) : list.query.isError ? (
|
|
371
|
+
<Card><Typography.Text type="danger">{list.query.error?.message || '列表读取失败'}</Typography.Text></Card>
|
|
372
|
+
) : records.length ? (
|
|
373
|
+
<div className="oxa-mobile-record-list">
|
|
374
|
+
{records.map(record => (
|
|
375
|
+
<Card className="oxa-mobile-record-card" key={record.id} size="small">
|
|
376
|
+
<div className="oxa-mobile-record-heading">
|
|
377
|
+
<a onClick={() => navigate(`/m/instruments/${record.id}`)}>{record.chineseName || '-'}</a>
|
|
378
|
+
<SurfaceFieldValue
|
|
379
|
+
field={surfaceField('instrumentStatus')}
|
|
380
|
+
value={record.instrumentStatus}
|
|
381
|
+
renderers={{
|
|
382
|
+
renderValue: ({ field, value }) => <Tag color={value === 'normal' ? 'green' : 'default'}>{field.options?.find(option => option.value === value)?.label || String(value)}</Tag>,
|
|
383
|
+
}}
|
|
384
|
+
/>
|
|
385
|
+
</div>
|
|
386
|
+
<div className="oxa-mobile-record-fields">
|
|
387
|
+
{listFieldsForCard.map(field => (
|
|
388
|
+
<div key={field.key}>
|
|
389
|
+
<Typography.Text type="secondary">{field.label}</Typography.Text>
|
|
390
|
+
<div><SurfaceFieldValue field={field} value={record[field.key]} /></div>
|
|
391
|
+
</div>
|
|
392
|
+
))}
|
|
393
|
+
</div>
|
|
394
|
+
<Space>
|
|
395
|
+
<Button onClick={() => navigate(`/m/instruments/${record.id}`)} size="small">查看</Button>
|
|
396
|
+
{canUpdate && <Button onClick={() => navigate(`/m/instruments/${record.id}/edit`)} size="small">编辑</Button>}
|
|
397
|
+
</Space>
|
|
398
|
+
</Card>
|
|
399
|
+
))}
|
|
400
|
+
</div>
|
|
401
|
+
) : (
|
|
402
|
+
<Card><Empty description="暂无仪器资源" /></Card>
|
|
403
|
+
)}
|
|
404
|
+
<div className="oxa-mobile-pagination">
|
|
405
|
+
<Button disabled={page <= 1} onClick={() => setPage(current => current - 1)}>上一页</Button>
|
|
406
|
+
<Typography.Text>第 {page} 页</Typography.Text>
|
|
407
|
+
<Button disabled={records.length < (instrumentSurface.list?.defaultPageSize || 20)} onClick={() => setPage(current => current + 1)}>下一页</Button>
|
|
408
|
+
</div>
|
|
409
|
+
{!canCreate && <Typography.Text type="secondary">当前角色无新增权限</Typography.Text>}
|
|
410
|
+
</MobileResourceListPage>
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function renderListValue(
|
|
415
|
+
field: SurfaceField,
|
|
416
|
+
value: unknown,
|
|
417
|
+
record: InstrumentRecord,
|
|
418
|
+
navigate: ReturnType<typeof useNavigate>
|
|
419
|
+
) {
|
|
420
|
+
if (field.key === 'image') {
|
|
421
|
+
return value && typeof value === 'object' && 'name' in value ? (
|
|
422
|
+
<Tag>{String((value as { name?: unknown }).name)}</Tag>
|
|
423
|
+
) : (
|
|
424
|
+
<span className="oxa-muted">无图</span>
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
if (field.key === 'chineseName') {
|
|
428
|
+
return <a onClick={() => navigate(`/instruments/${record.id}`)}>{String(value || '-')}</a>;
|
|
429
|
+
}
|
|
430
|
+
return (
|
|
431
|
+
<SurfaceFieldValue
|
|
432
|
+
field={field}
|
|
433
|
+
renderers={{
|
|
434
|
+
renderValue: ({ field: current, value: currentValue }) => {
|
|
435
|
+
if (current.key === 'collegeId') {
|
|
436
|
+
return <ResolvedValueText source="college" value={String(currentValue)} />;
|
|
437
|
+
}
|
|
438
|
+
if (current.key === 'instrumentStatus') {
|
|
439
|
+
const label = current.options?.find(option => option.value === currentValue)?.label || String(currentValue);
|
|
440
|
+
return (
|
|
441
|
+
<Tag color={currentValue === 'normal' ? 'green' : currentValue === 'maintenance' ? 'orange' : 'default'}>
|
|
442
|
+
{label}
|
|
443
|
+
</Tag>
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
return undefined;
|
|
447
|
+
},
|
|
448
|
+
}}
|
|
449
|
+
value={value}
|
|
450
|
+
/>
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function surfaceField(key: string): SurfaceField {
|
|
455
|
+
const field = instrumentSurface.fields[key];
|
|
456
|
+
return field ? { key, ...field } : { key, label: key, widget: 'text' };
|
|
457
|
+
}
|