openxiangda-cli 2.0.0-alpha.89 → 2.0.0-alpha.91

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 (77) hide show
  1. package/README.md +2 -2
  2. package/bin/run.js +1 -0
  3. package/dist/commands/skill.d.ts +26 -0
  4. package/dist/commands/skill.d.ts.map +1 -0
  5. package/dist/commands/skill.js +43 -0
  6. package/dist/commands/skill.js.map +1 -0
  7. package/dist/create-workspace.d.ts.map +1 -1
  8. package/dist/create-workspace.js +3 -3
  9. package/dist/create-workspace.js.map +1 -1
  10. package/dist/toolchain-capsule.d.ts.map +1 -1
  11. package/dist/toolchain-capsule.js +4 -8
  12. package/dist/toolchain-capsule.js.map +1 -1
  13. package/package.json +6 -7
  14. package/template/AGENTS.md +2 -1
  15. package/template/README.md +3 -3
  16. package/template/apps/server/package.json +1 -1
  17. package/template/apps/server/src/app.module.ts +1 -1
  18. package/template/apps/server/src/main.ts +1 -1
  19. package/template/apps/web/e2e/client-fixture.ts +5 -0
  20. package/template/apps/web/e2e/data-api-live-fixture.tsx +19 -14
  21. package/template/apps/web/e2e/field-protocol-fixture.tsx +87 -20
  22. package/template/apps/web/e2e/field-protocol.spec.ts +86 -0
  23. package/template/apps/web/e2e/resources.spec.ts +20 -181
  24. package/template/apps/web/field-protocol.e2e.html +3 -0
  25. package/template/apps/web/package.json +2 -5
  26. package/template/apps/web/src/main.tsx +12 -60
  27. package/template/apps/web/test/contracts.test.ts +52 -104
  28. package/template/openxiangda.config.ts +1 -1
  29. package/template/package.json +1 -3
  30. package/template/packages/contracts/src/generated.ts +1 -0
  31. package/template/scripts/verify-template-budget.mjs +2 -2
  32. package/template/apps/web/src/AuthoritativeSelector.tsx +0 -661
  33. package/template/apps/web/src/FilePreviewPage.tsx +0 -324
  34. package/template/apps/web/src/RoleSubjectSelect.tsx +0 -128
  35. package/template/apps/web/src/Shell.tsx +0 -445
  36. package/template/apps/web/src/appearance.tsx +0 -147
  37. package/template/apps/web/src/components/platform-fields/AddressField.tsx +0 -338
  38. package/template/apps/web/src/components/platform-fields/AttachmentFileList.tsx +0 -245
  39. package/template/apps/web/src/components/platform-fields/CascadeField.tsx +0 -49
  40. package/template/apps/web/src/components/platform-fields/DateTimeField.tsx +0 -148
  41. package/template/apps/web/src/components/platform-fields/JsonField.tsx +0 -77
  42. package/template/apps/web/src/components/platform-fields/LocationField.tsx +0 -164
  43. package/template/apps/web/src/components/platform-fields/PlatformDirectoryPicker.tsx +0 -673
  44. package/template/apps/web/src/components/platform-fields/ResourceReferenceField.tsx +0 -67
  45. package/template/apps/web/src/components/platform-fields/RichTextField.tsx +0 -196
  46. package/template/apps/web/src/components/platform-fields/SignatureField.tsx +0 -315
  47. package/template/apps/web/src/components/platform-fields/SubtableField.tsx +0 -553
  48. package/template/apps/web/src/components/platform-fields/address-value.ts +0 -80
  49. package/template/apps/web/src/components/platform-fields/cascade-value.ts +0 -66
  50. package/template/apps/web/src/components/platform-fields/directory-value.ts +0 -53
  51. package/template/apps/web/src/components/platform-fields/field-form-codec.ts +0 -98
  52. package/template/apps/web/src/components/platform-fields/location-value.ts +0 -87
  53. package/template/apps/web/src/components/platform-fields/resource-query.ts +0 -131
  54. package/template/apps/web/src/components/platform-fields/rich-text-value.ts +0 -59
  55. package/template/apps/web/src/components/platform-fields/subtable-value.ts +0 -187
  56. package/template/apps/web/src/components/resource/GeneratedResourceCrud.tsx +0 -1184
  57. package/template/apps/web/src/components/resource/ResourceBatchActions.tsx +0 -326
  58. package/template/apps/web/src/components/resource/StandardResourcePages.tsx +0 -392
  59. package/template/apps/web/src/components/resource/SurfaceFields.tsx +0 -915
  60. package/template/apps/web/src/components/resource/generated-resource-definition.ts +0 -18
  61. package/template/apps/web/src/components/resource/resource-import.ts +0 -205
  62. package/template/apps/web/src/data-provider.ts +0 -72
  63. package/template/apps/web/src/platform-client.ts +0 -708
  64. package/template/apps/web/src/runtime-meta.ts +0 -75
  65. package/template/apps/web/src/runtime.tsx +0 -227
  66. package/template/apps/web/src/styles.css +0 -1941
  67. package/template/apps/web/test/address-field.test.ts +0 -66
  68. package/template/apps/web/test/cascade-value.test.ts +0 -68
  69. package/template/apps/web/test/directory-value.test.ts +0 -55
  70. package/template/apps/web/test/field-bounds.test.ts +0 -17
  71. package/template/apps/web/test/field-form-codec.test.ts +0 -85
  72. package/template/apps/web/test/location-field.test.ts +0 -83
  73. package/template/apps/web/test/resource-import.test.ts +0 -143
  74. package/template/apps/web/test/resource-query.test.ts +0 -144
  75. package/template/apps/web/test/rich-json-field.test.ts +0 -53
  76. package/template/apps/web/test/signature-serial-field.test.ts +0 -40
  77. 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
- }