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
@@ -23,13 +23,32 @@ test.describe('Field Kit protocol acceptance', () => {
23
23
  },
24
24
  });
25
25
  });
26
+ await page.route('**/files/*/content?*', async route => {
27
+ await route.fulfill({
28
+ body: Buffer.from(
29
+ 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
30
+ 'base64'
31
+ ),
32
+ contentType: 'image/png',
33
+ status: 200,
34
+ });
35
+ });
26
36
  });
27
37
 
28
38
  test('renders and operates every desktop edit/read component', async ({ page }) => {
39
+ const pageErrors: string[] = [];
40
+ const addressRequests: string[] = [];
41
+ page.on('pageerror', error => pageErrors.push(error.message));
42
+ page.on('request', request => {
43
+ if (request.url().includes('/china-divisions/')) {
44
+ addressRequests.push(request.url());
45
+ }
46
+ });
29
47
  await page.setViewportSize({ width: 1440, height: 1100 });
30
48
  await page.goto('/field-protocol.e2e.html');
31
49
  await expect(page.locator('[data-field-code]')).toHaveCount(fieldCount);
32
50
  await expect(page.locator('[data-read-field]')).toHaveCount(fieldCount);
51
+ await expect(page.locator('[data-audit-field]')).toHaveCount(fieldCount);
33
52
  await expect(page.locator('[data-field-code="静态单选下拉"] .ant-select')).toBeVisible();
34
53
  await expect(page.locator('[data-field-code="单选按钮"] .ant-radio-group')).toBeVisible();
35
54
  await expect(page.locator('[data-field-code="复选框"] .ant-checkbox-group')).toBeVisible();
@@ -37,6 +56,22 @@ test.describe('Field Kit protocol acceptance', () => {
37
56
  await expect(page.locator('[data-field-code="精确定位"] input')).toHaveCount(0);
38
57
  await expect(page.locator('[data-field-code="精确定位"]')).not.toContainText('手工');
39
58
  await expect(page.locator('.ant-alert-error')).toHaveCount(0);
59
+ await expect.poll(() => addressRequests.length).toBeGreaterThan(0);
60
+ expect(addressRequests.every(url => new URL(url).pathname.startsWith('/service/china-divisions/'))).toBe(true);
61
+
62
+ await page.getByRole('button', { name: '验证表单' }).click();
63
+ await expect(page.locator('[data-field-code="UUID"] .ant-form-item-has-error')).toHaveCount(0);
64
+
65
+ await expect(page.locator('[data-read-field="图片"] img.oxa-file-thumbnail')).toHaveAttribute('src', /blob:/);
66
+ await expect(page.locator('[data-read-field="富文本"] img')).toHaveAttribute('src', /blob:/);
67
+ expect(await page.locator('[data-read-field="图片"] img.oxa-file-thumbnail').evaluate(
68
+ image => (image as HTMLImageElement).naturalWidth
69
+ )).toBeGreaterThan(0);
70
+ expect(await page.locator('[data-read-field="富文本"] img').evaluate(
71
+ image => (image as HTMLImageElement).naturalWidth
72
+ )).toBeGreaterThan(0);
73
+ await expect(page.locator('[aria-label="当前协议字段变更记录"]')).not.toContainText('[object Object]');
74
+ await expect(page.locator('[data-audit-digest="rich-text"]')).toContainText('大值摘要');
40
75
 
41
76
  await page.locator('[data-field-code="精确定位"]').getByRole('button', { name: '浏览器定位' }).click();
42
77
  await expect(page.locator('[data-field-code="精确定位"]')).toContainText('120.123456');
@@ -78,12 +113,63 @@ test.describe('Field Kit protocol acceptance', () => {
78
113
  await saveSignature.click();
79
114
  await expect(page.locator('[data-field-code="业务签名"]')).not.toContainText('尚未签名');
80
115
 
116
+ expect(pageErrors).toEqual([]);
117
+
81
118
  expect(
82
119
  await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)
83
120
  ).toBe(true);
84
121
  await page.screenshot({ fullPage: true, path: 'test-results/field-protocol-desktop.png' });
85
122
  });
86
123
 
124
+ test('resource selectors query once when selection changes', async ({ page }) => {
125
+ const requestCounts = new Map<string, number>();
126
+ await page.route('**/source/query', async route => {
127
+ const match = new URL(route.request().url()).pathname.match(/\/fields\/([^/]+)\/source\/query$/);
128
+ const fieldCode = decodeURIComponent(match?.[1] || 'unknown');
129
+ requestCounts.set(fieldCode, (requestCounts.get(fieldCode) || 0) + 1);
130
+ const body = route.request().postDataJSON() as { operation?: 'create' | 'update' };
131
+ await route.fulfill({
132
+ contentType: 'application/json',
133
+ json: {
134
+ schemaVersion: 'openxiangda.data-field-source-page/v2',
135
+ environment: {
136
+ id: 'field-protocol-environment',
137
+ key: 'preproduction',
138
+ activeAppVersionId: 'field-protocol-version',
139
+ headRevision: 1,
140
+ authzRevisionId: 'field-protocol-authz',
141
+ authzVersion: 1,
142
+ scopeDataVersion: 'field-protocol-scope',
143
+ },
144
+ resourceCode: 'field-records',
145
+ fieldCode,
146
+ sourceResourceCode: 'field-lookups',
147
+ operation: body.operation || 'create',
148
+ items: [{
149
+ label: `候选-${fieldCode}`,
150
+ value: `candidate-${fieldCode}`,
151
+ resourceCode: 'field-lookups',
152
+ snapshot: { code: `code-${fieldCode}` },
153
+ }],
154
+ nextCursor: null,
155
+ },
156
+ });
157
+ });
158
+
159
+ await page.goto('/field-protocol.e2e.html');
160
+ for (const fieldCode of ['资源单选', '资源多选']) {
161
+ await page.locator(`[data-field-code="${fieldCode}"]`).getByRole('combobox').click();
162
+ await expect.poll(() => requestCounts.get(fieldCode) || 0).toBe(1);
163
+ await page
164
+ .locator('.ant-select-dropdown:visible .oxa-selector-option')
165
+ .filter({ hasText: `候选-${fieldCode}` })
166
+ .click();
167
+ await page.waitForTimeout(500);
168
+ expect(requestCounts.get(fieldCode)).toBe(1);
169
+ await page.keyboard.press('Escape');
170
+ }
171
+ });
172
+
87
173
  test('renders every mobile control without horizontal overflow', async ({ page }) => {
88
174
  await page.setViewportSize({ width: 390, height: 844 });
89
175
  await page.goto('/field-protocol.e2e.html?mode=mobile');
@@ -21,119 +21,26 @@ const definitions = resourceDefinitions as Record<string, DeclaredResource>;
21
21
  const allCapabilities = [...capabilities] as string[];
22
22
  const runtimeBase = `/view/${appCode}`;
23
23
 
24
- const roleSubjects = [
25
- {
26
- subjectKey: 'membership:college-admin',
27
- subjectKind: 'membership',
28
- role: {
29
- code: 'college_admin',
30
- name: '学院管理员',
31
- source: 'package',
32
- description: '管理本学院业务数据',
33
- },
34
- revision: 1,
35
- scopeDimensionCount: 1,
36
- scopeSummary: [],
37
- scopeSummaryTruncated: false,
38
- validFrom: null,
39
- validTo: null,
40
- },
41
- {
42
- subjectKey: 'membership:instrument-admin',
43
- subjectKind: 'membership',
44
- role: {
45
- code: 'instrument_admin',
46
- name: '仪器管理员',
47
- source: 'package',
48
- description: '管理分配的仪器',
49
- },
50
- revision: 1,
51
- scopeDimensionCount: 0,
52
- scopeSummary: [],
53
- scopeSummaryTruncated: false,
54
- validFrom: null,
55
- validTo: null,
56
- },
57
- ] as const;
58
-
59
- function roleContext(subjectKey: string, authorized: boolean) {
60
- const subject = roleSubjects.find(item => item.subjectKey === subjectKey)!;
61
- const roleSessionId = `role-session-${subject.role.code}`;
24
+ function runtimeAuthorization(authorized: boolean) {
62
25
  return {
63
- schemaVersion: 'openxiangda.role-session-context/v2',
26
+ schemaVersion: 'openxiangda.runtime-authorization/v2',
64
27
  state: 'active',
65
28
  environment: {
66
29
  id: 'preproduction-id',
67
30
  key: 'preproduction',
68
- kind: 'preproduction',
69
31
  activeAppVersionId: 'app-version-1',
70
32
  headRevision: 1,
33
+ authzRevisionId: 'authz-1',
71
34
  authzVersion: 1,
72
- roleSubjectSetVersion: 'subjects-1',
73
- },
74
- identityScope: `preproduction-id:${subjectKey}:1`,
75
- subjectProfile: {
76
- schemaVersion: 'openxiangda.subject-profile/v2',
77
- userId: 'acceptance-user',
78
- displayName: '验收用户',
79
- avatarUrl: null,
80
- jobNumber: null,
81
- affiliatedDepartment: { id: 'department-1', name: '实验中心' },
82
- },
83
- currentRoleSubject: subject,
84
- roleSubjectPage: {
85
- schemaVersion: 'openxiangda.role-subject-page/v2',
86
- items: roleSubjects,
87
- total: roleSubjects.length,
88
- nextCursor: null,
89
- roleSubjectSetVersion: 'subjects-1',
90
- },
91
- roleSession: {
92
- schemaVersion: 'openxiangda.native-role-session/v2',
93
- id: roleSessionId,
94
- environmentId: 'preproduction-id',
95
- environmentKey: 'preproduction',
96
- loginSessionId: 'login-session-1',
97
- userId: 'acceptance-user',
98
- selectionKey: subjectKey,
99
- subjectKind: 'membership',
100
- activeRoleCode: subject.role.code,
101
- activeRoleSource: 'package',
102
- activeRoleMembershipId: subjectKey.slice('membership:'.length),
103
- activeAuthzRevisionId: 'authz-1',
104
- activeAuthzVersion: 1,
105
- activeAppVersionId: 'app-version-1',
106
- activeEnvironmentHeadRevision: 1,
107
- activeMembershipRevision: 1,
108
- status: 'active',
109
- issuedAt: '2026-08-23T00:00:00.000Z',
110
- expiresAt: '2099-08-23T00:00:00.000Z',
111
- lastUsedAt: '2026-08-23T00:00:00.000Z',
35
+ scopeDataVersion: 'scope-1',
112
36
  },
113
37
  principal: {
114
- schemaVersion: 'openxiangda.native-principal/v2',
115
- tenantId: 'tenant-1',
116
- appCode,
117
- environmentId: 'preproduction-id',
118
- environmentKey: 'preproduction',
119
- activeAppVersionId: 'app-version-1',
120
- environmentHeadRevision: 1,
121
- principalType: 'user',
122
- subjectId: 'acceptance-user',
38
+ type: 'user_union',
123
39
  userId: 'acceptance-user',
124
- loginSessionId: 'login-session-1',
125
- roleSessionId,
126
- subjectKind: 'membership',
127
- activeRoleCode: subject.role.code,
128
- activeRoleSource: 'package',
129
- activeRoleMembershipId: subjectKey.slice('membership:'.length),
130
- activeMembershipRevision: 1,
131
- authzRevisionId: 'authz-1',
132
- authzVersion: 1,
133
- scopeDataVersion: 'scope-1',
40
+ roleCodes: ['college_admin', 'instrument_admin'],
41
+ capabilityCodes: authorized ? allCapabilities : [],
134
42
  isAppSuperAdmin: false,
135
- capabilities: authorized ? allCapabilities : [],
136
- expiresAt: null,
43
+ identityScope: 'user-union:preproduction-id:acceptance-user:authz-1',
137
44
  },
138
45
  };
139
46
  }
@@ -143,10 +50,8 @@ async function mockPlatform(
143
50
  authorized = true,
144
51
  targetRuntimeBase = runtimeBase,
145
52
  ) {
146
- let activeSubjectKey = roleSubjects[0].subjectKey;
147
53
  let contextReads = 0;
148
54
  const observed: Array<{ path: string; roleSessionId: string }> = [];
149
- const switches: Array<Record<string, unknown>> = [];
150
55
  await page.route(`**${targetRuntimeBase}/**`, async route => {
151
56
  if (route.request().resourceType() !== 'document') return route.continue();
152
57
  const response = await route.fetch();
@@ -162,42 +67,13 @@ async function mockPlatform(
162
67
  const url = new URL(route.request().url());
163
68
  const request = route.request();
164
69
  const roleSessionId = request.headers()['x-openxiangda-role-session-id'] || '';
165
- if (url.pathname.endsWith('/native/authz/role-session/context/role-subjects')) {
166
- return route.fulfill({
167
- contentType: 'application/json',
168
- body: JSON.stringify({
169
- code: 200,
170
- data: {
171
- schemaVersion: 'openxiangda.role-subject-page/v2',
172
- items: roleSubjects,
173
- total: roleSubjects.length,
174
- nextCursor: null,
175
- roleSubjectSetVersion: 'subjects-1',
176
- },
177
- }),
178
- });
179
- }
180
- if (url.pathname.endsWith('/native/authz/role-session/context/switch')) {
181
- const body = request.postDataJSON() as Record<string, unknown>;
182
- switches.push(body);
183
- if (roleSubjects.some(item => item.subjectKey === body.targetRoleSubjectKey)) {
184
- activeSubjectKey = String(body.targetRoleSubjectKey) as typeof activeSubjectKey;
185
- }
186
- return route.fulfill({
187
- contentType: 'application/json',
188
- body: JSON.stringify({
189
- code: 200,
190
- data: roleContext(activeSubjectKey, authorized),
191
- }),
192
- });
193
- }
194
- if (url.pathname.endsWith('/native/authz/role-session/context')) {
70
+ if (url.pathname.endsWith('/native/authz/current')) {
195
71
  contextReads += 1;
196
72
  return route.fulfill({
197
73
  contentType: 'application/json',
198
74
  body: JSON.stringify({
199
75
  code: 200,
200
- data: roleContext(activeSubjectKey, authorized),
76
+ data: runtimeAuthorization(authorized),
201
77
  }),
202
78
  });
203
79
  }
@@ -262,7 +138,6 @@ async function mockPlatform(
262
138
  });
263
139
  return {
264
140
  observed,
265
- switches,
266
141
  get contextReads() {
267
142
  return contextReads;
268
143
  },
@@ -314,7 +189,7 @@ test('opens the stable admin entry under the preproduction gateway mount', async
314
189
  await expect(page).toHaveURL(`${devRuntimeBase}/${codes[0]}`);
315
190
  });
316
191
 
317
- test('binds Data, Directory, App API, and role switching to one active RoleSession', async ({ page }) => {
192
+ test('uses the current user role union without exposing a RoleSession header', async ({ page }) => {
318
193
  const platform = await mockPlatform(page);
319
194
  await page.goto(`${runtimeBase}/admin`);
320
195
  if (codes.length) {
@@ -324,7 +199,7 @@ test('binds Data, Directory, App API, and role switching to one active RoleSessi
324
199
  }
325
200
 
326
201
  await page.evaluate(async () => {
327
- const client = await import('/src/platform-client.ts');
202
+ const client = await import('/e2e/client-fixture.ts');
328
203
  await client.searchResource('acceptance-resource', 'name', {
329
204
  keyword: '显微镜',
330
205
  });
@@ -332,55 +207,19 @@ test('binds Data, Directory, App API, and role switching to one active RoleSessi
332
207
  await client.requestApplicationApi('/health/current-user');
333
208
  });
334
209
 
335
- const initialSession = 'role-session-college_admin';
336
- expect(
337
- platform.observed.filter(item =>
338
- /\/native\/data(?:-resources)?\/|\/directory\/|\/openxiangda-app-api\/v2\//.test(
339
- item.path
340
- )
210
+ const protectedRequests = platform.observed.filter(item =>
211
+ /\/native\/data(?:-resources)?\/|\/directory\/|\/openxiangda-app-api\/v2\//.test(
212
+ item.path
341
213
  )
342
- ).toEqual(
343
- expect.arrayContaining([
344
- expect.objectContaining({ roleSessionId: initialSession }),
345
- expect.objectContaining({ roleSessionId: initialSession }),
346
- expect.objectContaining({ roleSessionId: initialSession }),
347
- ])
348
214
  );
349
-
350
- await page.locator('.oxa-current-user').click();
351
- await page.getByText('切换应用角色', { exact: true }).click();
352
- await page.getByRole('combobox', { name: '选择应用角色' }).click();
353
- await page.getByText('仪器管理员', { exact: true }).click();
354
- await page.getByRole('button', { name: '切换角色' }).click();
355
-
356
- await expect.poll(() => platform.switches.length).toBe(1);
357
- expect(platform.switches[0]).toMatchObject({
358
- targetRoleSubjectKey: 'membership:instrument-admin',
359
- expectedRoleSessionId: initialSession,
360
- environmentKey: 'preproduction',
361
- });
362
- await expect.poll(() => platform.contextReads).toBeGreaterThan(1);
215
+ expect(protectedRequests.length).toBeGreaterThanOrEqual(3);
216
+ expect(protectedRequests.every(item => item.roleSessionId === '')).toBe(true);
217
+ expect(platform.contextReads).toBe(1);
363
218
  await page.locator('.oxa-current-user').click();
364
219
  await expect(page.locator('.oxa-user-dropdown-roles')).toContainText(
365
- '仪器管理员'
220
+ 'college_admin、instrument_admin'
366
221
  );
367
- await page.keyboard.press('Escape');
368
- await page.evaluate(async () => {
369
- const client = await import('/src/platform-client.ts');
370
- await client.searchResource('acceptance-resource', 'name', {
371
- keyword: '光谱仪',
372
- });
373
- });
374
- await expect
375
- .poll(
376
- () =>
377
- platform.observed.filter(
378
- item =>
379
- item.path.includes('/native/data-resources/') &&
380
- item.roleSessionId === 'role-session-instrument_admin'
381
- ).length
382
- )
383
- .toBeGreaterThan(0);
222
+ await expect(page.getByText('切换应用角色', { exact: true })).toHaveCount(0);
384
223
  });
385
224
 
386
225
  test('renders platform mobile controls from declared field widgets', async ({ page }) => {
@@ -3,6 +3,9 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="openxiangda-runtime-base" content="/" />
7
+ <meta name="openxiangda-app-code" content="field-protocol-fixture" />
8
+ <meta name="openxiangda-environment" content="preproduction" />
6
9
  <title>Field protocol acceptance</title>
7
10
  </head>
8
11
  <body>
@@ -14,13 +14,10 @@
14
14
  "@ant-design/icons": "6.2.3",
15
15
  "@refinedev/core": "5.0.12",
16
16
  "antd": "6.4.3",
17
- "dayjs": "1.11.18",
18
- "docx-preview": "0.3.7",
19
- "openxiangda-contracts": "2.0.0-alpha.43",
17
+ "openxiangda": "2.0.0-alpha.3",
20
18
  "react": "19.2.8",
21
19
  "react-dom": "19.2.8",
22
- "react-router-dom": "7.8.2",
23
- "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
20
+ "react-router-dom": "7.8.2"
24
21
  },
25
22
  "devDependencies": {
26
23
  "@playwright/test": "1.62.1",
@@ -1,67 +1,19 @@
1
- import { Refine } from '@refinedev/core';
2
1
  import React from 'react';
3
2
  import ReactDOM from 'react-dom/client';
4
- import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
5
- import { resourceDefinitions } from '../../../packages/contracts/src/generated.js';
6
- import { applicationProvider } from './data-provider';
7
- import { AppearanceProvider } from './appearance';
8
- import { GeneratedResourcePage } from './components/resource/GeneratedResourceCrud';
9
- import { FilePreviewPage } from './FilePreviewPage';
10
- import { EmptyApplicationPage } from './Shell';
11
- import { RuntimeBoundary } from './runtime';
12
- import { applicationBasename } from './runtime-meta';
13
- import { useGlobalRequestLoading } from './platform-client';
14
- import './styles.css';
15
-
16
- const codes = Object.keys(resourceDefinitions);
17
- const firstPath = codes[0] ? `/${codes[0]}` : '/';
18
- const adminEntry = codes.length ? (
19
- <Navigate replace to={firstPath} />
20
- ) : (
21
- <EmptyApplicationPage />
22
- );
23
-
24
- function GlobalRequestLoading() {
25
- const loading = useGlobalRequestLoading();
26
- return loading ? (
27
- <div
28
- aria-label="页面请求进行中"
29
- aria-valuetext="正在加载"
30
- className="oxa-global-request-loading"
31
- role="progressbar"
32
- >
33
- <span />
34
- </div>
35
- ) : null;
36
- }
37
-
38
- const resourceRoutes = codes.flatMap(resourceCode => [
39
- <Route key={`${resourceCode}-list`} path={`/${resourceCode}`} element={<GeneratedResourcePage resourceCode={resourceCode} />} />,
40
- <Route key={`${resourceCode}-new`} path={`/${resourceCode}/new`} element={<GeneratedResourcePage mode="create" resourceCode={resourceCode} />} />,
41
- <Route key={`${resourceCode}-detail`} path={`/${resourceCode}/:id`} element={<GeneratedResourcePage mode="detail" resourceCode={resourceCode} />} />,
42
- <Route key={`${resourceCode}-edit`} path={`/${resourceCode}/:id/edit`} element={<GeneratedResourcePage mode="edit" resourceCode={resourceCode} />} />,
43
- <Route key={`${resourceCode}-mobile-list`} path={`/m/${resourceCode}`} element={<GeneratedResourcePage resourceCode={resourceCode} variant="mobile" />} />,
44
- <Route key={`${resourceCode}-mobile-new`} path={`/m/${resourceCode}/new`} element={<GeneratedResourcePage mode="create" resourceCode={resourceCode} variant="mobile" />} />,
45
- <Route key={`${resourceCode}-mobile-detail`} path={`/m/${resourceCode}/:id`} element={<GeneratedResourcePage mode="detail" resourceCode={resourceCode} variant="mobile" />} />,
46
- <Route key={`${resourceCode}-mobile-edit`} path={`/m/${resourceCode}/:id/edit`} element={<GeneratedResourcePage mode="edit" resourceCode={resourceCode} variant="mobile" />} />,
47
- ]);
3
+ import { OpenXiangdaApplication } from 'openxiangda/react';
4
+ import {
5
+ appCode,
6
+ appName,
7
+ resourceDefinitions,
8
+ } from '../../../packages/contracts/src/generated.js';
9
+ import 'openxiangda/react/styles.css';
48
10
 
49
11
  ReactDOM.createRoot(document.getElementById('root')!).render(
50
12
  <React.StrictMode>
51
- <AppearanceProvider>
52
- <GlobalRequestLoading />
53
- <RuntimeBoundary>
54
- <BrowserRouter basename={applicationBasename()}>
55
- <Refine dataProvider={applicationProvider} resources={codes.map(code => ({ name: code, list: `/${code}`, create: `/${code}/new`, edit: `/${code}/:id/edit`, show: `/${code}/:id` }))}>
56
- <Routes>
57
- <Route path="/" element={adminEntry} />
58
- <Route path="/admin" element={adminEntry} />
59
- <Route path="/files/:resourceCode/:fileId/preview" element={<FilePreviewPage />} />
60
- {resourceRoutes}
61
- </Routes>
62
- </Refine>
63
- </BrowserRouter>
64
- </RuntimeBoundary>
65
- </AppearanceProvider>
13
+ <OpenXiangdaApplication
14
+ appCode={appCode}
15
+ appName={appName}
16
+ resourceDefinitions={resourceDefinitions}
17
+ />
66
18
  </React.StrictMode>,
67
19
  );