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,18 @@
1
+ import type { DataResourceSurface } from 'openxiangda-contracts/browser';
2
+
3
+ export interface GeneratedResourceDefinition {
4
+ code: string;
5
+ name: string;
6
+ capabilities: {
7
+ read: string;
8
+ create: string;
9
+ update: string;
10
+ delete: string;
11
+ };
12
+ surface: DataResourceSurface;
13
+ }
14
+
15
+ export type GeneratedResourceDefinitions = Record<
16
+ string,
17
+ GeneratedResourceDefinition
18
+ >;
@@ -82,7 +82,7 @@ export function parseResourceImportMatrix(
82
82
  if (!code) return null;
83
83
  const field = surface.fields[code];
84
84
  if (!field) return null;
85
- if (field.widget === 'file') {
85
+ if (['file', 'image', 'signature'].includes(field.type)) {
86
86
  errors.push(`文件字段“${field.label}”必须通过平台上传组件录入`);
87
87
  return null;
88
88
  }
@@ -153,7 +153,7 @@ function importValue(field: DataResourceSurface['fields'][string], input: unknow
153
153
  if (!Number.isFinite(value)) throw new Error('必须是数字');
154
154
  return value;
155
155
  }
156
- if (field.widget === 'boolean') {
156
+ if (field.type === 'boolean') {
157
157
  if (input === true || input === 1 || /^(?:是|true|yes|1)$/i.test(String(input).trim())) return true;
158
158
  if (input === false || input === 0 || /^(?:否|false|no|0)$/i.test(String(input).trim())) return false;
159
159
  throw new Error('请填写是或否');
@@ -185,7 +185,7 @@ function importValue(field: DataResourceSurface['fields'][string], input: unknow
185
185
  throw new Error('必须是有效 JSON');
186
186
  }
187
187
  }
188
- const multiple = field.widget === 'multi' || field.multiple;
188
+ const multiple = field.type.endsWith('.multiple');
189
189
  const values = multiple
190
190
  ? (Array.isArray(input) ? input : String(input).split(/[、,,;;\n]+/)).map((value) => String(value).trim()).filter(Boolean)
191
191
  : [String(input).trim()];
@@ -193,7 +193,7 @@ function importValue(field: DataResourceSurface['fields'][string], input: unknow
193
193
  const resolved = values.map((value) => {
194
194
  const option = field.options?.find((item) => item.value === value || item.label === value);
195
195
  if (!option) throw new Error(`“${value}”不在可选项中`);
196
- return option.value;
196
+ return { ...option };
197
197
  });
198
198
  return multiple ? resolved : resolved[0];
199
199
  }
@@ -2,46 +2,71 @@ import type { DataProvider } from '@refinedev/core';
2
2
  import { createNativeResourceClient, type GenericResourceQuery } from './platform-client';
3
3
  import { applicationCode } from './runtime-meta';
4
4
  import { resourceDefinitions } from '../../../packages/contracts/src/generated.js';
5
+ import type { GeneratedResourceDefinitions } from './components/resource/generated-resource-definition';
5
6
 
6
- type GeneratedDefinition = {
7
- code: string;
8
- name: string;
9
- capabilities: { read: string; create: string; update: string; delete: string };
10
- surface: import('openxiangda-contracts/browser').DataResourceSurface;
11
- };
12
-
13
- const definitions = resourceDefinitions as unknown as Record<string, GeneratedDefinition>;
7
+ const definitions = resourceDefinitions as unknown as GeneratedResourceDefinitions;
14
8
  type ListParams = Parameters<NonNullable<DataProvider['getList']>>[0];
15
9
  type OneParams = Parameters<NonNullable<DataProvider['getOne']>>[0];
16
10
  type CreateParams = Parameters<NonNullable<DataProvider['create']>>[0];
17
11
  type UpdateParams = Parameters<NonNullable<DataProvider['update']>>[0];
18
12
  type DeleteParams = Parameters<NonNullable<DataProvider['deleteOne']>>[0];
19
13
 
20
- function clientFor(resource: string) {
21
- const definition = definitions[resource];
22
- if (!definition) throw new Error(`OPENXIANGDA_GENERATED_RESOURCE_NOT_DECLARED:${resource}`);
23
- return createNativeResourceClient(resource, definition.surface);
14
+ export function createApplicationProvider(
15
+ declaredDefinitions: GeneratedResourceDefinitions
16
+ ) {
17
+ const clientFor = (resource: string) => {
18
+ const definition = declaredDefinitions[resource];
19
+ if (!definition) {
20
+ throw new Error(
21
+ `OPENXIANGDA_GENERATED_RESOURCE_NOT_DECLARED:${resource}`
22
+ );
23
+ }
24
+ return createNativeResourceClient(resource, definition.surface);
25
+ };
26
+
27
+ return {
28
+ getApiUrl: () =>
29
+ `/service/openxiangda-api/v2/applications/${applicationCode()}/native/data`,
30
+ getList: async ({ resource, pagination, sorters, meta }: ListParams) => {
31
+ const query = (meta?.query || {}) as Partial<GenericResourceQuery>;
32
+ const sorter = sorters?.[0];
33
+ const result = await clientFor(resource).list({
34
+ page: pagination?.currentPage || 1,
35
+ pageSize: pagination?.pageSize || 20,
36
+ ...query,
37
+ ...(sorter
38
+ ? {
39
+ sort: {
40
+ field: String(sorter.field),
41
+ order: (sorter.order || 'asc') as 'asc' | 'desc',
42
+ },
43
+ }
44
+ : {}),
45
+ });
46
+ return { data: result.rows, total: result.total };
47
+ },
48
+ getOne: async ({ resource, id }: OneParams) => ({
49
+ data: await clientFor(resource).get(String(id)),
50
+ }),
51
+ create: async ({ resource, variables }: CreateParams) => ({
52
+ data: await clientFor(resource).create(
53
+ variables as Record<string, unknown>
54
+ ),
55
+ }),
56
+ update: async ({ resource, id, variables, meta }: UpdateParams) => ({
57
+ data: await clientFor(resource).update(
58
+ String(id),
59
+ Number(meta?.expectedRevision),
60
+ variables as Record<string, unknown>
61
+ ),
62
+ }),
63
+ deleteOne: async ({ resource, id, meta }: DeleteParams) => ({
64
+ data: await clientFor(resource).remove(
65
+ String(id),
66
+ Number(meta?.expectedRevision)
67
+ ),
68
+ }),
69
+ } as unknown as DataProvider;
24
70
  }
25
71
 
26
- export const applicationProvider = {
27
- getApiUrl: () => `/service/openxiangda-api/v2/applications/${applicationCode()}/native/data`,
28
- getList: async ({ resource, pagination, sorters, meta }: ListParams) => {
29
- const query = (meta?.query || {}) as Partial<GenericResourceQuery>;
30
- const sorter = sorters?.[0];
31
- const result = await clientFor(resource).list({
32
- page: pagination?.currentPage || 1,
33
- pageSize: pagination?.pageSize || 20,
34
- ...query,
35
- ...(sorter ? { sort: { field: String(sorter.field), order: (sorter.order || 'asc') as 'asc' | 'desc' } } : {}),
36
- });
37
- return { data: result.rows, total: result.total };
38
- },
39
- getOne: async ({ resource, id }: OneParams) => ({ data: await clientFor(resource).get(String(id)) }),
40
- create: async ({ resource, variables }: CreateParams) => ({ data: await clientFor(resource).create(variables as Record<string, unknown>) }),
41
- update: async ({ resource, id, variables, meta }: UpdateParams) => ({
42
- data: await clientFor(resource).update(String(id), Number(meta?.expectedRevision), variables as Record<string, unknown>),
43
- }),
44
- deleteOne: async ({ resource, id, meta }: DeleteParams) => ({
45
- data: await clientFor(resource).remove(String(id), Number(meta?.expectedRevision)),
46
- }),
47
- } as unknown as DataProvider;
72
+ export const applicationProvider = createApplicationProvider(definitions);