openxiangda-cli 2.0.0-alpha.86 → 2.0.0-alpha.90
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/README.md +2 -2
- package/bin/run.js +1 -0
- package/dist/commands/skill.d.ts +26 -0
- package/dist/commands/skill.d.ts.map +1 -0
- package/dist/commands/skill.js +43 -0
- package/dist/commands/skill.js.map +1 -0
- package/dist/create-workspace.d.ts.map +1 -1
- package/dist/create-workspace.js +3 -3
- package/dist/create-workspace.js.map +1 -1
- package/dist/toolchain-capsule.d.ts.map +1 -1
- package/dist/toolchain-capsule.js +4 -8
- package/dist/toolchain-capsule.js.map +1 -1
- package/package.json +6 -7
- package/template/AGENTS.md +2 -1
- package/template/README.md +5 -5
- package/template/apps/server/package.json +1 -1
- package/template/apps/server/src/app.module.ts +14 -1
- package/template/apps/server/src/main.ts +1 -1
- package/template/apps/server/test/smoke.test.ts +3 -1
- package/template/apps/web/e2e/client-fixture.ts +5 -0
- package/template/apps/web/e2e/data-api-live-fixture.tsx +19 -14
- package/template/apps/web/e2e/field-protocol-fixture.tsx +87 -20
- package/template/apps/web/e2e/field-protocol.spec.ts +86 -0
- package/template/apps/web/e2e/resources.spec.ts +20 -181
- package/template/apps/web/field-protocol.e2e.html +3 -0
- package/template/apps/web/package.json +2 -5
- package/template/apps/web/src/main.tsx +12 -60
- package/template/apps/web/test/contracts.test.ts +52 -104
- package/template/openxiangda.config.ts +2 -2
- package/template/package.json +1 -3
- package/template/packages/contracts/src/generated.ts +27 -0
- package/template/scripts/verify-template-budget.mjs +2 -2
- package/template/apps/web/src/AuthoritativeSelector.tsx +0 -661
- package/template/apps/web/src/FilePreviewPage.tsx +0 -324
- package/template/apps/web/src/RoleSubjectSelect.tsx +0 -128
- package/template/apps/web/src/Shell.tsx +0 -445
- package/template/apps/web/src/appearance.tsx +0 -147
- package/template/apps/web/src/components/platform-fields/AddressField.tsx +0 -338
- package/template/apps/web/src/components/platform-fields/AttachmentFileList.tsx +0 -245
- package/template/apps/web/src/components/platform-fields/CascadeField.tsx +0 -49
- package/template/apps/web/src/components/platform-fields/DateTimeField.tsx +0 -148
- package/template/apps/web/src/components/platform-fields/JsonField.tsx +0 -77
- package/template/apps/web/src/components/platform-fields/LocationField.tsx +0 -164
- package/template/apps/web/src/components/platform-fields/PlatformDirectoryPicker.tsx +0 -673
- package/template/apps/web/src/components/platform-fields/ResourceReferenceField.tsx +0 -67
- package/template/apps/web/src/components/platform-fields/RichTextField.tsx +0 -196
- package/template/apps/web/src/components/platform-fields/SignatureField.tsx +0 -315
- package/template/apps/web/src/components/platform-fields/SubtableField.tsx +0 -553
- package/template/apps/web/src/components/platform-fields/address-value.ts +0 -80
- package/template/apps/web/src/components/platform-fields/cascade-value.ts +0 -66
- package/template/apps/web/src/components/platform-fields/directory-value.ts +0 -53
- package/template/apps/web/src/components/platform-fields/field-form-codec.ts +0 -98
- package/template/apps/web/src/components/platform-fields/location-value.ts +0 -87
- package/template/apps/web/src/components/platform-fields/resource-query.ts +0 -131
- package/template/apps/web/src/components/platform-fields/rich-text-value.ts +0 -59
- package/template/apps/web/src/components/platform-fields/subtable-value.ts +0 -187
- package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +0 -1184
- package/template/apps/web/src/components/resource/ResourceBatchActions.tsx +0 -326
- package/template/apps/web/src/components/resource/StandardResourcePages.tsx +0 -392
- package/template/apps/web/src/components/resource/SurfaceFields.tsx +0 -915
- package/template/apps/web/src/components/resource/generated-resource-definition.ts +0 -18
- package/template/apps/web/src/components/resource/resource-import.ts +0 -205
- package/template/apps/web/src/data-provider.ts +0 -72
- package/template/apps/web/src/platform-client.ts +0 -708
- package/template/apps/web/src/runtime-meta.ts +0 -75
- package/template/apps/web/src/runtime.tsx +0 -227
- package/template/apps/web/src/styles.css +0 -1941
- package/template/apps/web/test/address-field.test.ts +0 -66
- package/template/apps/web/test/cascade-value.test.ts +0 -68
- package/template/apps/web/test/directory-value.test.ts +0 -55
- package/template/apps/web/test/field-bounds.test.ts +0 -17
- package/template/apps/web/test/field-form-codec.test.ts +0 -85
- package/template/apps/web/test/location-field.test.ts +0 -83
- package/template/apps/web/test/resource-import.test.ts +0 -143
- package/template/apps/web/test/resource-query.test.ts +0 -144
- package/template/apps/web/test/rich-json-field.test.ts +0 -53
- package/template/apps/web/test/signature-serial-field.test.ts +0 -40
- package/template/apps/web/test/subtable-value.test.ts +0 -144
|
@@ -1,148 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,164 +0,0 @@
|
|
|
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
|
-
}
|