openxiangda-cli 2.0.0-alpha.16 → 2.0.0-alpha.162

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 (276) hide show
  1. package/README.md +51 -15
  2. package/bin/run.js +79 -1
  3. package/dist/base.d.ts +59 -5
  4. package/dist/base.d.ts.map +1 -1
  5. package/dist/base.js +213 -10
  6. package/dist/base.js.map +1 -1
  7. package/dist/commands/accept.d.ts +28 -0
  8. package/dist/commands/accept.d.ts.map +1 -0
  9. package/dist/commands/accept.js +21 -0
  10. package/dist/commands/accept.js.map +1 -0
  11. package/dist/commands/cancel.d.ts +19 -1
  12. package/dist/commands/cancel.d.ts.map +1 -1
  13. package/dist/commands/cancel.js +1 -1
  14. package/dist/commands/cancel.js.map +1 -1
  15. package/dist/commands/check.d.ts +22 -1
  16. package/dist/commands/check.d.ts.map +1 -1
  17. package/dist/commands/check.js +17 -4
  18. package/dist/commands/check.js.map +1 -1
  19. package/dist/commands/create.d.ts +39 -0
  20. package/dist/commands/create.d.ts.map +1 -0
  21. package/dist/commands/create.js +457 -0
  22. package/dist/commands/create.js.map +1 -0
  23. package/dist/commands/deploy.d.ts +26 -8
  24. package/dist/commands/deploy.d.ts.map +1 -1
  25. package/dist/commands/deploy.js +31 -17
  26. package/dist/commands/deploy.js.map +1 -1
  27. package/dist/commands/dev.d.ts +23 -1
  28. package/dist/commands/dev.d.ts.map +1 -1
  29. package/dist/commands/dev.js +28 -4
  30. package/dist/commands/dev.js.map +1 -1
  31. package/dist/commands/login.d.ts +29 -0
  32. package/dist/commands/login.d.ts.map +1 -0
  33. package/dist/commands/{auth/login.js → login.js} +27 -9
  34. package/dist/commands/login.js.map +1 -0
  35. package/dist/commands/logs.d.ts +22 -2
  36. package/dist/commands/logs.d.ts.map +1 -1
  37. package/dist/commands/logs.js +9 -5
  38. package/dist/commands/logs.js.map +1 -1
  39. package/dist/commands/retry.d.ts +19 -1
  40. package/dist/commands/retry.d.ts.map +1 -1
  41. package/dist/commands/retry.js +5 -2
  42. package/dist/commands/retry.js.map +1 -1
  43. package/dist/commands/rollback.d.ts +23 -4
  44. package/dist/commands/rollback.d.ts.map +1 -1
  45. package/dist/commands/rollback.js +18 -5
  46. package/dist/commands/rollback.js.map +1 -1
  47. package/dist/commands/skill.d.ts +31 -0
  48. package/dist/commands/skill.d.ts.map +1 -0
  49. package/dist/commands/skill.js +43 -0
  50. package/dist/commands/skill.js.map +1 -0
  51. package/dist/commands/spec.d.ts +39 -0
  52. package/dist/commands/spec.d.ts.map +1 -0
  53. package/dist/commands/spec.js +117 -0
  54. package/dist/commands/spec.js.map +1 -0
  55. package/dist/commands/start.d.ts +28 -0
  56. package/dist/commands/start.d.ts.map +1 -0
  57. package/dist/commands/start.js +18 -0
  58. package/dist/commands/start.js.map +1 -0
  59. package/dist/commands/status.d.ts +19 -1
  60. package/dist/commands/status.d.ts.map +1 -1
  61. package/dist/commands/status.js +1 -1
  62. package/dist/commands/status.js.map +1 -1
  63. package/dist/commands/stop.d.ts +28 -0
  64. package/dist/commands/stop.d.ts.map +1 -0
  65. package/dist/commands/stop.js +18 -0
  66. package/dist/commands/stop.js.map +1 -0
  67. package/dist/create-workspace.d.ts +67 -0
  68. package/dist/create-workspace.d.ts.map +1 -0
  69. package/dist/create-workspace.js +631 -0
  70. package/dist/create-workspace.js.map +1 -0
  71. package/dist/studio-events.d.ts +9 -0
  72. package/dist/studio-events.d.ts.map +1 -0
  73. package/dist/studio-events.js +30 -0
  74. package/dist/studio-events.js.map +1 -0
  75. package/dist/toolchain-capsule.d.ts +4 -0
  76. package/dist/toolchain-capsule.d.ts.map +1 -0
  77. package/dist/toolchain-capsule.js +116 -0
  78. package/dist/toolchain-capsule.js.map +1 -0
  79. package/package.json +16 -9
  80. package/template/.dockerignore +23 -0
  81. package/template/.env.example +3 -0
  82. package/template/AGENTS.md +47 -0
  83. package/template/README.md +65 -0
  84. package/template/_gitignore +14 -0
  85. package/template/apps/server/Dockerfile +20 -0
  86. package/template/apps/server/package.json +27 -0
  87. package/template/apps/server/src/app.module.ts +23 -0
  88. package/template/apps/server/src/main.ts +5 -0
  89. package/template/apps/server/test/smoke.test.ts +17 -0
  90. package/template/apps/server/tsconfig.json +9 -0
  91. package/template/apps/web/data-api-live.e2e.html +15 -0
  92. package/template/apps/web/e2e/client-fixture.ts +5 -0
  93. package/template/apps/web/e2e/data-api-live-fixture.tsx +162 -0
  94. package/template/apps/web/e2e/data-api-live.spec.ts +144 -0
  95. package/template/apps/web/e2e/field-protocol-fixture.tsx +350 -0
  96. package/template/apps/web/e2e/field-protocol.spec.ts +195 -0
  97. package/template/apps/web/e2e/resource-experience-fixture.tsx +446 -0
  98. package/template/apps/web/e2e/resources.spec.ts +638 -0
  99. package/template/apps/web/e2e/theme-contract-fixture.css +15 -0
  100. package/template/apps/web/e2e/workflow-experience-fixture.tsx +247 -0
  101. package/template/apps/web/e2e/workflow.spec.ts +1066 -0
  102. package/template/apps/web/field-protocol.e2e.html +15 -0
  103. package/template/apps/web/index.html +12 -0
  104. package/template/apps/web/package.json +34 -0
  105. package/template/apps/web/playwright.config.ts +47 -0
  106. package/template/apps/web/resource-experience.e2e.html +15 -0
  107. package/template/apps/web/scripts/check.mjs +43 -0
  108. package/template/apps/web/scripts/verify-build.mjs +6 -0
  109. package/template/apps/web/src/main.tsx +65 -0
  110. package/template/apps/web/src/user-end/tailwind.css +17 -0
  111. package/template/apps/web/test/contracts.test.ts +161 -0
  112. package/template/apps/web/test/loopback.test.ts +68 -0
  113. package/template/apps/web/tsconfig.json +12 -0
  114. package/template/apps/web/vite.config.ts +41 -0
  115. package/template/apps/web/workflow-experience.e2e.html +15 -0
  116. package/template/appspec/app.md +36 -0
  117. package/template/openxiangda.config.ts +77 -0
  118. package/template/package.json +29 -0
  119. package/template/packages/contracts/package.json +23 -0
  120. package/template/packages/contracts/src/generated.ts +265 -0
  121. package/template/packages/contracts/src/index.ts +1 -0
  122. package/template/packages/contracts/tsconfig.json +9 -0
  123. package/template/pnpm-workspace.yaml +7 -0
  124. package/template/scripts/verify-template-budget.mjs +49 -0
  125. package/template/tsconfig.base.json +13 -0
  126. package/dist/commands/app/create.d.ts +0 -15
  127. package/dist/commands/app/create.d.ts.map +0 -1
  128. package/dist/commands/app/create.js +0 -42
  129. package/dist/commands/app/create.js.map +0 -1
  130. package/dist/commands/app/info.d.ts +0 -9
  131. package/dist/commands/app/info.d.ts.map +0 -1
  132. package/dist/commands/app/info.js +0 -10
  133. package/dist/commands/app/info.js.map +0 -1
  134. package/dist/commands/app/link.d.ts +0 -10
  135. package/dist/commands/app/link.d.ts.map +0 -1
  136. package/dist/commands/app/link.js +0 -16
  137. package/dist/commands/app/link.js.map +0 -1
  138. package/dist/commands/app/provision.d.ts +0 -9
  139. package/dist/commands/app/provision.d.ts.map +0 -1
  140. package/dist/commands/app/provision.js +0 -10
  141. package/dist/commands/app/provision.js.map +0 -1
  142. package/dist/commands/auth/login.d.ts +0 -11
  143. package/dist/commands/auth/login.d.ts.map +0 -1
  144. package/dist/commands/auth/login.js.map +0 -1
  145. package/dist/commands/auth/logout.d.ts +0 -6
  146. package/dist/commands/auth/logout.d.ts.map +0 -1
  147. package/dist/commands/auth/logout.js +0 -30
  148. package/dist/commands/auth/logout.js.map +0 -1
  149. package/dist/commands/auth/status.d.ts +0 -6
  150. package/dist/commands/auth/status.d.ts.map +0 -1
  151. package/dist/commands/auth/status.js +0 -60
  152. package/dist/commands/auth/status.js.map +0 -1
  153. package/dist/commands/build.d.ts +0 -11
  154. package/dist/commands/build.d.ts.map +0 -1
  155. package/dist/commands/build.js +0 -23
  156. package/dist/commands/build.js.map +0 -1
  157. package/dist/commands/doctor.d.ts +0 -9
  158. package/dist/commands/doctor.d.ts.map +0 -1
  159. package/dist/commands/doctor.js +0 -7
  160. package/dist/commands/doctor.js.map +0 -1
  161. package/dist/commands/event/delivery/list.d.ts +0 -10
  162. package/dist/commands/event/delivery/list.d.ts.map +0 -1
  163. package/dist/commands/event/delivery/list.js +0 -14
  164. package/dist/commands/event/delivery/list.js.map +0 -1
  165. package/dist/commands/event/delivery/replay.d.ts +0 -13
  166. package/dist/commands/event/delivery/replay.d.ts.map +0 -1
  167. package/dist/commands/event/delivery/replay.js +0 -16
  168. package/dist/commands/event/delivery/replay.js.map +0 -1
  169. package/dist/commands/event/subscription/list.d.ts +0 -9
  170. package/dist/commands/event/subscription/list.d.ts.map +0 -1
  171. package/dist/commands/event/subscription/list.js +0 -10
  172. package/dist/commands/event/subscription/list.js.map +0 -1
  173. package/dist/commands/event/subscription/rotate-secret.d.ts +0 -15
  174. package/dist/commands/event/subscription/rotate-secret.d.ts.map +0 -1
  175. package/dist/commands/event/subscription/rotate-secret.js +0 -33
  176. package/dist/commands/event/subscription/rotate-secret.js.map +0 -1
  177. package/dist/commands/event/subscription/status.d.ts +0 -14
  178. package/dist/commands/event/subscription/status.d.ts.map +0 -1
  179. package/dist/commands/event/subscription/status.js +0 -19
  180. package/dist/commands/event/subscription/status.js.map +0 -1
  181. package/dist/commands/event/timer/list.d.ts +0 -9
  182. package/dist/commands/event/timer/list.d.ts.map +0 -1
  183. package/dist/commands/event/timer/list.js +0 -10
  184. package/dist/commands/event/timer/list.js.map +0 -1
  185. package/dist/commands/event/timer/status.d.ts +0 -14
  186. package/dist/commands/event/timer/status.d.ts.map +0 -1
  187. package/dist/commands/event/timer/status.js +0 -19
  188. package/dist/commands/event/timer/status.js.map +0 -1
  189. package/dist/commands/generate.d.ts +0 -10
  190. package/dist/commands/generate.d.ts.map +0 -1
  191. package/dist/commands/generate.js +0 -8
  192. package/dist/commands/generate.js.map +0 -1
  193. package/dist/commands/oauth/audit/list.d.ts +0 -12
  194. package/dist/commands/oauth/audit/list.d.ts.map +0 -1
  195. package/dist/commands/oauth/audit/list.js +0 -20
  196. package/dist/commands/oauth/audit/list.js.map +0 -1
  197. package/dist/commands/oauth/client/create.d.ts +0 -13
  198. package/dist/commands/oauth/client/create.d.ts.map +0 -1
  199. package/dist/commands/oauth/client/create.js +0 -34
  200. package/dist/commands/oauth/client/create.js.map +0 -1
  201. package/dist/commands/oauth/client/list.d.ts +0 -9
  202. package/dist/commands/oauth/client/list.d.ts.map +0 -1
  203. package/dist/commands/oauth/client/list.js +0 -10
  204. package/dist/commands/oauth/client/list.js.map +0 -1
  205. package/dist/commands/oauth/client/revoke.d.ts +0 -12
  206. package/dist/commands/oauth/client/revoke.d.ts.map +0 -1
  207. package/dist/commands/oauth/client/revoke.js +0 -12
  208. package/dist/commands/oauth/client/revoke.js.map +0 -1
  209. package/dist/commands/oauth/client/rotate.d.ts +0 -13
  210. package/dist/commands/oauth/client/rotate.d.ts.map +0 -1
  211. package/dist/commands/oauth/client/rotate.js +0 -20
  212. package/dist/commands/oauth/client/rotate.js.map +0 -1
  213. package/dist/commands/oauth/client/update.d.ts +0 -15
  214. package/dist/commands/oauth/client/update.d.ts.map +0 -1
  215. package/dist/commands/oauth/client/update.js +0 -23
  216. package/dist/commands/oauth/client/update.js.map +0 -1
  217. package/dist/commands/oauth/runtime/rotate.d.ts +0 -12
  218. package/dist/commands/oauth/runtime/rotate.d.ts.map +0 -1
  219. package/dist/commands/oauth/runtime/rotate.js +0 -31
  220. package/dist/commands/oauth/runtime/rotate.js.map +0 -1
  221. package/dist/commands/oauth/runtime/status.d.ts +0 -10
  222. package/dist/commands/oauth/runtime/status.d.ts.map +0 -1
  223. package/dist/commands/oauth/runtime/status.js +0 -17
  224. package/dist/commands/oauth/runtime/status.js.map +0 -1
  225. package/dist/commands/promote.d.ts +0 -13
  226. package/dist/commands/promote.d.ts.map +0 -1
  227. package/dist/commands/promote.js +0 -9
  228. package/dist/commands/promote.js.map +0 -1
  229. package/dist/commands/secret/audit/list.d.ts +0 -14
  230. package/dist/commands/secret/audit/list.d.ts.map +0 -1
  231. package/dist/commands/secret/audit/list.js +0 -16
  232. package/dist/commands/secret/audit/list.js.map +0 -1
  233. package/dist/commands/secret/create.d.ts +0 -18
  234. package/dist/commands/secret/create.d.ts.map +0 -1
  235. package/dist/commands/secret/create.js +0 -33
  236. package/dist/commands/secret/create.js.map +0 -1
  237. package/dist/commands/secret/delete.d.ts +0 -15
  238. package/dist/commands/secret/delete.d.ts.map +0 -1
  239. package/dist/commands/secret/delete.js +0 -20
  240. package/dist/commands/secret/delete.js.map +0 -1
  241. package/dist/commands/secret/list.d.ts +0 -10
  242. package/dist/commands/secret/list.d.ts.map +0 -1
  243. package/dist/commands/secret/list.js +0 -14
  244. package/dist/commands/secret/list.js.map +0 -1
  245. package/dist/commands/secret/rotate.d.ts +0 -18
  246. package/dist/commands/secret/rotate.d.ts.map +0 -1
  247. package/dist/commands/secret/rotate.js +0 -30
  248. package/dist/commands/secret/rotate.js.map +0 -1
  249. package/dist/commands/secret/update.d.ts +0 -17
  250. package/dist/commands/secret/update.d.ts.map +0 -1
  251. package/dist/commands/secret/update.js +0 -37
  252. package/dist/commands/secret/update.js.map +0 -1
  253. package/dist/commands/skill/install.d.ts +0 -11
  254. package/dist/commands/skill/install.d.ts.map +0 -1
  255. package/dist/commands/skill/install.js +0 -52
  256. package/dist/commands/skill/install.js.map +0 -1
  257. package/dist/commands/skill/validate.d.ts +0 -9
  258. package/dist/commands/skill/validate.d.ts.map +0 -1
  259. package/dist/commands/skill/validate.js +0 -32
  260. package/dist/commands/skill/validate.js.map +0 -1
  261. package/dist/commands/test.d.ts +0 -9
  262. package/dist/commands/test.d.ts.map +0 -1
  263. package/dist/commands/test.js +0 -7
  264. package/dist/commands/test.js.map +0 -1
  265. package/dist/commands/workflow/provider/list.d.ts +0 -10
  266. package/dist/commands/workflow/provider/list.d.ts.map +0 -1
  267. package/dist/commands/workflow/provider/list.js +0 -14
  268. package/dist/commands/workflow/provider/list.js.map +0 -1
  269. package/dist/commands/workflow/provider/rotate.d.ts +0 -15
  270. package/dist/commands/workflow/provider/rotate.d.ts.map +0 -1
  271. package/dist/commands/workflow/provider/rotate.js +0 -24
  272. package/dist/commands/workflow/provider/rotate.js.map +0 -1
  273. package/dist/secret-value.d.ts +0 -5
  274. package/dist/secret-value.d.ts.map +0 -1
  275. package/dist/secret-value.js +0 -15
  276. package/dist/secret-value.js.map +0 -1
