openxiangda-cli 2.0.0-alpha.84 → 2.0.0-alpha.86

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.
Files changed (62) hide show
  1. package/package.json +4 -4
  2. package/template/AGENTS.md +4 -4
  3. package/template/README.md +2 -2
  4. package/template/apps/server/package.json +1 -1
  5. package/template/apps/web/data-api-live.e2e.html +15 -0
  6. package/template/apps/web/e2e/data-api-live-fixture.tsx +141 -0
  7. package/template/apps/web/e2e/data-api-live.spec.ts +138 -0
  8. package/template/apps/web/e2e/field-protocol-fixture.tsx +280 -0
  9. package/template/apps/web/e2e/field-protocol.spec.ts +100 -0
  10. package/template/apps/web/e2e/resources.spec.ts +258 -20
  11. package/template/apps/web/field-protocol.e2e.html +12 -0
  12. package/template/apps/web/package.json +1 -1
  13. package/template/apps/web/playwright.config.ts +3 -1
  14. package/template/apps/web/scripts/check.mjs +8 -5
  15. package/template/apps/web/scripts/verify-build.mjs +1 -1
  16. package/template/apps/web/src/AuthoritativeSelector.tsx +165 -132
  17. package/template/apps/web/src/FilePreviewPage.tsx +30 -9
  18. package/template/apps/web/src/RoleSubjectSelect.tsx +128 -0
  19. package/template/apps/web/src/Shell.tsx +93 -6
  20. package/template/apps/web/src/components/platform-fields/AddressField.tsx +338 -0
  21. package/template/apps/web/src/components/platform-fields/AttachmentFileList.tsx +25 -6
  22. package/template/apps/web/src/components/platform-fields/CascadeField.tsx +49 -0
  23. package/template/apps/web/src/components/platform-fields/DateTimeField.tsx +148 -0
  24. package/template/apps/web/src/components/platform-fields/JsonField.tsx +77 -0
  25. package/template/apps/web/src/components/platform-fields/LocationField.tsx +164 -0
  26. package/template/apps/web/src/components/platform-fields/PlatformDirectoryPicker.tsx +19 -44
  27. package/template/apps/web/src/components/platform-fields/ResourceReferenceField.tsx +67 -0
  28. package/template/apps/web/src/components/platform-fields/RichTextField.tsx +196 -0
  29. package/template/apps/web/src/components/platform-fields/SignatureField.tsx +315 -0
  30. package/template/apps/web/src/components/platform-fields/SubtableField.tsx +553 -0
  31. package/template/apps/web/src/components/platform-fields/address-value.ts +80 -0
  32. package/template/apps/web/src/components/platform-fields/cascade-value.ts +66 -0
  33. package/template/apps/web/src/components/platform-fields/directory-value.ts +53 -0
  34. package/template/apps/web/src/components/platform-fields/field-form-codec.ts +98 -0
  35. package/template/apps/web/src/components/platform-fields/location-value.ts +87 -0
  36. package/template/apps/web/src/components/platform-fields/resource-query.ts +131 -0
  37. package/template/apps/web/src/components/platform-fields/rich-text-value.ts +59 -0
  38. package/template/apps/web/src/components/platform-fields/subtable-value.ts +187 -0
  39. package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +194 -108
  40. package/template/apps/web/src/components/resource/ResourceBatchActions.tsx +8 -1
  41. package/template/apps/web/src/components/resource/SurfaceFields.tsx +385 -79
  42. package/template/apps/web/src/components/resource/generated-resource-definition.ts +18 -0
  43. package/template/apps/web/src/components/resource/resource-import.ts +4 -4
  44. package/template/apps/web/src/data-provider.ts +59 -34
  45. package/template/apps/web/src/platform-client.ts +385 -111
  46. package/template/apps/web/src/runtime.tsx +166 -14
  47. package/template/apps/web/src/styles.css +264 -0
  48. package/template/apps/web/test/address-field.test.ts +66 -0
  49. package/template/apps/web/test/cascade-value.test.ts +68 -0
  50. package/template/apps/web/test/contracts.test.ts +29 -5
  51. package/template/apps/web/test/directory-value.test.ts +55 -0
  52. package/template/apps/web/test/field-bounds.test.ts +17 -0
  53. package/template/apps/web/test/field-form-codec.test.ts +85 -0
  54. package/template/apps/web/test/location-field.test.ts +83 -0
  55. package/template/apps/web/test/resource-import.test.ts +8 -7
  56. package/template/apps/web/test/resource-query.test.ts +144 -0
  57. package/template/apps/web/test/rich-json-field.test.ts +53 -0
  58. package/template/apps/web/test/signature-serial-field.test.ts +40 -0
  59. package/template/apps/web/test/subtable-value.test.ts +144 -0
  60. package/template/package.json +3 -3
  61. package/template/packages/contracts/src/generated.ts +69 -0
  62. package/template/scripts/verify-template-budget.mjs +11 -4
