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