openxiangda-cli 2.0.0-alpha.61 → 2.0.0-alpha.66
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.map +1 -1
- package/dist/create-workspace.js +1 -0
- package/dist/create-workspace.js.map +1 -1
- package/package.json +4 -4
- package/template/AGENTS.md +1 -1
- package/template/README.md +7 -7
- package/template/apps/server/Dockerfile +2 -1
- package/template/apps/server/package.json +1 -1
- package/template/apps/server/src/app.module.ts +1 -3
- package/template/apps/server/test/smoke.test.ts +6 -23
- package/template/apps/web/e2e/resources.spec.ts +20 -0
- package/template/apps/web/index.html +1 -1
- package/template/apps/web/package.json +1 -1
- package/template/apps/web/src/AuthoritativeSelector.tsx +298 -41
- package/template/apps/web/src/Shell.tsx +39 -211
- package/template/apps/web/src/components/platform-fields/AttachmentFileList.tsx +1 -1
- package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +229 -16
- package/template/apps/web/src/components/resource/SurfaceFields.tsx +142 -2
- package/template/apps/web/src/data-provider.ts +23 -102
- package/template/apps/web/src/main.tsx +35 -83
- package/template/apps/web/src/platform-client.ts +112 -424
- package/template/apps/web/src/runtime-meta.ts +1 -1
- package/template/apps/web/src/styles.css +285 -3
- package/template/apps/web/test/contracts.test.ts +43 -769
- package/template/openxiangda.config.ts +14 -81
- package/template/package.json +3 -3
- package/template/packages/contracts/src/generated.ts +7 -804
- package/template/apps/server/src/instrument-context.controller.ts +0 -25
- package/template/apps/web/e2e/instruments.spec.ts +0 -1338
- package/template/apps/web/src/CollegePage.tsx +0 -181
- package/template/apps/web/src/InstrumentDetailPage.tsx +0 -176
- package/template/apps/web/src/InstrumentForm.tsx +0 -380
- package/template/apps/web/src/InstrumentFormPage.tsx +0 -82
- package/template/apps/web/src/InstrumentListPage.tsx +0 -457
- package/template/apps/web/src/fields.ts +0 -18
- package/template/apps/web/src/instrument.ts +0 -56
- package/template/platform/data/colleges.ts +0 -21
- package/template/platform/data/instrument-surface.ts +0 -125
- package/template/platform/data/instruments.ts +0 -83
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ApartmentOutlined,
|
|
3
|
-
|
|
3
|
+
DatabaseOutlined,
|
|
4
4
|
UserOutlined,
|
|
5
5
|
} from '@ant-design/icons';
|
|
6
6
|
import { Avatar, Select, Space, Spin, Tag, Typography } from 'antd';
|
|
7
7
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
8
8
|
import {
|
|
9
|
-
resolveCollegeScope,
|
|
10
9
|
resolveDirectory,
|
|
11
|
-
|
|
10
|
+
resolveResource,
|
|
12
11
|
searchDirectory,
|
|
12
|
+
searchResource,
|
|
13
13
|
type DirectoryKind,
|
|
14
14
|
} from './platform-client';
|
|
15
15
|
|
|
16
|
-
type Source = DirectoryKind | '
|
|
16
|
+
type Source = DirectoryKind | 'resource';
|
|
17
17
|
type Option = {
|
|
18
18
|
value: string;
|
|
19
19
|
label: string;
|
|
@@ -55,14 +55,14 @@ async function searchSource(
|
|
|
55
55
|
source: Source,
|
|
56
56
|
operation: 'create' | 'update',
|
|
57
57
|
keyword: string,
|
|
58
|
-
cursor?: string
|
|
58
|
+
cursor?: string,
|
|
59
|
+
resourceCode?: string,
|
|
60
|
+
labelField?: string,
|
|
59
61
|
) {
|
|
60
|
-
if (source === '
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
nextCursor: page.nextCursor,
|
|
65
|
-
};
|
|
62
|
+
if (source === 'resource') {
|
|
63
|
+
if (!resourceCode || !labelField) throw new Error('OPENXIANGDA_RESOURCE_REFERENCE_CONFIG_INVALID');
|
|
64
|
+
const page = await searchResource(resourceCode, labelField, { keyword, cursor });
|
|
65
|
+
return { items: page.items.map(item => ({ ...item, value: item.id, source }) as Option), nextCursor: page.nextCursor };
|
|
66
66
|
}
|
|
67
67
|
const page = await searchDirectory(source, { keyword, cursor });
|
|
68
68
|
return {
|
|
@@ -74,12 +74,13 @@ async function searchSource(
|
|
|
74
74
|
async function resolveSource(
|
|
75
75
|
source: Source,
|
|
76
76
|
operation: 'create' | 'update',
|
|
77
|
-
values: string[]
|
|
77
|
+
values: string[],
|
|
78
|
+
resourceCode?: string,
|
|
79
|
+
labelField?: string,
|
|
78
80
|
) {
|
|
79
|
-
if (source === '
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
);
|
|
81
|
+
if (source === 'resource') {
|
|
82
|
+
if (!resourceCode || !labelField) throw new Error('OPENXIANGDA_RESOURCE_REFERENCE_CONFIG_INVALID');
|
|
83
|
+
return (await resolveResource(resourceCode, values, labelField)).items.map(item => ({ ...item, value: item.id, source }) as Option);
|
|
83
84
|
}
|
|
84
85
|
return (await resolveDirectory(source, values)).items.map(item =>
|
|
85
86
|
directoryOption(source, item)
|
|
@@ -94,8 +95,8 @@ function optionIcon(option: Pick<Option, 'source' | 'label'>) {
|
|
|
94
95
|
</Avatar>
|
|
95
96
|
);
|
|
96
97
|
}
|
|
97
|
-
return option.source === '
|
|
98
|
-
<
|
|
98
|
+
return option.source === 'resource' ? (
|
|
99
|
+
<DatabaseOutlined className="oxa-selector-icon" />
|
|
99
100
|
) : (
|
|
100
101
|
<ApartmentOutlined className="oxa-selector-icon" />
|
|
101
102
|
);
|
|
@@ -106,6 +107,19 @@ function optionDescription(option: Option) {
|
|
|
106
107
|
return option.description || '';
|
|
107
108
|
}
|
|
108
109
|
|
|
110
|
+
export type AuthoritativeSelectorProps = {
|
|
111
|
+
value?: string | string[];
|
|
112
|
+
onChange?: (value: string | string[] | undefined) => void;
|
|
113
|
+
source: Source;
|
|
114
|
+
operation: 'create' | 'update';
|
|
115
|
+
multiple?: boolean;
|
|
116
|
+
disabled?: boolean;
|
|
117
|
+
id?: string;
|
|
118
|
+
placeholder: string;
|
|
119
|
+
resourceCode?: string;
|
|
120
|
+
labelField?: string;
|
|
121
|
+
};
|
|
122
|
+
|
|
109
123
|
export function AuthoritativeSelector({
|
|
110
124
|
value,
|
|
111
125
|
onChange,
|
|
@@ -115,16 +129,9 @@ export function AuthoritativeSelector({
|
|
|
115
129
|
disabled = false,
|
|
116
130
|
id,
|
|
117
131
|
placeholder,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
source: Source;
|
|
122
|
-
operation: 'create' | 'update';
|
|
123
|
-
multiple?: boolean;
|
|
124
|
-
disabled?: boolean;
|
|
125
|
-
id?: string;
|
|
126
|
-
placeholder: string;
|
|
127
|
-
}) {
|
|
132
|
+
resourceCode,
|
|
133
|
+
labelField,
|
|
134
|
+
}: AuthoritativeSelectorProps) {
|
|
128
135
|
const [options, setOptions] = useState<Option[]>([]);
|
|
129
136
|
const [keyword, setKeyword] = useState('');
|
|
130
137
|
const [cursor, setCursor] = useState<string | null>(null);
|
|
@@ -144,7 +151,7 @@ export function AuthoritativeSelector({
|
|
|
144
151
|
if (missing.length === 0) return;
|
|
145
152
|
let active = true;
|
|
146
153
|
setLoading(true);
|
|
147
|
-
void resolveSource(source, operation, missing)
|
|
154
|
+
void resolveSource(source, operation, missing, resourceCode, labelField)
|
|
148
155
|
.then(items => {
|
|
149
156
|
if (active) setOptions(current => mergeOptions(current, items));
|
|
150
157
|
})
|
|
@@ -158,11 +165,11 @@ export function AuthoritativeSelector({
|
|
|
158
165
|
return () => {
|
|
159
166
|
active = false;
|
|
160
167
|
};
|
|
161
|
-
}, [operation, options, source, values]);
|
|
168
|
+
}, [labelField, operation, options, resourceCode, source, values]);
|
|
162
169
|
|
|
163
170
|
useEffect(() => {
|
|
164
171
|
if (disabled) return;
|
|
165
|
-
if (
|
|
172
|
+
if (keyword.trim().length < 2) {
|
|
166
173
|
setCursor(null);
|
|
167
174
|
return;
|
|
168
175
|
}
|
|
@@ -170,7 +177,7 @@ export function AuthoritativeSelector({
|
|
|
170
177
|
const timer = window.setTimeout(() => {
|
|
171
178
|
setLoading(true);
|
|
172
179
|
setError('');
|
|
173
|
-
void searchSource(source, operation, keyword)
|
|
180
|
+
void searchSource(source, operation, keyword, undefined, resourceCode, labelField)
|
|
174
181
|
.then(page => {
|
|
175
182
|
if (sequence !== requestSequence.current) return;
|
|
176
183
|
setOptions(current => {
|
|
@@ -192,14 +199,38 @@ export function AuthoritativeSelector({
|
|
|
192
199
|
});
|
|
193
200
|
}, 300);
|
|
194
201
|
return () => window.clearTimeout(timer);
|
|
195
|
-
}, [disabled, keyword, operation, source, values]);
|
|
202
|
+
}, [disabled, keyword, labelField, operation, resourceCode, source, values]);
|
|
203
|
+
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (disabled || !open || keyword.trim().length >= 2) return;
|
|
206
|
+
const sequence = ++requestSequence.current;
|
|
207
|
+
setLoading(true);
|
|
208
|
+
setError('');
|
|
209
|
+
void searchSource(source, operation, '', undefined, resourceCode, labelField)
|
|
210
|
+
.then(page => {
|
|
211
|
+
if (sequence !== requestSequence.current) return;
|
|
212
|
+
setOptions(current => {
|
|
213
|
+
const selected = current.filter(option => values.includes(option.value));
|
|
214
|
+
return mergeOptions(selected, page.items);
|
|
215
|
+
});
|
|
216
|
+
setCursor(page.nextCursor);
|
|
217
|
+
})
|
|
218
|
+
.catch(reason => {
|
|
219
|
+
if (sequence === requestSequence.current) {
|
|
220
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
.finally(() => {
|
|
224
|
+
if (sequence === requestSequence.current) setLoading(false);
|
|
225
|
+
});
|
|
226
|
+
}, [disabled, keyword, labelField, open, operation, resourceCode, source, values]);
|
|
196
227
|
|
|
197
228
|
const loadNext = () => {
|
|
198
229
|
if (!cursor || loading) return;
|
|
199
230
|
const nextCursor = cursor;
|
|
200
231
|
setLoading(true);
|
|
201
232
|
setError('');
|
|
202
|
-
void searchSource(source, operation, keyword, nextCursor)
|
|
233
|
+
void searchSource(source, operation, keyword, nextCursor, resourceCode, labelField)
|
|
203
234
|
.then(page => {
|
|
204
235
|
setOptions(current => mergeOptions(current, page.items));
|
|
205
236
|
setCursor(page.nextCursor);
|
|
@@ -230,8 +261,8 @@ export function AuthoritativeSelector({
|
|
|
230
261
|
<Spin size="small" />
|
|
231
262
|
) : error ? (
|
|
232
263
|
<Typography.Text type="danger">{error}</Typography.Text>
|
|
233
|
-
) :
|
|
234
|
-
'
|
|
264
|
+
) : keyword.trim().length < 2 ? (
|
|
265
|
+
'暂无可选数据,可输入至少 2 个字符搜索'
|
|
235
266
|
) : (
|
|
236
267
|
'无可选数据'
|
|
237
268
|
)
|
|
@@ -240,7 +271,10 @@ export function AuthoritativeSelector({
|
|
|
240
271
|
onChange?.(next || undefined);
|
|
241
272
|
if (!multiple) setOpen(false);
|
|
242
273
|
}}
|
|
243
|
-
onOpenChange={
|
|
274
|
+
onOpenChange={next => {
|
|
275
|
+
setOpen(next);
|
|
276
|
+
if (!next) setKeyword('');
|
|
277
|
+
}}
|
|
244
278
|
onPopupScroll={event => {
|
|
245
279
|
const element = event.currentTarget;
|
|
246
280
|
if (
|
|
@@ -275,8 +309,8 @@ export function AuthoritativeSelector({
|
|
|
275
309
|
prefix={
|
|
276
310
|
source === 'user' ? (
|
|
277
311
|
<UserOutlined />
|
|
278
|
-
) : source === '
|
|
279
|
-
<
|
|
312
|
+
) : source === 'resource' ? (
|
|
313
|
+
<DatabaseOutlined />
|
|
280
314
|
) : (
|
|
281
315
|
<ApartmentOutlined />
|
|
282
316
|
)
|
|
@@ -314,12 +348,235 @@ export function AuthoritativeSelector({
|
|
|
314
348
|
);
|
|
315
349
|
}
|
|
316
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Mobile reference picker. It deliberately avoids Ant Design Select so that
|
|
353
|
+
* resource references have a touch-friendly, full-width interaction on
|
|
354
|
+
* phones. The search and resolve calls are the same authoritative platform
|
|
355
|
+
* APIs used by the desktop selector.
|
|
356
|
+
*/
|
|
357
|
+
export function MobileAuthoritativeSelector({
|
|
358
|
+
value,
|
|
359
|
+
onChange,
|
|
360
|
+
source,
|
|
361
|
+
operation,
|
|
362
|
+
multiple = false,
|
|
363
|
+
disabled = false,
|
|
364
|
+
id,
|
|
365
|
+
placeholder,
|
|
366
|
+
resourceCode,
|
|
367
|
+
labelField,
|
|
368
|
+
}: AuthoritativeSelectorProps) {
|
|
369
|
+
const [options, setOptions] = useState<Option[]>([]);
|
|
370
|
+
const [keyword, setKeyword] = useState('');
|
|
371
|
+
const [cursor, setCursor] = useState<string | null>(null);
|
|
372
|
+
const [loading, setLoading] = useState(false);
|
|
373
|
+
const [error, setError] = useState('');
|
|
374
|
+
const [open, setOpen] = useState(false);
|
|
375
|
+
const [draftValues, setDraftValues] = useState<string[]>([]);
|
|
376
|
+
const requestSequence = useRef(0);
|
|
377
|
+
const values = useMemo(
|
|
378
|
+
() => (Array.isArray(value) ? value : value ? [value] : []),
|
|
379
|
+
[value]
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
useEffect(() => {
|
|
383
|
+
setDraftValues(values);
|
|
384
|
+
}, [open, values]);
|
|
385
|
+
|
|
386
|
+
useEffect(() => {
|
|
387
|
+
const missing = values.filter(
|
|
388
|
+
selected => !options.some(option => option.value === selected)
|
|
389
|
+
);
|
|
390
|
+
if (missing.length === 0) return;
|
|
391
|
+
let active = true;
|
|
392
|
+
setLoading(true);
|
|
393
|
+
void resolveSource(source, operation, missing, resourceCode, labelField)
|
|
394
|
+
.then(items => {
|
|
395
|
+
if (active) setOptions(current => mergeOptions(current, items));
|
|
396
|
+
})
|
|
397
|
+
.catch(reason => {
|
|
398
|
+
if (active) setError(reason instanceof Error ? reason.message : String(reason));
|
|
399
|
+
})
|
|
400
|
+
.finally(() => {
|
|
401
|
+
if (active) setLoading(false);
|
|
402
|
+
});
|
|
403
|
+
return () => {
|
|
404
|
+
active = false;
|
|
405
|
+
};
|
|
406
|
+
}, [labelField, operation, options, resourceCode, source, values]);
|
|
407
|
+
|
|
408
|
+
useEffect(() => {
|
|
409
|
+
if (disabled || !open) return;
|
|
410
|
+
const trimmed = keyword.trim();
|
|
411
|
+
if (trimmed.length > 0 && trimmed.length < 2) return;
|
|
412
|
+
const sequence = ++requestSequence.current;
|
|
413
|
+
const timer = window.setTimeout(() => {
|
|
414
|
+
setLoading(true);
|
|
415
|
+
setError('');
|
|
416
|
+
void searchSource(source, operation, trimmed, undefined, resourceCode, labelField)
|
|
417
|
+
.then(page => {
|
|
418
|
+
if (sequence !== requestSequence.current) return;
|
|
419
|
+
setOptions(current => {
|
|
420
|
+
const selected = current.filter(option => values.includes(option.value));
|
|
421
|
+
return mergeOptions(selected, page.items);
|
|
422
|
+
});
|
|
423
|
+
setCursor(page.nextCursor);
|
|
424
|
+
})
|
|
425
|
+
.catch(reason => {
|
|
426
|
+
if (sequence === requestSequence.current) {
|
|
427
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
.finally(() => {
|
|
431
|
+
if (sequence === requestSequence.current) setLoading(false);
|
|
432
|
+
});
|
|
433
|
+
}, trimmed ? 300 : 0);
|
|
434
|
+
return () => window.clearTimeout(timer);
|
|
435
|
+
}, [disabled, keyword, labelField, open, operation, resourceCode, source, values]);
|
|
436
|
+
|
|
437
|
+
const loadNext = () => {
|
|
438
|
+
if (!cursor || loading) return;
|
|
439
|
+
const nextCursor = cursor;
|
|
440
|
+
setLoading(true);
|
|
441
|
+
setError('');
|
|
442
|
+
void searchSource(source, operation, keyword.trim(), nextCursor, resourceCode, labelField)
|
|
443
|
+
.then(page => {
|
|
444
|
+
setOptions(current => mergeOptions(current, page.items));
|
|
445
|
+
setCursor(page.nextCursor);
|
|
446
|
+
})
|
|
447
|
+
.catch(reason => setError(reason instanceof Error ? reason.message : String(reason)))
|
|
448
|
+
.finally(() => setLoading(false));
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
const selectedItems = values
|
|
452
|
+
.map(selected => options.find(option => option.value === selected))
|
|
453
|
+
.filter((item): item is Option => Boolean(item));
|
|
454
|
+
const selectedLabel = selectedItems.length
|
|
455
|
+
? selectedItems.map(item => item.label).join('、')
|
|
456
|
+
: values.length
|
|
457
|
+
? (loading ? '读取中…' : values.join('、'))
|
|
458
|
+
: `请选择${placeholder}`;
|
|
459
|
+
|
|
460
|
+
const toggleValue = (nextValue: string) => {
|
|
461
|
+
const item = options.find(option => option.value === nextValue);
|
|
462
|
+
if (!item?.selectable) return;
|
|
463
|
+
if (!multiple) {
|
|
464
|
+
onChange?.(nextValue);
|
|
465
|
+
setOpen(false);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
setDraftValues(current =>
|
|
469
|
+
current.includes(nextValue)
|
|
470
|
+
? current.filter(itemValue => itemValue !== nextValue)
|
|
471
|
+
: [...current, nextValue]
|
|
472
|
+
);
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
return (
|
|
476
|
+
<>
|
|
477
|
+
<button
|
|
478
|
+
aria-haspopup="dialog"
|
|
479
|
+
className="oxa-mobile-reference-trigger"
|
|
480
|
+
disabled={disabled}
|
|
481
|
+
id={id}
|
|
482
|
+
onClick={() => {
|
|
483
|
+
setDraftValues(values);
|
|
484
|
+
setKeyword('');
|
|
485
|
+
setOpen(true);
|
|
486
|
+
}}
|
|
487
|
+
type="button"
|
|
488
|
+
>
|
|
489
|
+
<span className={values.length ? '' : 'oxa-mobile-reference-placeholder'}>
|
|
490
|
+
{selectedLabel}
|
|
491
|
+
</span>
|
|
492
|
+
<span aria-hidden="true">⌄</span>
|
|
493
|
+
</button>
|
|
494
|
+
{open && (
|
|
495
|
+
<div
|
|
496
|
+
aria-label={placeholder}
|
|
497
|
+
aria-modal="true"
|
|
498
|
+
className="oxa-mobile-reference-backdrop"
|
|
499
|
+
onClick={() => setOpen(false)}
|
|
500
|
+
role="dialog"
|
|
501
|
+
>
|
|
502
|
+
<div className="oxa-mobile-reference-sheet" onClick={event => event.stopPropagation()}>
|
|
503
|
+
<div className="oxa-mobile-reference-header">
|
|
504
|
+
<strong>{placeholder}</strong>
|
|
505
|
+
<button onClick={() => setOpen(false)} type="button">关闭</button>
|
|
506
|
+
</div>
|
|
507
|
+
<input
|
|
508
|
+
autoFocus
|
|
509
|
+
className="oxa-mobile-input oxa-mobile-reference-search"
|
|
510
|
+
onChange={event => setKeyword(event.target.value)}
|
|
511
|
+
placeholder={`搜索${placeholder}`}
|
|
512
|
+
type="search"
|
|
513
|
+
value={keyword}
|
|
514
|
+
/>
|
|
515
|
+
{error && <div className="oxa-mobile-reference-error">{error}</div>}
|
|
516
|
+
{loading && <div className="oxa-mobile-reference-loading">加载中…</div>}
|
|
517
|
+
{!loading && !error && options.length === 0 && (
|
|
518
|
+
<div className="oxa-mobile-reference-empty">
|
|
519
|
+
{keyword.trim().length < 2 ? '请输入至少 2 个字符搜索' : '暂无可选数据'}
|
|
520
|
+
</div>
|
|
521
|
+
)}
|
|
522
|
+
<div aria-multiselectable={multiple} className="oxa-mobile-reference-options" role="listbox">
|
|
523
|
+
{options.map(option => {
|
|
524
|
+
const selected = (multiple ? draftValues : values).includes(option.value);
|
|
525
|
+
const description = optionDescription(option);
|
|
526
|
+
return (
|
|
527
|
+
<button
|
|
528
|
+
aria-selected={selected}
|
|
529
|
+
className={`oxa-mobile-reference-option${selected ? ' is-selected' : ''}`}
|
|
530
|
+
disabled={!option.selectable}
|
|
531
|
+
key={option.value}
|
|
532
|
+
onClick={() => toggleValue(option.value)}
|
|
533
|
+
role="option"
|
|
534
|
+
type="button"
|
|
535
|
+
>
|
|
536
|
+
{optionIcon(option)}
|
|
537
|
+
<span className="oxa-mobile-reference-option-copy">
|
|
538
|
+
<strong>{option.label}</strong>
|
|
539
|
+
{description && <small>{description}</small>}
|
|
540
|
+
</span>
|
|
541
|
+
{selected && <span aria-hidden="true">✓</span>}
|
|
542
|
+
</button>
|
|
543
|
+
);
|
|
544
|
+
})}
|
|
545
|
+
</div>
|
|
546
|
+
{cursor && (
|
|
547
|
+
<button className="oxa-mobile-reference-more" onClick={loadNext} type="button">
|
|
548
|
+
{loading ? '加载中…' : '加载更多'}
|
|
549
|
+
</button>
|
|
550
|
+
)}
|
|
551
|
+
{multiple && (
|
|
552
|
+
<button
|
|
553
|
+
className="oxa-mobile-reference-confirm"
|
|
554
|
+
onClick={() => {
|
|
555
|
+
onChange?.(draftValues.length ? draftValues : undefined);
|
|
556
|
+
setOpen(false);
|
|
557
|
+
}}
|
|
558
|
+
type="button"
|
|
559
|
+
>
|
|
560
|
+
确定({draftValues.length})
|
|
561
|
+
</button>
|
|
562
|
+
)}
|
|
563
|
+
</div>
|
|
564
|
+
</div>
|
|
565
|
+
)}
|
|
566
|
+
</>
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
317
570
|
export function ResolvedValueText({
|
|
318
571
|
source,
|
|
319
572
|
value,
|
|
573
|
+
resourceCode,
|
|
574
|
+
labelField,
|
|
320
575
|
}: {
|
|
321
576
|
source: Source;
|
|
322
577
|
value?: string | string[];
|
|
578
|
+
resourceCode?: string;
|
|
579
|
+
labelField?: string;
|
|
323
580
|
}) {
|
|
324
581
|
const values = useMemo(
|
|
325
582
|
() => (Array.isArray(value) ? value : value ? [value] : []),
|
|
@@ -334,7 +591,7 @@ export function ResolvedValueText({
|
|
|
334
591
|
}
|
|
335
592
|
let active = true;
|
|
336
593
|
setFailed(false);
|
|
337
|
-
void resolveSource(source, 'update', values)
|
|
594
|
+
void resolveSource(source, 'update', values, resourceCode, labelField)
|
|
338
595
|
.then(next => {
|
|
339
596
|
if (active) setItems(next);
|
|
340
597
|
})
|
|
@@ -344,7 +601,7 @@ export function ResolvedValueText({
|
|
|
344
601
|
return () => {
|
|
345
602
|
active = false;
|
|
346
603
|
};
|
|
347
|
-
}, [source, values]);
|
|
604
|
+
}, [labelField, resourceCode, source, values]);
|
|
348
605
|
if (values.length === 0) return <>-</>;
|
|
349
606
|
if (failed) return <Typography.Text type="danger">目录解析失败</Typography.Text>;
|
|
350
607
|
if (!items.length) return <>读取中…</>;
|