@@ -0,0 +1,148 @@
1
+ import { DatePicker, TimePicker, Typography } from 'antd';
2
+ import type { DataFieldSurface } from 'openxiangda-contracts/browser';
3
+ import { fieldValueForData, fieldValueForForm } from './field-form-codec';
4
+
5
+ export function DateTimeField({
6
+ field,
7
+ disabled,
8
+ mobile = false,
9
+ }: {
10
+ field: DataFieldSurface;
11
+ disabled?: boolean;
12
+ mobile?: boolean;
13
+ }) {
14
+ const className = mobile ? 'oxa-mobile-date-time-field' : undefined;
15
+ switch (field.type) {
16
+ case 'time': {
17
+ const format = field.timePrecision === 'minute' ? 'HH:mm' : 'HH:mm:ss';
18
+ return (
19
+ <TimePicker
20
+ className={className}
21
+ disabled={disabled}
22
+ format={format}
23
+ needConfirm={false}
24
+ placeholder={`请选择${field.label}`}
25
+ showSecond={field.timePrecision !== 'minute'}
26
+ style={{ width: '100%' }}
27
+ />
28
+ );
29
+ }
30
+ case 'datetime':
31
+ return (
32
+ <DatePicker
33
+ className={className}
34
+ disabled={disabled}
35
+ format="YYYY-MM-DD HH:mm:ss"
36
+ placeholder={`请选择${field.label}`}
37
+ showTime={{ format: 'HH:mm:ss' }}
38
+ style={{ width: '100%' }}
39
+ />
40
+ );
41
+ case 'date-range':
42
+ return (
43
+ <DatePicker.RangePicker
44
+ className={className}
45
+ disabled={disabled}
46
+ format="YYYY-MM-DD"
47
+ placeholder={[`开始${field.label}`, `结束${field.label}`]}
48
+ style={{ width: '100%' }}
49
+ />
50
+ );
51
+ case 'datetime-range':
52
+ return (
53
+ <DatePicker.RangePicker
54
+ className={className}
55
+ disabled={disabled}
56
+ format="YYYY-MM-DD HH:mm:ss"
57
+ placeholder={[`开始${field.label}`, `结束${field.label}`]}
58
+ showTime={{ format: 'HH:mm:ss' }}
59
+ style={{ width: '100%' }}
60
+ />
61
+ );
62
+ case 'date':
63
+ default:
64
+ return (
65
+ <DatePicker
66
+ className={className}
67
+ disabled={disabled}
68
+ format="YYYY-MM-DD"
69
+ placeholder={`请选择${field.label}`}
70
+ style={{ width: '100%' }}
71
+ />
72
+ );
73
+ }
74
+ }
75
+
76
+ export function DateTimeValueDisplay({
77
+ field,
78
+ value,
79
+ }: {
80
+ field: DataFieldSurface;
81
+ value: unknown;
82
+ }) {
83
+ if (field.type === 'date-range' || field.type === 'datetime-range') {
84
+ const range = value as { start?: unknown; end?: unknown };
85
+ return <>{range?.start && range?.end ? `${range.start} 至 ${range.end}` : '-'}</>;
86
+ }
87
+ if (field.type === 'time') {
88
+ const time = String(value);
89
+ return <>{field.timePrecision === 'minute' ? time.slice(0, 5) : time}</>;
90
+ }
91
+ if (field.type === 'datetime') {
92
+ const parsed = new Date(String(value));
93
+ return <>{Number.isNaN(parsed.getTime()) ? String(value) : parsed.toLocaleString('zh-CN', { hour12: false })}</>;
94
+ }
95
+ return <Typography.Text>{String(value)}</Typography.Text>;
96
+ }
97
+
98
+ export function DateTimeFilter({
99
+ field,
100
+ value,
101
+ onChange,
102
+ }: {
103
+ field: DataFieldSurface;
104
+ value: unknown;
105
+ onChange: (value: unknown) => void;
106
+ }) {
107
+ const formValue = fieldValueForForm(field, value) as never;
108
+ const emit = (next: unknown) => onChange(
109
+ next === null ? undefined : fieldValueForData(field, next)
110
+ );
111
+ if (field.type === 'time') {
112
+ const format = field.timePrecision === 'minute' ? 'HH:mm' : 'HH:mm:ss';
113
+ return (
114
+ <TimePicker
115
+ allowClear
116
+ format={format}
117
+ onChange={emit}
118
+ placeholder={`筛选${field.label}`}
119
+ showSecond={field.timePrecision !== 'minute'}
120
+ value={formValue}
121
+ />
122
+ );
123
+ }
124
+ if (field.type === 'date-range' || field.type === 'datetime-range') {
125
+ const datetime = field.type === 'datetime-range';
126
+ return (
127
+ <DatePicker.RangePicker
128
+ allowClear
129
+ format={datetime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'}
130
+ onChange={emit}
131
+ placeholder={[`开始${field.label}`, `结束${field.label}`]}
132
+ showTime={datetime ? { format: 'HH:mm:ss' } : false}
133
+ value={formValue}
134
+ />
135
+ );
136
+ }
137
+ const datetime = field.type === 'datetime';
138
+ return (
139
+ <DatePicker
140
+ allowClear
141
+ format={datetime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'}
142
+ onChange={emit}
143
+ placeholder={`筛选${field.label}`}
144
+ showTime={datetime ? { format: 'HH:mm:ss' } : false}
145
+ value={formValue}
146
+ />
147
+ );
148
+ }
@@ -0,0 +1,77 @@
1
+ import { CheckCircleOutlined, FormatPainterOutlined } from '@ant-design/icons';
2
+ import { Alert, Button, Input, Space } from 'antd';
3
+ import { useEffect, useState } from 'react';
4
+
5
+ function stringify(value: unknown) {
6
+ if (value === undefined) return '';
7
+ return JSON.stringify(value, null, 2);
8
+ }
9
+
10
+ export function parseJsonEditorValue(value: string) {
11
+ const text = value.trim();
12
+ return text ? JSON.parse(text) : undefined;
13
+ }
14
+
15
+ export function JsonValueDisplay({ value }: { value: unknown }) {
16
+ return <pre className="oxa-json-value">{stringify(value)}</pre>;
17
+ }
18
+
19
+ export function JsonField({
20
+ value,
21
+ onChange,
22
+ disabled = false,
23
+ mobile = false,
24
+ }: {
25
+ value?: unknown;
26
+ onChange?: (value: unknown) => void;
27
+ disabled?: boolean;
28
+ mobile?: boolean;
29
+ }) {
30
+ const [draft, setDraft] = useState(() => stringify(value));
31
+ const [error, setError] = useState('');
32
+
33
+ useEffect(() => setDraft(stringify(value)), [value]);
34
+
35
+ const validate = (next: string) => {
36
+ setDraft(next);
37
+ try {
38
+ const parsed = parseJsonEditorValue(next);
39
+ setError('');
40
+ onChange?.(parsed);
41
+ return parsed;
42
+ } catch (reason) {
43
+ setError(reason instanceof Error ? reason.message : 'JSON 格式无效');
44
+ return undefined;
45
+ }
46
+ };
47
+
48
+ return (
49
+ <div className={`oxa-json-field${mobile ? ' oxa-mobile-json-field' : ''}`}>
50
+ <Input.TextArea
51
+ autoSize={{ minRows: mobile ? 8 : 6, maxRows: 18 }}
52
+ disabled={disabled}
53
+ onChange={event => validate(event.target.value)}
54
+ placeholder='例如:{"enabled": true}'
55
+ spellCheck={false}
56
+ value={draft}
57
+ />
58
+ <Space>
59
+ <Button
60
+ disabled={disabled || Boolean(error) || !draft.trim()}
61
+ icon={<FormatPainterOutlined />}
62
+ onClick={() => {
63
+ const parsed = validate(draft);
64
+ if (parsed !== undefined) setDraft(stringify(parsed));
65
+ }}
66
+ size="small"
67
+ >
68
+ 格式化
69
+ </Button>
70
+ {!error && draft.trim() && (
71
+ <span className="oxa-json-valid"><CheckCircleOutlined /> 格式有效</span>
72
+ )}
73
+ </Space>
74
+ {error && <Alert showIcon title={`JSON 格式无效:${error}`} type="error" />}
75
+ </div>
76
+ );
77
+ }
@@ -0,0 +1,164 @@
1
+ import {
2
+ AimOutlined,
3
+ DeleteOutlined,
4
+ EnvironmentOutlined,
5
+ } from '@ant-design/icons';
6
+ import { Alert, Button, Space, Tooltip, Typography } from 'antd';
7
+ import type { StableLocationValue } from 'openxiangda-contracts/browser';
8
+ import { useState } from 'react';
9
+ import {
10
+ browserLocationValue,
11
+ dingTalkLocationValue,
12
+ type DingTalkCoordinateSnapshot,
13
+ } from './location-value';
14
+
15
+ interface DingTalkGeolocationApi {
16
+ get(options: {
17
+ targetAccuracy: number;
18
+ coordinate: 0;
19
+ withReGeocode: true;
20
+ useCache: false;
21
+ onSuccess: (result: DingTalkCoordinateSnapshot) => void;
22
+ onFail: (error: unknown) => void;
23
+ }): void;
24
+ }
25
+
26
+ type CaptureSource = StableLocationValue['source'];
27
+
28
+ export function LocationValueDisplay({ value }: { value: StableLocationValue }) {
29
+ return (
30
+ <span className="oxa-location-value oxa-location-value-readonly">
31
+ <Typography.Text strong>
32
+ {value.longitude.toFixed(6)}, {value.latitude.toFixed(6)}
33
+ </Typography.Text>
34
+ <Typography.Text type="secondary">
35
+ {value.source === 'dingTalk' ? '钉钉定位' : '浏览器定位'}
36
+ {value.accuracy === undefined ? '' : ` · 精度 ${Math.round(value.accuracy)} 米`}
37
+ </Typography.Text>
38
+ {(value.name || value.address) && (
39
+ <Typography.Text>{value.name || value.address}</Typography.Text>
40
+ )}
41
+ </span>
42
+ );
43
+ }
44
+
45
+ function dingTalkGeolocation() {
46
+ if (typeof window === 'undefined') return undefined;
47
+ return (
48
+ window as unknown as {
49
+ dd?: { device?: { geolocation?: DingTalkGeolocationApi } };
50
+ }
51
+ ).dd?.device?.geolocation;
52
+ }
53
+
54
+ function browserCapture() {
55
+ return new Promise<StableLocationValue>((resolve, reject) => {
56
+ if (!navigator.geolocation) {
57
+ reject(new Error('当前浏览器不支持定位'));
58
+ return;
59
+ }
60
+ navigator.geolocation.getCurrentPosition(
61
+ position => resolve(browserLocationValue({
62
+ longitude: position.coords.longitude,
63
+ latitude: position.coords.latitude,
64
+ accuracy: position.coords.accuracy,
65
+ timestamp: position.timestamp,
66
+ })),
67
+ error => reject(new Error(error.message || '浏览器定位失败')),
68
+ { enableHighAccuracy: true, maximumAge: 0, timeout: 15_000 }
69
+ );
70
+ });
71
+ }
72
+
73
+ function dingTalkCapture() {
74
+ return new Promise<StableLocationValue>((resolve, reject) => {
75
+ const geolocation = dingTalkGeolocation();
76
+ if (!geolocation) {
77
+ reject(new Error('当前环境未提供钉钉定位能力'));
78
+ return;
79
+ }
80
+ geolocation.get({
81
+ targetAccuracy: 20,
82
+ coordinate: 0,
83
+ withReGeocode: true,
84
+ useCache: false,
85
+ onSuccess: result => {
86
+ try {
87
+ resolve(dingTalkLocationValue(result));
88
+ } catch (error) {
89
+ reject(error);
90
+ }
91
+ },
92
+ onFail: error => reject(new Error(
93
+ typeof error === 'object' && error && 'errorMessage' in error
94
+ ? String(error.errorMessage)
95
+ : '钉钉定位失败'
96
+ )),
97
+ });
98
+ });
99
+ }
100
+
101
+ export function LocationField({
102
+ value,
103
+ onChange,
104
+ disabled,
105
+ mobile = false,
106
+ }: {
107
+ value?: StableLocationValue;
108
+ onChange?: (value: StableLocationValue | undefined) => void;
109
+ disabled?: boolean;
110
+ mobile?: boolean;
111
+ }) {
112
+ const [capturing, setCapturing] = useState<CaptureSource>();
113
+ const [error, setError] = useState('');
114
+ const hasDingTalk = Boolean(dingTalkGeolocation());
115
+
116
+ const capture = async (source: CaptureSource) => {
117
+ setCapturing(source);
118
+ setError('');
119
+ try {
120
+ onChange?.(await (source === 'dingTalk' ? dingTalkCapture() : browserCapture()));
121
+ } catch (captureError) {
122
+ setError(captureError instanceof Error ? captureError.message : String(captureError));
123
+ } finally {
124
+ setCapturing(undefined);
125
+ }
126
+ };
127
+
128
+ return (
129
+ <div className={`oxa-location-field${mobile ? ' oxa-mobile-location-field' : ''}`}>
130
+ <Space wrap>
131
+ <Tooltip title={hasDingTalk ? '使用钉钉定位' : '仅在钉钉客户端内可用'}>
132
+ <Button
133
+ disabled={disabled || !hasDingTalk}
134
+ icon={<EnvironmentOutlined />}
135
+ loading={capturing === 'dingTalk'}
136
+ onClick={() => void capture('dingTalk')}
137
+ >
138
+ 钉钉定位
139
+ </Button>
140
+ </Tooltip>
141
+ <Button
142
+ disabled={disabled}
143
+ icon={<AimOutlined />}
144
+ loading={capturing === 'browser'}
145
+ onClick={() => void capture('browser')}
146
+ >
147
+ 浏览器定位
148
+ </Button>
149
+ {value && (
150
+ <Tooltip title="清除定位">
151
+ <Button
152
+ aria-label="清除定位"
153
+ disabled={disabled || Boolean(capturing)}
154
+ icon={<DeleteOutlined />}
155
+ onClick={() => onChange?.(undefined)}
156
+ />
157
+ </Tooltip>
158
+ )}
159
+ </Space>
160
+ {error && <Alert showIcon title={error} type="error" />}
161
+ {value && <LocationValueDisplay value={value} />}
162
+ </div>
163
+ );
164
+ }
@@ -20,7 +20,9 @@ import {
20
20
  Typography,
21
21
  type TreeDataNode,
22
22
  } from 'antd';
23
- import type { DirectoryEntry } from 'openxiangda-contracts/browser';
23
+ import type {
24
+ DirectoryEntry,
25
+ } from 'openxiangda-contracts/browser';
24
26
  import {
25
27
  useCallback,
26
28
  useEffect,
@@ -32,17 +34,18 @@ import {
32
34
  import {
33
35
  browseDepartmentTree,
34
36
  browseDepartmentUsers,
35
- resolveDirectory,
36
37
  searchDirectory,
37
38
  type DirectoryKind,
38
39
  } from '../../platform-client';
40
+ import {
41
+ directoryEntryFromStored,
42
+ directoryStoredValueFromEntries,
43
+ directoryStoredValues,
44
+ type DirectoryStoredValue,
45
+ } from './directory-value';
39
46
 
40
47
  type DepartmentNode = DirectoryEntry & { children?: DepartmentNode[] };
41
48
 
42
- function selectedValues(value?: string | string[]) {
43
- return Array.isArray(value) ? value : value ? [value] : [];
44
- }
45
-
46
49
  function replaceChildren(
47
50
  nodes: DepartmentNode[],
48
51
  parentId: string,
@@ -82,8 +85,8 @@ export function PlatformDirectoryPicker({
82
85
  id,
83
86
  }: {
84
87
  kind: DirectoryKind;
85
- value?: string | string[];
86
- onChange?: (value: string | string[] | undefined) => void;
88
+ value?: DirectoryStoredValue | DirectoryStoredValue[];
89
+ onChange?: (value: DirectoryStoredValue | DirectoryStoredValue[] | undefined) => void;
87
90
  multiple?: boolean;
88
91
  disabled?: boolean;
89
92
  placeholder: string;
@@ -91,48 +94,24 @@ export function PlatformDirectoryPicker({
91
94
  }) {
92
95
  const screens = Grid.useBreakpoint();
93
96
  const mobile = !screens.md;
94
- const values = useMemo(() => selectedValues(value), [value]);
97
+ const values = useMemo(() => directoryStoredValues(value), [value]);
95
98
  const [open, setOpen] = useState(false);
96
- const [selected, setSelected] = useState<DirectoryEntry[]>([]);
97
- const [resolving, setResolving] = useState(false);
98
- const [resolveFailed, setResolveFailed] = useState(false);
99
+ const [selected, setSelected] = useState<DirectoryEntry[]>(() =>
100
+ values.map(item => directoryEntryFromStored(kind, item))
101
+ );
99
102
 
100
103
  useEffect(() => {
101
- if (!values.length) {
102
- setSelected([]);
103
- setResolveFailed(false);
104
- return;
105
- }
106
- let active = true;
107
- setResolving(true);
108
- setResolveFailed(false);
109
- void resolveDirectory(kind, values)
110
- .then(page => {
111
- if (active) setSelected(page.items);
112
- })
113
- .catch(() => {
114
- if (active) setResolveFailed(true);
115
- })
116
- .finally(() => {
117
- if (active) setResolving(false);
118
- });
119
- return () => {
120
- active = false;
121
- };
104
+ setSelected(values.map(item => directoryEntryFromStored(kind, item)));
122
105
  }, [kind, values]);
123
106
 
124
107
  const remove = (entryId: string) => {
125
108
  const next = selected.filter(entry => entry.id !== entryId);
126
109
  setSelected(next);
127
- onChange?.(
128
- multiple ? next.map(entry => entry.id) : next[0]?.id || undefined
129
- );
110
+ onChange?.(directoryStoredValueFromEntries(next, multiple));
130
111
  };
131
112
  const confirm = (items: DirectoryEntry[]) => {
132
113
  setSelected(items);
133
- onChange?.(
134
- multiple ? items.map(entry => entry.id) : items[0]?.id || undefined
135
- );
114
+ onChange?.(directoryStoredValueFromEntries(items, multiple));
136
115
  setOpen(false);
137
116
  };
138
117
  const panel = (
@@ -168,11 +147,7 @@ export function PlatformDirectoryPicker({
168
147
  {kind === 'user' ? <UserOutlined /> : <ApartmentOutlined />}
169
148
  </span>
170
149
  <span className="oxa-directory-trigger-values">
171
- {resolving ? (
172
- <Typography.Text type="secondary">读取中…</Typography.Text>
173
- ) : resolveFailed ? (
174
- <Typography.Text type="danger">目录解析失败</Typography.Text>
175
- ) : selected.length ? (
150
+ {selected.length ? (
176
151
  selected.map(entry => (
177
152
  <Tag
178
153
  closable={!disabled}
@@ -0,0 +1,67 @@
1
+ import { Form } from 'antd';
2
+ import type {
3
+ DataFieldSurface,
4
+ ResourceReferenceValue,
5
+ } from 'openxiangda-contracts/browser';
6
+ import {
7
+ AuthoritativeSelector,
8
+ MobileAuthoritativeSelector,
9
+ } from '../../AuthoritativeSelector';
10
+
11
+ export function ResourceReferenceField({
12
+ field,
13
+ fieldCode,
14
+ resourceCode,
15
+ operation,
16
+ disabled,
17
+ mobile,
18
+ value,
19
+ onChange,
20
+ }: {
21
+ field: DataFieldSurface;
22
+ fieldCode: string;
23
+ resourceCode?: string;
24
+ operation: 'create' | 'update';
25
+ disabled?: boolean;
26
+ mobile?: boolean;
27
+ value?: ResourceReferenceValue | ResourceReferenceValue[];
28
+ onChange?: (
29
+ value: ResourceReferenceValue | ResourceReferenceValue[] | undefined
30
+ ) => void;
31
+ }) {
32
+ const form = Form.useFormInstance();
33
+ const formValues = Form.useWatch([], form) as
34
+ | Record<string, unknown>
35
+ | undefined;
36
+ const bindings = Object.fromEntries(
37
+ (field.source?.filters || []).flatMap(filter =>
38
+ 'binding' in filter
39
+ ? [[filter.binding.field, formValues?.[filter.binding.field]]]
40
+ : []
41
+ )
42
+ );
43
+ const Selector = mobile
44
+ ? MobileAuthoritativeSelector
45
+ : AuthoritativeSelector;
46
+ return (
47
+ <Selector
48
+ bindings={bindings}
49
+ disabled={disabled}
50
+ fieldCode={fieldCode}
51
+ multiple={field.type === 'resource-ref.multiple'}
52
+ onChange={next =>
53
+ onChange?.(
54
+ next as
55
+ | ResourceReferenceValue
56
+ | ResourceReferenceValue[]
57
+ | undefined
58
+ )
59
+ }
60
+ operation={operation}
61
+ placeholder={`搜索并选择${field.label}`}
62
+ resourceCode={resourceCode}
63
+ source="resource"
64
+ value={value}
65
+ />
66
+ );
67
+ }