@@ -0,0 +1,162 @@
1
+ import { Refine } from '@refinedev/core';
2
+ import { App as AntdApp } from 'antd';
3
+ import React from 'react';
4
+ import ReactDOM from 'react-dom/client';
5
+ import { MemoryRouter, Route, Routes } from 'react-router-dom';
6
+ import {
7
+ AppearanceProvider,
8
+ createApplicationProvider,
9
+ GeneratedResourcePage,
10
+ OpenXiangdaResourceDefinitionsProvider,
11
+ RuntimeBoundary,
12
+ type GeneratedResourceDefinition,
13
+ } from 'openxiangda/react';
14
+ import 'openxiangda/react/styles.css';
15
+
16
+ const code = 'field-records';
17
+ const capabilityBase = `app:native-projection-app:data:${code}`;
18
+ const capabilities = {
19
+ read: `${capabilityBase}:read`,
20
+ create: `${capabilityBase}:create`,
21
+ update: `${capabilityBase}:update`,
22
+ delete: `${capabilityBase}:delete`,
23
+ };
24
+
25
+ function fieldCapabilities() {
26
+ return {
27
+ readCapabilities: [capabilities.read],
28
+ createCapabilities: [capabilities.create],
29
+ updateCapabilities: [capabilities.update],
30
+ };
31
+ }
32
+
33
+ export const liveDefinition: GeneratedResourceDefinition = {
34
+ code,
35
+ name: '字段协议记录',
36
+ capabilities,
37
+ surface: {
38
+ mutationOwner: 'native',
39
+ generated: {
40
+ list: true,
41
+ detail: true,
42
+ create: true,
43
+ update: true,
44
+ delete: true,
45
+ },
46
+ fields: {
47
+ text_short: {
48
+ label: '标题',
49
+ type: 'text.short',
50
+ widget: 'text',
51
+ requiredHint: true,
52
+ maxLength: 120,
53
+ list: true,
54
+ searchable: true,
55
+ sortable: true,
56
+ ...fieldCapabilities(),
57
+ },
58
+ option_single: {
59
+ label: '单选快照',
60
+ type: 'option.single',
61
+ widget: 'radio',
62
+ options: [
63
+ { label: 'Open', value: 'open' },
64
+ { label: 'Closed', value: 'closed' },
65
+ ],
66
+ list: true,
67
+ ...fieldCapabilities(),
68
+ },
69
+ option_multiple: {
70
+ label: '多选快照',
71
+ type: 'option.multiple',
72
+ widget: 'checkbox',
73
+ options: [
74
+ { label: 'Open', value: 'open' },
75
+ { label: 'Urgent', value: 'urgent' },
76
+ ],
77
+ list: true,
78
+ ...fieldCapabilities(),
79
+ },
80
+ location_value: {
81
+ label: '精确定位',
82
+ type: 'location',
83
+ widget: 'location',
84
+ list: true,
85
+ ...fieldCapabilities(),
86
+ },
87
+ },
88
+ list: {
89
+ defaultPageSize: 20,
90
+ searchableFields: ['text_short'],
91
+ filterFields: ['option_single'],
92
+ defaultSort: { field: 'text_short', order: 'asc' },
93
+ },
94
+ form: { layout: 'sections' },
95
+ detail: { layout: 'sections' },
96
+ mobile: { enabled: true },
97
+ },
98
+ };
99
+
100
+ const definitions = { [code]: liveDefinition };
101
+ const provider = createApplicationProvider(definitions);
102
+ const routeBase = `/admin/resources/${code}`;
103
+ const routePaths = {
104
+ fallback: '/admin',
105
+ list: routeBase,
106
+ create: `${routeBase}/new`,
107
+ detail: `${routeBase}/:id`,
108
+ edit: `${routeBase}/:id/edit`,
109
+ };
110
+ const initialPath =
111
+ new URLSearchParams(window.location.search).get('initial') || routeBase;
112
+
113
+ function resourcePage(mode: 'list' | 'create' | 'edit' | 'detail') {
114
+ return (
115
+ <GeneratedResourcePage
116
+ definition={liveDefinition}
117
+ mode={mode}
118
+ paths={routePaths}
119
+ resourceCode={code}
120
+ />
121
+ );
122
+ }
123
+
124
+ ReactDOM.createRoot(document.getElementById('root')!).render(
125
+ <React.StrictMode>
126
+ <OpenXiangdaResourceDefinitionsProvider definitions={definitions}>
127
+ <AppearanceProvider>
128
+ <AntdApp>
129
+ <RuntimeBoundary>
130
+ <MemoryRouter initialEntries={[initialPath]}>
131
+ <Refine
132
+ dataProvider={provider}
133
+ resources={[
134
+ {
135
+ name: code,
136
+ list: routePaths.list,
137
+ create: routePaths.create,
138
+ edit: routePaths.edit,
139
+ show: routePaths.detail,
140
+ },
141
+ ]}
142
+ >
143
+ <Routes>
144
+ <Route path={routePaths.list} element={resourcePage('list')} />
145
+ <Route path={routePaths.create} element={resourcePage('create')} />
146
+ <Route
147
+ path={routePaths.detail}
148
+ element={resourcePage('detail')}
149
+ />
150
+ <Route
151
+ path={routePaths.edit}
152
+ element={resourcePage('edit')}
153
+ />
154
+ </Routes>
155
+ </Refine>
156
+ </MemoryRouter>
157
+ </RuntimeBoundary>
158
+ </AntdApp>
159
+ </AppearanceProvider>
160
+ </OpenXiangdaResourceDefinitionsProvider>
161
+ </React.StrictMode>
162
+ );
@@ -0,0 +1,144 @@
1
+ import { expect, test } from '@playwright/test';
2
+
3
+ const liveEnabled = process.env.OPENXIANGDA_LIVE_DATA_API === '1';
4
+ const title = 'UI live field record';
5
+
6
+ test.describe('@live-data-api standard resource UI', () => {
7
+ test.setTimeout(90_000);
8
+ test.skip(
9
+ !liveEnabled,
10
+ 'Requires the disposable PostgreSQL Native Data API acceptance bridge.'
11
+ );
12
+
13
+ test.beforeEach(async ({ context }) => {
14
+ await context.addCookies([
15
+ {
16
+ name: 'openxiangda-field-ui-role',
17
+ value: 'super',
18
+ domain: '127.0.0.1',
19
+ path: '/',
20
+ },
21
+ ]);
22
+ });
23
+
24
+ test('runs create, list, query, get, update, audit and delete through the standard page', async ({
25
+ context,
26
+ page,
27
+ }) => {
28
+ await context.grantPermissions(['geolocation']);
29
+ await context.setGeolocation({
30
+ longitude: 120.123456,
31
+ latitude: 30.234567,
32
+ accuracy: 4,
33
+ });
34
+ await page.goto(
35
+ '/data-api-live.e2e.html?initial=/admin/resources/field-records',
36
+ );
37
+ await expect(page.locator('.oxa-list-surface')).toBeVisible();
38
+
39
+ await page.getByRole('button', { name: '新增字段协议记录' }).click();
40
+ await expect(page.getByRole('heading', { name: '新增字段协议记录' })).toBeVisible();
41
+ await page.getByLabel('标题').fill(title);
42
+ await page.getByRole('radio', { name: 'Open' }).check();
43
+ const openCheckbox = page.getByRole('checkbox', { name: 'Open' });
44
+ const urgentCheckbox = page.getByRole('checkbox', { name: 'Urgent' });
45
+ await openCheckbox.click();
46
+ await urgentCheckbox.click();
47
+ await expect(openCheckbox).toBeChecked();
48
+ await expect(urgentCheckbox).toBeChecked();
49
+ await page.getByRole('button', { name: '浏览器定位' }).click();
50
+ await expect(page.getByText('120.123456, 30.234567')).toBeVisible();
51
+ await page.getByRole('button', { name: /^保\s*存$/ }).click();
52
+
53
+ await expect(page.locator('.oxa-list-surface')).toBeVisible();
54
+ await page.locator('.oxa-search-panel .ant-select').first().click();
55
+ await page.getByText('标题', { exact: true }).last().click();
56
+ await page.getByPlaceholder('关键词').fill(title);
57
+ await page.getByRole('button', { name: /查询/ }).click();
58
+ const createdRow = page.getByRole('row').filter({ hasText: title });
59
+ await expect(createdRow).toHaveCount(1);
60
+ await expect(createdRow).toContainText('Open');
61
+ await expect(createdRow).toContainText('Urgent');
62
+ await expect(createdRow).toContainText('120.123456, 30.234567');
63
+
64
+ await createdRow.getByRole('button', { name: '查看' }).click();
65
+ await expect(page.getByRole('heading', { name: '字段协议记录详情' })).toBeVisible();
66
+ await expect(page.getByText(title, { exact: true })).toBeVisible();
67
+ await expect(page.getByText(/浏览器定位/)).toBeVisible();
68
+ await page.locator('.oxa-audit-collapse .ant-collapse-header').click();
69
+ await expect(page.getByText('创建', { exact: true })).toBeVisible();
70
+ await page.screenshot({
71
+ fullPage: true,
72
+ path: 'test-results/data-api-live-detail.png',
73
+ });
74
+
75
+ await page.getByRole('button', { name: /编辑/ }).click();
76
+ await page.getByLabel('标题').fill(`${title} updated`);
77
+ await page.getByRole('radio', { name: 'Closed' }).check();
78
+ await page.getByRole('checkbox', { name: 'Open' }).click();
79
+ await expect(page.getByRole('checkbox', { name: 'Open' })).not.toBeChecked();
80
+ await page.getByRole('button', { name: /^保存修改$/ }).click();
81
+ await expect(page.getByText(`${title} updated`, { exact: true })).toBeVisible();
82
+ await expect(page.getByText('Closed', { exact: true })).toBeVisible();
83
+
84
+ await page.getByRole('button', { name: '返回列表' }).click();
85
+ const updatedRow = page
86
+ .getByRole('row')
87
+ .filter({ hasText: `${title} updated` });
88
+ await expect(updatedRow).toHaveCount(1);
89
+ await updatedRow.getByRole('button', { name: '删除' }).click();
90
+ await page.getByRole('button', { name: /^确\s*定$/ }).click();
91
+ await expect(updatedRow).toHaveCount(0);
92
+ });
93
+
94
+ test('applies PostgreSQL row scope to the standard list and query UI', async ({
95
+ context,
96
+ page,
97
+ }) => {
98
+ await context.clearCookies();
99
+ await context.addCookies([
100
+ {
101
+ name: 'openxiangda-field-ui-role',
102
+ value: 'restricted',
103
+ domain: '127.0.0.1',
104
+ path: '/',
105
+ },
106
+ ]);
107
+ await page.goto(
108
+ '/data-api-live.e2e.html?initial=/admin/resources/field-records',
109
+ );
110
+ await expect(page.locator('.oxa-list-surface')).toBeVisible();
111
+ await page.locator('.oxa-search-panel .ant-select').first().click();
112
+ await page.getByText('标题', { exact: true }).last().click();
113
+ await page.getByPlaceholder('关键词').fill('UI RLS');
114
+ await page.getByRole('button', { name: /查询/ }).click();
115
+ await expect(page.getByText('UI RLS visible', { exact: true })).toBeVisible();
116
+ await expect(page.getByText('UI RLS hidden', { exact: true })).toHaveCount(0);
117
+ await page.screenshot({
118
+ fullPage: true,
119
+ path: 'test-results/data-api-live-rls.png',
120
+ });
121
+ });
122
+
123
+ test('fails closed in the standard page when the role has no read capability', async ({
124
+ context,
125
+ page,
126
+ }) => {
127
+ await context.clearCookies();
128
+ await context.addCookies([
129
+ {
130
+ name: 'openxiangda-field-ui-role',
131
+ value: 'denied',
132
+ domain: '127.0.0.1',
133
+ path: '/',
134
+ },
135
+ ]);
136
+ await page.goto(
137
+ '/data-api-live.e2e.html?initial=/admin/resources/field-records',
138
+ );
139
+ await expect(
140
+ page.getByText('当前平台用户无字段协议记录页面权限', { exact: true })
141
+ ).toBeVisible();
142
+ await expect(page.locator('.oxa-list-surface')).toHaveCount(0);
143
+ });
144
+ });
@@ -0,0 +1,350 @@
1
+ import { Button, ConfigProvider, Form, Typography } from 'antd';
2
+ import type {
3
+ DataAuditEntry,
4
+ DataFieldSurface,
5
+ DataFileRef,
6
+ } from 'openxiangda/core';
7
+ import React from 'react';
8
+ import ReactDOM from 'react-dom/client';
9
+ import {
10
+ auditFieldChange,
11
+ MobileSurfaceFieldControl,
12
+ SurfaceAuditFieldValue,
13
+ SurfaceFieldControl,
14
+ SurfaceFieldValue,
15
+ type SurfaceField,
16
+ type SurfaceFieldRenderers,
17
+ } from 'openxiangda/field-kit';
18
+ import 'openxiangda/react/styles.css';
19
+
20
+ const options = [
21
+ { label: '启用', value: 'enabled' },
22
+ { label: '停用', value: 'disabled' },
23
+ ];
24
+ const source = {
25
+ kind: 'resource' as const,
26
+ resourceCode: 'field-lookups',
27
+ labelField: 'name',
28
+ searchFields: ['name'],
29
+ snapshotFields: ['code'],
30
+ };
31
+ const field = (
32
+ key: string,
33
+ type: string,
34
+ widget: string,
35
+ extra: Record<string, unknown> = {}
36
+ ) =>
37
+ ({
38
+ key,
39
+ label: key,
40
+ type,
41
+ widget,
42
+ readCapabilities: [],
43
+ createCapabilities: [],
44
+ updateCapabilities: [],
45
+ ...(type === 'date-range' || type === 'datetime-range'
46
+ ? { rangeBoundary: 'closed' as const }
47
+ : {}),
48
+ ...extra,
49
+ } as unknown as SurfaceField);
50
+
51
+ const fields = [
52
+ field('单行文本', 'text.short', 'text', { maxLength: 120 }),
53
+ field('邮箱', 'text.short', 'email'),
54
+ field('电话', 'text.short', 'phone'),
55
+ field('多行文本', 'text.long', 'textarea'),
56
+ field('富文本', 'text.rich', 'rich-text'),
57
+ field('整数', 'number.integer', 'number', { scale: 0 }),
58
+ field('小数', 'number.decimal', 'number', { scale: 2 }),
59
+ field('金额', 'number.decimal', 'money', { scale: 2 }),
60
+ field('百分比', 'number.decimal', 'percent', { scale: 2 }),
61
+ field('布尔', 'boolean', 'switch'),
62
+ field('日期', 'date', 'date'),
63
+ field('时间', 'time', 'time', { timePrecision: 'second' }),
64
+ field('日期时间', 'datetime', 'datetime'),
65
+ field('日期范围', 'date-range', 'date-range'),
66
+ field('日期时间范围', 'datetime-range', 'datetime-range'),
67
+ field('静态单选下拉', 'option.single', 'select', { options }),
68
+ field('静态多选下拉', 'option.multiple', 'multi-select', { options }),
69
+ field('单选按钮', 'option.single', 'radio', { options }),
70
+ field('复选框', 'option.multiple', 'checkbox', { options }),
71
+ field('动态单选下拉', 'resource-ref.single', 'select', { source }),
72
+ field('动态多选下拉', 'resource-ref.multiple', 'multi-select', { source }),
73
+ field('级联单选', 'cascade.single', 'cascade', {
74
+ options: [
75
+ {
76
+ label: '设备',
77
+ value: 'equipment',
78
+ children: [{ label: '显微镜', value: 'microscope' }],
79
+ },
80
+ ],
81
+ }),
82
+ field('级联多选', 'cascade.multiple', 'cascade', {
83
+ options: [
84
+ {
85
+ label: '设备',
86
+ value: 'equipment',
87
+ children: [{ label: '显微镜', value: 'microscope' }],
88
+ },
89
+ ],
90
+ }),
91
+ field('成员单选', 'user.single', 'directory-user'),
92
+ field('成员多选', 'user.multiple', 'directory-user'),
93
+ field('部门单选', 'department.single', 'directory-department'),
94
+ field('部门多选', 'department.multiple', 'directory-department'),
95
+ field('资源单选', 'resource-ref.single', 'resource', { source }),
96
+ field('资源多选', 'resource-ref.multiple', 'resource', { source }),
97
+ field('附件', 'file', 'attachment', {
98
+ accept: ['.pdf', 'text/plain'],
99
+ maxCount: 3,
100
+ maxSizeMb: 20,
101
+ }),
102
+ field('图片', 'image', 'image', {
103
+ accept: ['image/*'],
104
+ maxCount: 3,
105
+ maxSizeMb: 20,
106
+ }),
107
+ field('业务签名', 'signature', 'signature'),
108
+ field('地址', 'address', 'address'),
109
+ field('精确定位', 'location', 'location'),
110
+ field('UUID', 'uuid', 'readonly', { requiredHint: true }),
111
+ field('JSON', 'json', 'json'),
112
+ field('流水号', 'serial-number', 'readonly'),
113
+ field('子表', 'subtable', 'subtable'),
114
+ ] satisfies Array<DataFieldSurface & { key: string }>;
115
+
116
+ const user = { label: '张三', value: 'user-1', employeeNo: 'E1001' };
117
+ const department = {
118
+ label: '理学院',
119
+ value: 'college-a',
120
+ fullPath: '学校 / 理学院',
121
+ };
122
+ const reference = {
123
+ label: '显微镜',
124
+ value: 'resource-1',
125
+ resourceCode: 'field-lookups',
126
+ snapshot: { code: 'M-001' },
127
+ };
128
+ const fileRef: DataFileRef = {
129
+ schemaVersion: 'openxiangda.data-file-ref/v2',
130
+ id: '11111111-1111-4111-8111-111111111111',
131
+ name: '验收附件.pdf',
132
+ size: 1280,
133
+ contentType: 'application/pdf',
134
+ };
135
+ const imageRef: DataFileRef = {
136
+ ...fileRef,
137
+ id: '22222222-2222-4222-8222-222222222222',
138
+ name: '验收图片.png',
139
+ contentType: 'image/png',
140
+ width: 640,
141
+ height: 480,
142
+ previewUrl: '/service/openxiangda-api/v2/applications/field-protocol-fixture/native/data/field-records/files/22222222-2222-4222-8222-222222222222/content?disposition=inline',
143
+ thumbnailUrl: '/service/openxiangda-api/v2/applications/field-protocol-fixture/native/data/field-records/files/22222222-2222-4222-8222-222222222222/content?variant=thumbnail&disposition=inline',
144
+ };
145
+ const values: Record<string, unknown> = {
146
+ 单行文本: '仪器验收记录',
147
+ 邮箱: 'user@example.com',
148
+ 电话: '13800000000',
149
+ 多行文本: '第一行\n第二行',
150
+ 富文本: `<p>已保存的<strong>富文本</strong></p><img src="${imageRef.previewUrl}" alt="富文本图片">`,
151
+ 整数: -7,
152
+ 小数: 12.34,
153
+ 金额: 5600.5,
154
+ 百分比: 98.5,
155
+ 布尔: true,
156
+ 日期: '2026-08-24',
157
+ 时间: '10:20:30',
158
+ 日期时间: '2026-08-24T02:20:30.000Z',
159
+ 日期范围: { start: '2026-08-01', end: '2026-08-31' },
160
+ 日期时间范围: {
161
+ start: '2026-08-01T00:00:00.000Z',
162
+ end: '2026-08-31T23:59:59.000Z',
163
+ },
164
+ 静态单选下拉: options[0],
165
+ 静态多选下拉: options,
166
+ 单选按钮: options[0],
167
+ 复选框: options,
168
+ 动态单选下拉: reference,
169
+ 动态多选下拉: [reference],
170
+ 级联单选: [
171
+ { label: '设备', value: 'equipment' },
172
+ { label: '显微镜', value: 'microscope' },
173
+ ],
174
+ 级联多选: [
175
+ [
176
+ { label: '设备', value: 'equipment' },
177
+ { label: '显微镜', value: 'microscope' },
178
+ ],
179
+ ],
180
+ 成员单选: user,
181
+ 成员多选: [user],
182
+ 部门单选: department,
183
+ 部门多选: [department],
184
+ 资源单选: reference,
185
+ 资源多选: [reference],
186
+ 附件: [fileRef],
187
+ 图片: [imageRef],
188
+ 业务签名: {
189
+ file: {
190
+ ...fileRef,
191
+ id: '33333333-3333-4333-8333-333333333333',
192
+ name: 'signature.png',
193
+ contentType: 'image/png',
194
+ },
195
+ signer: user,
196
+ signedAt: '2026-08-24T02:20:30.000Z',
197
+ hash: 'a'.repeat(64),
198
+ points: [{ x: 1, y: 1, t: 1 }],
199
+ },
200
+ 地址: {
201
+ country: { label: '中国', value: 'CN' },
202
+ province: { label: '浙江省', value: '330000' },
203
+ city: { label: '杭州市', value: '330100' },
204
+ district: { label: '西湖区', value: '330106' },
205
+ detail: '文一路 1 号',
206
+ fullAddress: '浙江省杭州市西湖区文一路 1 号',
207
+ },
208
+ 精确定位: {
209
+ source: 'browser',
210
+ longitude: 120.123456,
211
+ latitude: 30.234567,
212
+ accuracy: 5,
213
+ capturedAt: '2026-08-24T02:20:30.000Z',
214
+ },
215
+ JSON: { status: 'ok', nested: { count: 1 } },
216
+ 流水号: 'FP-001000',
217
+ 子表: [{ item_name: '第一行' }, { item_name: '第二行' }],
218
+ };
219
+
220
+ const auditEntry: DataAuditEntry = {
221
+ id: 'field-protocol-event',
222
+ operation: 'created',
223
+ recordId: 'field-protocol-record',
224
+ revision: 1,
225
+ actor: { principalType: 'user', subjectId: 'user-1' },
226
+ correlation: {
227
+ eventId: 'field-protocol-event',
228
+ requestId: null,
229
+ traceId: null,
230
+ environmentKey: 'preproduction',
231
+ appVersionId: 'field-protocol-version',
232
+ environmentHeadRevision: 1,
233
+ capturePlanRevision: 1,
234
+ cause: { eventId: null, subscriptionCode: null, depth: 0 },
235
+ },
236
+ changes: Object.fromEntries(
237
+ fields.map(item => [item.key, { after: values[item.key] ?? null }])
238
+ ),
239
+ projection: {},
240
+ occurredAt: '2026-08-25T00:00:00.000Z',
241
+ };
242
+
243
+ const renderers: SurfaceFieldRenderers = {
244
+ signer: user,
245
+ upload: async (_field, file) => ({
246
+ schemaVersion: 'openxiangda.data-file-ref/v2',
247
+ id: crypto.randomUUID(),
248
+ name: file.name,
249
+ size: file.size,
250
+ contentType: file.type,
251
+ }),
252
+ renderSubtable: () => (
253
+ <div className="field-protocol-subtable">2 行子表 · 新增 · 删除 · 排序</div>
254
+ ),
255
+ renderValue: ({ field: item, value }) =>
256
+ item.type === 'subtable'
257
+ ? `${Array.isArray(value) ? value.length : 0} 行子表`
258
+ : undefined,
259
+ };
260
+
261
+ function AcceptancePage() {
262
+ const mobile = new URLSearchParams(location.search).get('mode') === 'mobile';
263
+ const Control = mobile ? MobileSurfaceFieldControl : SurfaceFieldControl;
264
+ return (
265
+ <ConfigProvider theme={{ token: { borderRadius: 6 } }}>
266
+ <main className={`field-protocol-page ${mobile ? 'is-mobile' : 'is-desktop'}`}>
267
+ <header>
268
+ <Typography.Title level={mobile ? 4 : 2}>
269
+ Field Kit 协议验收
270
+ </Typography.Title>
271
+ <Typography.Text type="secondary">
272
+ {mobile ? '移动编辑态' : '桌面编辑态与已存快照只读态'}
273
+ </Typography.Text>
274
+ </header>
275
+ <section aria-label={mobile ? '移动编辑字段' : '桌面编辑字段'} className="field-protocol-edit">
276
+ <Form initialValues={values} layout="vertical" onFinish={() => undefined}>
277
+ <div className="field-protocol-grid">
278
+ {fields.map(item => (
279
+ <div data-field-code={item.key} key={item.key}>
280
+ <Control
281
+ disabled={item.widget === 'readonly'}
282
+ field={item}
283
+ operation="create"
284
+ renderers={renderers}
285
+ resourceCode="field-records"
286
+ />
287
+ </div>
288
+ ))}
289
+ </div>
290
+ <Button htmlType="submit">验证表单</Button>
291
+ </Form>
292
+ </section>
293
+ {!mobile && (
294
+ <section aria-label="已存快照只读字段" className="field-protocol-readonly">
295
+ <Typography.Title level={3}>已存快照</Typography.Title>
296
+ <div className="field-protocol-read-grid">
297
+ {fields.map(item => (
298
+ <div data-read-field={item.key} key={item.key}>
299
+ <strong>{item.label}</strong>
300
+ <SurfaceFieldValue
301
+ field={item}
302
+ renderers={renderers}
303
+ resourceCode="field-records"
304
+ value={values[item.key]}
305
+ />
306
+ </div>
307
+ ))}
308
+ </div>
309
+ </section>
310
+ )}
311
+ {!mobile && (
312
+ <section aria-label="当前协议字段变更记录" className="field-protocol-readonly">
313
+ <Typography.Title level={3}>当前协议字段变更记录</Typography.Title>
314
+ <div className="field-protocol-read-grid">
315
+ {fields.map(item => (
316
+ <div data-audit-field={item.key} key={item.key}>
317
+ <strong>{item.label}</strong>
318
+ <SurfaceAuditFieldValue
319
+ field={item}
320
+ resourceCode="field-records"
321
+ value={auditFieldChange(auditEntry, item.key)?.after}
322
+ />
323
+ </div>
324
+ ))}
325
+ <div data-audit-digest="rich-text">
326
+ <strong>富文本摘要</strong>
327
+ <SurfaceAuditFieldValue
328
+ field={fields[4]}
329
+ value={{
330
+ kind: 'digest',
331
+ truncated: true,
332
+ valueType: 'text.rich',
333
+ bytes: 65_537,
334
+ sha256: 'a'.repeat(64),
335
+ }}
336
+ />
337
+ </div>
338
+ </div>
339
+ </section>
340
+ )}
341
+ </main>
342
+ </ConfigProvider>
343
+ );
344
+ }
345
+
346
+ ReactDOM.createRoot(document.getElementById('root')!).render(
347
+ <React.StrictMode>
348
+ <AcceptancePage />
349
+ </React.StrictMode>
350
+ );