openxiangda-cli 2.0.0-alpha.49 → 2.0.0-alpha.56

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 (321) hide show
  1. package/README.md +4 -18
  2. package/bin/run.js +67 -1
  3. package/dist/base.d.ts +36 -7
  4. package/dist/base.d.ts.map +1 -1
  5. package/dist/base.js +95 -18
  6. package/dist/base.js.map +1 -1
  7. package/dist/commands/check.d.ts +14 -1
  8. package/dist/commands/check.d.ts.map +1 -1
  9. package/dist/commands/create.d.ts +23 -0
  10. package/dist/commands/create.d.ts.map +1 -0
  11. package/dist/commands/create.js +93 -0
  12. package/dist/commands/create.js.map +1 -0
  13. package/dist/commands/deploy.d.ts +19 -8
  14. package/dist/commands/deploy.d.ts.map +1 -1
  15. package/dist/commands/deploy.js +29 -16
  16. package/dist/commands/deploy.js.map +1 -1
  17. package/dist/commands/dev.d.ts +14 -3
  18. package/dist/commands/dev.d.ts.map +1 -1
  19. package/dist/commands/dev.js +4 -8
  20. package/dist/commands/dev.js.map +1 -1
  21. package/dist/commands/login.d.ts +24 -0
  22. package/dist/commands/login.d.ts.map +1 -0
  23. package/dist/commands/{auth/login.js → login.js} +27 -9
  24. package/dist/commands/login.js.map +1 -0
  25. package/dist/commands/logs.d.ts +15 -2
  26. package/dist/commands/logs.d.ts.map +1 -1
  27. package/dist/commands/logs.js +2 -2
  28. package/dist/commands/logs.js.map +1 -1
  29. package/dist/commands/rollback.d.ts +15 -4
  30. package/dist/commands/rollback.d.ts.map +1 -1
  31. package/dist/commands/rollback.js +11 -4
  32. package/dist/commands/rollback.js.map +1 -1
  33. package/dist/commands/status.d.ts +14 -1
  34. package/dist/commands/status.d.ts.map +1 -1
  35. package/dist/commands/status.js +1 -1
  36. package/dist/commands/status.js.map +1 -1
  37. package/dist/create-workspace.d.ts +23 -0
  38. package/dist/create-workspace.d.ts.map +1 -0
  39. package/dist/create-workspace.js +279 -0
  40. package/dist/create-workspace.js.map +1 -0
  41. package/package.json +14 -7
  42. package/template/.dockerignore +12 -0
  43. package/template/.env.example +3 -0
  44. package/template/AGENTS.md +8 -0
  45. package/template/README.md +23 -0
  46. package/template/_gitignore +14 -0
  47. package/template/apps/server/Dockerfile +19 -0
  48. package/template/apps/server/package.json +27 -0
  49. package/template/apps/server/src/app.module.ts +32 -0
  50. package/template/apps/server/src/instrument-context.controller.ts +25 -0
  51. package/template/apps/server/src/main.ts +18 -0
  52. package/template/apps/server/test/smoke.test.ts +28 -0
  53. package/template/apps/server/tsconfig.json +9 -0
  54. package/template/apps/web/e2e/instruments.spec.ts +150 -0
  55. package/template/apps/web/index.html +12 -0
  56. package/template/apps/web/package.json +33 -0
  57. package/template/apps/web/playwright.config.ts +21 -0
  58. package/template/apps/web/scripts/check.mjs +32 -0
  59. package/template/apps/web/scripts/verify-build.mjs +20 -0
  60. package/template/apps/web/src/InstrumentDetailPage.tsx +60 -0
  61. package/template/apps/web/src/InstrumentForm.tsx +322 -0
  62. package/template/apps/web/src/InstrumentFormPage.tsx +77 -0
  63. package/template/apps/web/src/InstrumentListPage.tsx +390 -0
  64. package/template/apps/web/src/Shell.tsx +52 -0
  65. package/template/apps/web/src/data-provider.ts +42 -0
  66. package/template/apps/web/src/fields.ts +72 -0
  67. package/template/apps/web/src/instrument.ts +55 -0
  68. package/template/apps/web/src/main.tsx +58 -0
  69. package/template/apps/web/src/platform-client.ts +246 -0
  70. package/template/apps/web/src/runtime-meta.ts +60 -0
  71. package/template/apps/web/src/runtime.tsx +73 -0
  72. package/template/apps/web/src/styles.css +84 -0
  73. package/template/apps/web/test/contracts.test.ts +234 -0
  74. package/template/apps/web/test/loopback.test.ts +62 -0
  75. package/template/apps/web/tsconfig.json +12 -0
  76. package/template/apps/web/vite.config.ts +32 -0
  77. package/template/openxiangda.config.ts +77 -0
  78. package/template/package.json +29 -0
  79. package/template/packages/contracts/package.json +23 -0
  80. package/template/packages/contracts/src/generated.ts +129 -0
  81. package/template/packages/contracts/src/index.ts +1 -0
  82. package/template/packages/contracts/tsconfig.json +9 -0
  83. package/template/platform/data/instruments.ts +81 -0
  84. package/template/pnpm-workspace.yaml +7 -0
  85. package/template/scripts/verify-template-budget.mjs +37 -0
  86. package/template/tsconfig.base.json +13 -0
  87. package/dist/commands/app/create.d.ts +0 -15
  88. package/dist/commands/app/create.d.ts.map +0 -1
  89. package/dist/commands/app/create.js +0 -42
  90. package/dist/commands/app/create.js.map +0 -1
  91. package/dist/commands/app/info.d.ts +0 -9
  92. package/dist/commands/app/info.d.ts.map +0 -1
  93. package/dist/commands/app/info.js +0 -10
  94. package/dist/commands/app/info.js.map +0 -1
  95. package/dist/commands/app/link.d.ts +0 -10
  96. package/dist/commands/app/link.d.ts.map +0 -1
  97. package/dist/commands/app/link.js +0 -16
  98. package/dist/commands/app/link.js.map +0 -1
  99. package/dist/commands/app/provision.d.ts +0 -9
  100. package/dist/commands/app/provision.d.ts.map +0 -1
  101. package/dist/commands/app/provision.js +0 -10
  102. package/dist/commands/app/provision.js.map +0 -1
  103. package/dist/commands/auth/login.d.ts +0 -11
  104. package/dist/commands/auth/login.d.ts.map +0 -1
  105. package/dist/commands/auth/login.js.map +0 -1
  106. package/dist/commands/auth/logout.d.ts +0 -6
  107. package/dist/commands/auth/logout.d.ts.map +0 -1
  108. package/dist/commands/auth/logout.js +0 -30
  109. package/dist/commands/auth/logout.js.map +0 -1
  110. package/dist/commands/auth/status.d.ts +0 -6
  111. package/dist/commands/auth/status.d.ts.map +0 -1
  112. package/dist/commands/auth/status.js +0 -60
  113. package/dist/commands/auth/status.js.map +0 -1
  114. package/dist/commands/authz/membership/grant.d.ts +0 -16
  115. package/dist/commands/authz/membership/grant.d.ts.map +0 -1
  116. package/dist/commands/authz/membership/grant.js +0 -32
  117. package/dist/commands/authz/membership/grant.js.map +0 -1
  118. package/dist/commands/authz/membership/list.d.ts +0 -16
  119. package/dist/commands/authz/membership/list.d.ts.map +0 -1
  120. package/dist/commands/authz/membership/list.js +0 -34
  121. package/dist/commands/authz/membership/list.js.map +0 -1
  122. package/dist/commands/authz/membership/revoke.d.ts +0 -15
  123. package/dist/commands/authz/membership/revoke.d.ts.map +0 -1
  124. package/dist/commands/authz/membership/revoke.js +0 -22
  125. package/dist/commands/authz/membership/revoke.js.map +0 -1
  126. package/dist/commands/authz/membership/update.d.ts +0 -18
  127. package/dist/commands/authz/membership/update.d.ts.map +0 -1
  128. package/dist/commands/authz/membership/update.js +0 -31
  129. package/dist/commands/authz/membership/update.js.map +0 -1
  130. package/dist/commands/authz/relationship/grant.d.ts +0 -20
  131. package/dist/commands/authz/relationship/grant.d.ts.map +0 -1
  132. package/dist/commands/authz/relationship/grant.js +0 -40
  133. package/dist/commands/authz/relationship/grant.js.map +0 -1
  134. package/dist/commands/authz/relationship/list.d.ts +0 -18
  135. package/dist/commands/authz/relationship/list.d.ts.map +0 -1
  136. package/dist/commands/authz/relationship/list.js +0 -43
  137. package/dist/commands/authz/relationship/list.js.map +0 -1
  138. package/dist/commands/authz/relationship/revoke.d.ts +0 -15
  139. package/dist/commands/authz/relationship/revoke.d.ts.map +0 -1
  140. package/dist/commands/authz/relationship/revoke.js +0 -22
  141. package/dist/commands/authz/relationship/revoke.js.map +0 -1
  142. package/dist/commands/authz/relationship/update.d.ts +0 -18
  143. package/dist/commands/authz/relationship/update.d.ts.map +0 -1
  144. package/dist/commands/authz/relationship/update.js +0 -28
  145. package/dist/commands/authz/relationship/update.js.map +0 -1
  146. package/dist/commands/authz/super-admin/grant.d.ts +0 -12
  147. package/dist/commands/authz/super-admin/grant.d.ts.map +0 -1
  148. package/dist/commands/authz/super-admin/grant.js +0 -20
  149. package/dist/commands/authz/super-admin/grant.js.map +0 -1
  150. package/dist/commands/authz/super-admin/list.d.ts +0 -15
  151. package/dist/commands/authz/super-admin/list.d.ts.map +0 -1
  152. package/dist/commands/authz/super-admin/list.js +0 -27
  153. package/dist/commands/authz/super-admin/list.js.map +0 -1
  154. package/dist/commands/authz/super-admin/revoke.d.ts +0 -15
  155. package/dist/commands/authz/super-admin/revoke.d.ts.map +0 -1
  156. package/dist/commands/authz/super-admin/revoke.js +0 -21
  157. package/dist/commands/authz/super-admin/revoke.js.map +0 -1
  158. package/dist/commands/build.d.ts +0 -11
  159. package/dist/commands/build.d.ts.map +0 -1
  160. package/dist/commands/build.js +0 -23
  161. package/dist/commands/build.js.map +0 -1
  162. package/dist/commands/cancel.d.ts +0 -12
  163. package/dist/commands/cancel.d.ts.map +0 -1
  164. package/dist/commands/cancel.js +0 -12
  165. package/dist/commands/cancel.js.map +0 -1
  166. package/dist/commands/dev/reset.d.ts +0 -10
  167. package/dist/commands/dev/reset.d.ts.map +0 -1
  168. package/dist/commands/dev/reset.js +0 -17
  169. package/dist/commands/dev/reset.js.map +0 -1
  170. package/dist/commands/dev/status.d.ts +0 -9
  171. package/dist/commands/dev/status.d.ts.map +0 -1
  172. package/dist/commands/dev/status.js +0 -10
  173. package/dist/commands/dev/status.js.map +0 -1
  174. package/dist/commands/dev/stop.d.ts +0 -9
  175. package/dist/commands/dev/stop.d.ts.map +0 -1
  176. package/dist/commands/dev/stop.js +0 -10
  177. package/dist/commands/dev/stop.js.map +0 -1
  178. package/dist/commands/doctor.d.ts +0 -9
  179. package/dist/commands/doctor.d.ts.map +0 -1
  180. package/dist/commands/doctor.js +0 -7
  181. package/dist/commands/doctor.js.map +0 -1
  182. package/dist/commands/environment/start.d.ts +0 -13
  183. package/dist/commands/environment/start.d.ts.map +0 -1
  184. package/dist/commands/environment/start.js +0 -22
  185. package/dist/commands/environment/start.js.map +0 -1
  186. package/dist/commands/environment/status.d.ts +0 -9
  187. package/dist/commands/environment/status.d.ts.map +0 -1
  188. package/dist/commands/environment/status.js +0 -10
  189. package/dist/commands/environment/status.js.map +0 -1
  190. package/dist/commands/environment/stop.d.ts +0 -13
  191. package/dist/commands/environment/stop.d.ts.map +0 -1
  192. package/dist/commands/environment/stop.js +0 -22
  193. package/dist/commands/environment/stop.js.map +0 -1
  194. package/dist/commands/event/delivery/list.d.ts +0 -10
  195. package/dist/commands/event/delivery/list.d.ts.map +0 -1
  196. package/dist/commands/event/delivery/list.js +0 -14
  197. package/dist/commands/event/delivery/list.js.map +0 -1
  198. package/dist/commands/event/delivery/replay.d.ts +0 -13
  199. package/dist/commands/event/delivery/replay.d.ts.map +0 -1
  200. package/dist/commands/event/delivery/replay.js +0 -16
  201. package/dist/commands/event/delivery/replay.js.map +0 -1
  202. package/dist/commands/event/subscription/list.d.ts +0 -9
  203. package/dist/commands/event/subscription/list.d.ts.map +0 -1
  204. package/dist/commands/event/subscription/list.js +0 -10
  205. package/dist/commands/event/subscription/list.js.map +0 -1
  206. package/dist/commands/event/subscription/rotate-secret.d.ts +0 -15
  207. package/dist/commands/event/subscription/rotate-secret.d.ts.map +0 -1
  208. package/dist/commands/event/subscription/rotate-secret.js +0 -33
  209. package/dist/commands/event/subscription/rotate-secret.js.map +0 -1
  210. package/dist/commands/event/subscription/status.d.ts +0 -14
  211. package/dist/commands/event/subscription/status.d.ts.map +0 -1
  212. package/dist/commands/event/subscription/status.js +0 -19
  213. package/dist/commands/event/subscription/status.js.map +0 -1
  214. package/dist/commands/event/timer/fire.d.ts +0 -12
  215. package/dist/commands/event/timer/fire.d.ts.map +0 -1
  216. package/dist/commands/event/timer/fire.js +0 -12
  217. package/dist/commands/event/timer/fire.js.map +0 -1
  218. package/dist/commands/event/timer/list.d.ts +0 -9
  219. package/dist/commands/event/timer/list.d.ts.map +0 -1
  220. package/dist/commands/event/timer/list.js +0 -10
  221. package/dist/commands/event/timer/list.js.map +0 -1
  222. package/dist/commands/event/timer/status.d.ts +0 -14
  223. package/dist/commands/event/timer/status.d.ts.map +0 -1
  224. package/dist/commands/event/timer/status.js +0 -19
  225. package/dist/commands/event/timer/status.js.map +0 -1
  226. package/dist/commands/generate.d.ts +0 -10
  227. package/dist/commands/generate.d.ts.map +0 -1
  228. package/dist/commands/generate.js +0 -8
  229. package/dist/commands/generate.js.map +0 -1
  230. package/dist/commands/oauth/audit/list.d.ts +0 -12
  231. package/dist/commands/oauth/audit/list.d.ts.map +0 -1
  232. package/dist/commands/oauth/audit/list.js +0 -20
  233. package/dist/commands/oauth/audit/list.js.map +0 -1
  234. package/dist/commands/oauth/client/create.d.ts +0 -13
  235. package/dist/commands/oauth/client/create.d.ts.map +0 -1
  236. package/dist/commands/oauth/client/create.js +0 -31
  237. package/dist/commands/oauth/client/create.js.map +0 -1
  238. package/dist/commands/oauth/client/list.d.ts +0 -9
  239. package/dist/commands/oauth/client/list.d.ts.map +0 -1
  240. package/dist/commands/oauth/client/list.js +0 -10
  241. package/dist/commands/oauth/client/list.js.map +0 -1
  242. package/dist/commands/oauth/client/revoke.d.ts +0 -12
  243. package/dist/commands/oauth/client/revoke.d.ts.map +0 -1
  244. package/dist/commands/oauth/client/revoke.js +0 -12
  245. package/dist/commands/oauth/client/revoke.js.map +0 -1
  246. package/dist/commands/oauth/client/rotate.d.ts +0 -13
  247. package/dist/commands/oauth/client/rotate.d.ts.map +0 -1
  248. package/dist/commands/oauth/client/rotate.js +0 -20
  249. package/dist/commands/oauth/client/rotate.js.map +0 -1
  250. package/dist/commands/oauth/client/update.d.ts +0 -15
  251. package/dist/commands/oauth/client/update.d.ts.map +0 -1
  252. package/dist/commands/oauth/client/update.js +0 -23
  253. package/dist/commands/oauth/client/update.js.map +0 -1
  254. package/dist/commands/oauth/runtime/rotate.d.ts +0 -12
  255. package/dist/commands/oauth/runtime/rotate.d.ts.map +0 -1
  256. package/dist/commands/oauth/runtime/rotate.js +0 -28
  257. package/dist/commands/oauth/runtime/rotate.js.map +0 -1
  258. package/dist/commands/oauth/runtime/status.d.ts +0 -10
  259. package/dist/commands/oauth/runtime/status.d.ts.map +0 -1
  260. package/dist/commands/oauth/runtime/status.js +0 -13
  261. package/dist/commands/oauth/runtime/status.js.map +0 -1
  262. package/dist/commands/promote.d.ts +0 -13
  263. package/dist/commands/promote.d.ts.map +0 -1
  264. package/dist/commands/promote.js +0 -9
  265. package/dist/commands/promote.js.map +0 -1
  266. package/dist/commands/retry.d.ts +0 -12
  267. package/dist/commands/retry.d.ts.map +0 -1
  268. package/dist/commands/retry.js +0 -9
  269. package/dist/commands/retry.js.map +0 -1
  270. package/dist/commands/secret/audit/list.d.ts +0 -14
  271. package/dist/commands/secret/audit/list.d.ts.map +0 -1
  272. package/dist/commands/secret/audit/list.js +0 -16
  273. package/dist/commands/secret/audit/list.js.map +0 -1
  274. package/dist/commands/secret/create.d.ts +0 -18
  275. package/dist/commands/secret/create.d.ts.map +0 -1
  276. package/dist/commands/secret/create.js +0 -33
  277. package/dist/commands/secret/create.js.map +0 -1
  278. package/dist/commands/secret/delete.d.ts +0 -15
  279. package/dist/commands/secret/delete.d.ts.map +0 -1
  280. package/dist/commands/secret/delete.js +0 -20
  281. package/dist/commands/secret/delete.js.map +0 -1
  282. package/dist/commands/secret/list.d.ts +0 -10
  283. package/dist/commands/secret/list.d.ts.map +0 -1
  284. package/dist/commands/secret/list.js +0 -13
  285. package/dist/commands/secret/list.js.map +0 -1
  286. package/dist/commands/secret/rotate.d.ts +0 -18
  287. package/dist/commands/secret/rotate.d.ts.map +0 -1
  288. package/dist/commands/secret/rotate.js +0 -30
  289. package/dist/commands/secret/rotate.js.map +0 -1
  290. package/dist/commands/secret/update.d.ts +0 -17
  291. package/dist/commands/secret/update.d.ts.map +0 -1
  292. package/dist/commands/secret/update.js +0 -37
  293. package/dist/commands/secret/update.js.map +0 -1
  294. package/dist/commands/skill/install.d.ts +0 -11
  295. package/dist/commands/skill/install.d.ts.map +0 -1
  296. package/dist/commands/skill/install.js +0 -52
  297. package/dist/commands/skill/install.js.map +0 -1
  298. package/dist/commands/skill/validate.d.ts +0 -9
  299. package/dist/commands/skill/validate.d.ts.map +0 -1
  300. package/dist/commands/skill/validate.js +0 -32
  301. package/dist/commands/skill/validate.js.map +0 -1
  302. package/dist/commands/test.d.ts +0 -9
  303. package/dist/commands/test.d.ts.map +0 -1
  304. package/dist/commands/test.js +0 -7
  305. package/dist/commands/test.js.map +0 -1
  306. package/dist/commands/workflow/provider/list.d.ts +0 -10
  307. package/dist/commands/workflow/provider/list.d.ts.map +0 -1
  308. package/dist/commands/workflow/provider/list.js +0 -13
  309. package/dist/commands/workflow/provider/list.js.map +0 -1
  310. package/dist/commands/workflow/provider/rotate.d.ts +0 -15
  311. package/dist/commands/workflow/provider/rotate.d.ts.map +0 -1
  312. package/dist/commands/workflow/provider/rotate.js +0 -24
  313. package/dist/commands/workflow/provider/rotate.js.map +0 -1
  314. package/dist/native-authz.d.ts +0 -4
  315. package/dist/native-authz.d.ts.map +0 -1
  316. package/dist/native-authz.js +0 -25
  317. package/dist/native-authz.js.map +0 -1
  318. package/dist/secret-value.d.ts +0 -5
  319. package/dist/secret-value.d.ts.map +0 -1
  320. package/dist/secret-value.js +0 -15
  321. package/dist/secret-value.js.map +0 -1
@@ -0,0 +1,390 @@
1
+ import {
2
+ DeleteOutlined,
3
+ EditOutlined,
4
+ EyeOutlined,
5
+ ExportOutlined,
6
+ ImportOutlined,
7
+ PlusOutlined,
8
+ SearchOutlined,
9
+ } from '@ant-design/icons';
10
+ import { useDelete, useList } from '@refinedev/core';
11
+ import {
12
+ App,
13
+ Button,
14
+ Card,
15
+ DatePicker,
16
+ Input,
17
+ InputNumber,
18
+ Popconfirm,
19
+ Result,
20
+ Select,
21
+ Space,
22
+ Table,
23
+ Tag,
24
+ Tooltip,
25
+ type TableColumnsType,
26
+ } from 'antd';
27
+ import { useMemo, useState } from 'react';
28
+ import { useNavigate } from 'react-router-dom';
29
+ import { Shell } from './Shell';
30
+ import {
31
+ INSTRUMENT_CAPABILITIES,
32
+ type InstrumentListQuery,
33
+ type InstrumentRecord,
34
+ } from './instrument';
35
+ import { useRuntime } from './runtime';
36
+
37
+ const colleges = [
38
+ { label: '先进材料学院', value: 'college-am' },
39
+ { label: '生命健康学院', value: 'college-lh' },
40
+ { label: '公共测试中心', value: 'college-pt' },
41
+ ];
42
+ const admins = [
43
+ { label: '王老师', value: 'user-wang' },
44
+ { label: '李老师', value: 'user-li' },
45
+ { label: '陈老师', value: 'user-chen' },
46
+ ];
47
+ const statuses = [
48
+ { label: '正常', value: 'normal' },
49
+ { label: '维修', value: 'maintenance' },
50
+ { label: '停用', value: 'disabled' },
51
+ { label: '报废', value: 'scrapped' },
52
+ ];
53
+
54
+ export function InstrumentListPage() {
55
+ const navigate = useNavigate();
56
+ const { hasCapability } = useRuntime();
57
+ const { message } = App.useApp();
58
+ const [page, setPage] = useState(1);
59
+ const [pageSize, setPageSize] = useState(20);
60
+ const [draft, setDraft] = useState<Partial<InstrumentListQuery>>({});
61
+ const [query, setQuery] = useState<Partial<InstrumentListQuery>>({});
62
+ const [sort, setSort] = useState<{
63
+ field: string;
64
+ order: 'asc' | 'desc';
65
+ }>({ field: 'updatedAt', order: 'desc' });
66
+ const list = useList<InstrumentRecord>({
67
+ resource: 'instruments',
68
+ pagination: { currentPage: page, pageSize },
69
+ sorters: [sort],
70
+ meta: { query },
71
+ });
72
+ const remove = useDelete();
73
+ const data = (list.result.data || []) as InstrumentRecord[];
74
+ const total = list.result.total || 0;
75
+ const canCreate = hasCapability(INSTRUMENT_CAPABILITIES.create);
76
+ const canUpdate = hasCapability(INSTRUMENT_CAPABILITIES.update);
77
+ const canDelete = hasCapability(INSTRUMENT_CAPABILITIES.delete);
78
+ const columns = useMemo<TableColumnsType<InstrumentRecord>>(
79
+ () => [
80
+ {
81
+ title: '图片',
82
+ dataIndex: 'image',
83
+ width: 84,
84
+ render: value =>
85
+ value?.name ? <Tag>{String(value.name)}</Tag> : <span className="oxa-muted">无图</span>,
86
+ },
87
+ {
88
+ title: '仪器名称',
89
+ dataIndex: 'chineseName',
90
+ width: 210,
91
+ sorter: true,
92
+ render: (value, record) => (
93
+ <a onClick={() => navigate(`/instruments/${record.id}`)}>{value}</a>
94
+ ),
95
+ },
96
+ { title: '规格型号', dataIndex: 'specModel', width: 160 },
97
+ { title: '仪器编码', dataIndex: 'instrumentCode', width: 140 },
98
+ { title: '资产编码', dataIndex: 'assetCode', width: 140 },
99
+ {
100
+ title: '所属学院',
101
+ dataIndex: 'collegeId',
102
+ width: 140,
103
+ render: value =>
104
+ colleges.find(item => item.value === value)?.label || value,
105
+ },
106
+ {
107
+ title: '仪器管理员',
108
+ dataIndex: 'instrumentAdminIds',
109
+ width: 130,
110
+ render: values =>
111
+ (values as string[])
112
+ .map(
113
+ value =>
114
+ admins.find(item => item.value === value)?.label || value
115
+ )
116
+ .join('、'),
117
+ },
118
+ {
119
+ title: '仪器状态',
120
+ dataIndex: 'instrumentStatus',
121
+ width: 100,
122
+ render: value => (
123
+ <Tag
124
+ color={
125
+ value === 'normal'
126
+ ? 'green'
127
+ : value === 'maintenance'
128
+ ? 'orange'
129
+ : 'default'
130
+ }
131
+ >
132
+ {statuses.find(item => item.value === value)?.label || value}
133
+ </Tag>
134
+ ),
135
+ },
136
+ {
137
+ title: '对外开放',
138
+ dataIndex: 'openToOutside',
139
+ width: 90,
140
+ render: value => (
141
+ <Tag color={value ? 'green' : 'default'}>{value ? '是' : '否'}</Tag>
142
+ ),
143
+ },
144
+ {
145
+ title: '更新时间',
146
+ dataIndex: 'updatedAt',
147
+ width: 160,
148
+ sorter: true,
149
+ render: value => (value ? new Date(value).toLocaleString() : '-'),
150
+ },
151
+ {
152
+ title: '操作',
153
+ fixed: 'right',
154
+ width: 180,
155
+ render: (_, record) => (
156
+ <Space>
157
+ <Button
158
+ aria-label="查看"
159
+ icon={<EyeOutlined />}
160
+ size="small"
161
+ onClick={() => navigate(`/instruments/${record.id}`)}
162
+ />
163
+ {canUpdate && (
164
+ <Button
165
+ aria-label="编辑"
166
+ icon={<EditOutlined />}
167
+ size="small"
168
+ onClick={() => navigate(`/instruments/${record.id}/edit`)}
169
+ />
170
+ )}
171
+ {canDelete && (
172
+ <Popconfirm
173
+ title="确定删除这台仪器?"
174
+ description="删除后不可恢复"
175
+ onConfirm={() =>
176
+ remove.mutate(
177
+ {
178
+ resource: 'instruments',
179
+ id: record.id,
180
+ meta: { expectedRevision: record.revision },
181
+ },
182
+ {
183
+ onSuccess: () => {
184
+ message.success('已删除');
185
+ void list.query.refetch();
186
+ },
187
+ }
188
+ )
189
+ }
190
+ >
191
+ <Button
192
+ danger
193
+ aria-label="删除"
194
+ icon={<DeleteOutlined />}
195
+ size="small"
196
+ />
197
+ </Popconfirm>
198
+ )}
199
+ </Space>
200
+ ),
201
+ },
202
+ ],
203
+ [canDelete, canUpdate, list.query, message, navigate, remove]
204
+ );
205
+ if (!hasCapability(INSTRUMENT_CAPABILITIES.read)) {
206
+ return (
207
+ <Shell>
208
+ <Result status="403" title="当前平台用户无仪器页面权限" />
209
+ </Shell>
210
+ );
211
+ }
212
+ return (
213
+ <Shell>
214
+ <Card>
215
+ <Space direction="vertical" size={16} style={{ width: '100%' }}>
216
+ <div className="oxa-page-heading">
217
+ <div>
218
+ <h1 className="oxa-title" data-testid="instrument-title">
219
+ 仪器资源
220
+ </h1>
221
+ <div className="oxa-muted">
222
+ Refine 管理查询状态;CRUD 只经过平台 Data API adapter
223
+ </div>
224
+ </div>
225
+ <Space>
226
+ <Tooltip title="批量事务合同落地后启用,不用自定义接口绕过 Data API">
227
+ <Button disabled icon={<ImportOutlined />}>
228
+ 导入
229
+ </Button>
230
+ </Tooltip>
231
+ <Button
232
+ icon={<ExportOutlined />}
233
+ onClick={() => message.info('导出将使用当前 Data API 查询条件')}
234
+ >
235
+ 导出
236
+ </Button>
237
+ {canCreate && (
238
+ <Button
239
+ type="primary"
240
+ icon={<PlusOutlined />}
241
+ onClick={() => navigate('/instruments/new')}
242
+ >
243
+ 新增仪器
244
+ </Button>
245
+ )}
246
+ </Space>
247
+ </div>
248
+ <div className="oxa-filter-grid">
249
+ <Input
250
+ allowClear
251
+ prefix={<SearchOutlined />}
252
+ placeholder="仪器中文名关键词"
253
+ value={draft.keyword}
254
+ onChange={event =>
255
+ setDraft(value => ({ ...value, keyword: event.target.value }))
256
+ }
257
+ />
258
+ <Select
259
+ allowClear
260
+ options={colleges}
261
+ placeholder="所属学院"
262
+ value={draft.collegeId}
263
+ onChange={value =>
264
+ setDraft(item => ({ ...item, collegeId: value }))
265
+ }
266
+ />
267
+ <Select
268
+ allowClear
269
+ options={admins}
270
+ placeholder="仪器管理员"
271
+ value={draft.instrumentAdminId}
272
+ onChange={value =>
273
+ setDraft(item => ({ ...item, instrumentAdminId: value }))
274
+ }
275
+ />
276
+ <Select
277
+ allowClear
278
+ options={statuses}
279
+ placeholder="仪器状态"
280
+ value={draft.instrumentStatus}
281
+ onChange={value =>
282
+ setDraft(item => ({ ...item, instrumentStatus: value }))
283
+ }
284
+ />
285
+ <Select
286
+ allowClear
287
+ options={[
288
+ { label: '对外开放', value: true },
289
+ { label: '未开放', value: false },
290
+ ]}
291
+ placeholder="是否对外开放"
292
+ value={draft.openToOutside}
293
+ onChange={value =>
294
+ setDraft(item => ({ ...item, openToOutside: value }))
295
+ }
296
+ />
297
+ <DatePicker.RangePicker
298
+ onChange={(_, values) =>
299
+ setDraft(item => ({
300
+ ...item,
301
+ enabledDate:
302
+ values[0] && values[1]
303
+ ? [values[0], values[1]]
304
+ : undefined,
305
+ }))
306
+ }
307
+ />
308
+ <Space.Compact>
309
+ <InputNumber
310
+ min={0}
311
+ placeholder="最低资产价值"
312
+ value={draft.assetValue?.[0]}
313
+ onChange={value =>
314
+ setDraft(item => ({
315
+ ...item,
316
+ assetValue: [
317
+ value === null ? undefined : Number(value),
318
+ item.assetValue?.[1],
319
+ ],
320
+ }))
321
+ }
322
+ />
323
+ <InputNumber
324
+ min={0}
325
+ placeholder="最高资产价值"
326
+ value={draft.assetValue?.[1]}
327
+ onChange={value =>
328
+ setDraft(item => ({
329
+ ...item,
330
+ assetValue: [
331
+ item.assetValue?.[0],
332
+ value === null ? undefined : Number(value),
333
+ ],
334
+ }))
335
+ }
336
+ />
337
+ </Space.Compact>
338
+ <Space>
339
+ <Button
340
+ type="primary"
341
+ icon={<SearchOutlined />}
342
+ onClick={() => {
343
+ setPage(1);
344
+ setQuery(draft);
345
+ }}
346
+ >
347
+ 查询
348
+ </Button>
349
+ <Button
350
+ onClick={() => {
351
+ setDraft({});
352
+ setQuery({});
353
+ setPage(1);
354
+ }}
355
+ >
356
+ 重置
357
+ </Button>
358
+ </Space>
359
+ </div>
360
+ <Table<InstrumentRecord>
361
+ columns={columns}
362
+ dataSource={data}
363
+ loading={list.query.isLoading}
364
+ rowKey="id"
365
+ scroll={{ x: 1450 }}
366
+ onChange={(_, __, sorter) => {
367
+ const item = Array.isArray(sorter) ? sorter[0] : sorter;
368
+ if (item?.field && item.order) {
369
+ setSort({
370
+ field: String(item.field),
371
+ order: item.order === 'ascend' ? 'asc' : 'desc',
372
+ });
373
+ }
374
+ }}
375
+ pagination={{
376
+ current: page,
377
+ pageSize,
378
+ total,
379
+ showSizeChanger: true,
380
+ onChange: (next, size) => {
381
+ setPage(next);
382
+ setPageSize(size);
383
+ },
384
+ }}
385
+ />
386
+ </Space>
387
+ </Card>
388
+ </Shell>
389
+ );
390
+ }
@@ -0,0 +1,52 @@
1
+ import { DatabaseOutlined, SafetyCertificateOutlined } from '@ant-design/icons';
2
+ import { App, Button, Space, Tag } from 'antd';
3
+ import type { ReactNode } from 'react';
4
+ import { useRuntime } from './runtime';
5
+ import { applicationApiPath } from './runtime-meta';
6
+
7
+ export function Shell({ children }: { children: ReactNode }) {
8
+ const { identity } = useRuntime();
9
+ const { message } = App.useApp();
10
+ const inspectContext = async () => {
11
+ const response = await fetch(applicationApiPath('api/instruments/context'), {
12
+ credentials: 'include',
13
+ headers: { accept: 'application/json' },
14
+ });
15
+ if (!response.ok) throw new Error(`HTTP_${response.status}`);
16
+ const body = (await response.json()) as {
17
+ userId?: string;
18
+ data?: { userId?: string };
19
+ };
20
+ message.success(
21
+ `本地 Nest 已读取平台用户:${body.data?.userId || body.userId || identity.userId}`
22
+ );
23
+ };
24
+ return (
25
+ <div className="oxa-shell">
26
+ <header className="oxa-header">
27
+ <Space>
28
+ <DatabaseOutlined />
29
+ <strong>仪器资源管理</strong>
30
+ <Tag color="blue">{identity.environment.key}</Tag>
31
+ </Space>
32
+ <Space wrap>
33
+ <span>{identity.userId}</span>
34
+ <span className="oxa-header-roles">
35
+ {identity.roleCodes.join('、') || '无应用角色'}
36
+ </span>
37
+ <Button
38
+ ghost
39
+ size="small"
40
+ icon={<SafetyCertificateOutlined />}
41
+ onClick={() =>
42
+ void inspectContext().catch(error => message.error(error.message))
43
+ }
44
+ >
45
+ 验证本地 Nest
46
+ </Button>
47
+ </Space>
48
+ </header>
49
+ <main className="oxa-main">{children}</main>
50
+ </div>
51
+ );
52
+ }
@@ -0,0 +1,42 @@
1
+ import type { DataProvider } from '@refinedev/core';
2
+ import type { InstrumentListQuery, InstrumentRecord } from './instrument';
3
+ import { instrumentDataApi } from './platform-client';
4
+ import { applicationCode } from './runtime-meta';
5
+
6
+ export const instrumentProvider = {
7
+ getApiUrl: () =>
8
+ `/service/openxiangda-api/v2/applications/${applicationCode()}/native/data`,
9
+ getList: async ({ pagination, sorters, meta }) => {
10
+ const filters = (meta?.query || {}) as Partial<InstrumentListQuery>;
11
+ const sorter = sorters?.[0];
12
+ const result = await instrumentDataApi.list({
13
+ ...filters,
14
+ page: pagination?.currentPage || 1,
15
+ pageSize: pagination?.pageSize || 20,
16
+ sort: sorter
17
+ ? {
18
+ field: String(sorter.field),
19
+ order: sorter.order,
20
+ }
21
+ : { field: 'updatedAt', order: 'desc' },
22
+ });
23
+ return { data: result.rows, total: result.total };
24
+ },
25
+ getOne: async ({ id }) => ({ data: await instrumentDataApi.get(String(id)) }),
26
+ create: async ({ variables }) => ({
27
+ data: await instrumentDataApi.create(variables as Partial<InstrumentRecord>),
28
+ }),
29
+ update: async ({ id, variables, meta }) => ({
30
+ data: await instrumentDataApi.update(
31
+ String(id),
32
+ Number(meta?.expectedRevision),
33
+ variables as Partial<InstrumentRecord>
34
+ ),
35
+ }),
36
+ deleteOne: async ({ id, meta }) => ({
37
+ data: await instrumentDataApi.remove(
38
+ String(id),
39
+ Number(meta?.expectedRevision)
40
+ ),
41
+ }),
42
+ } as DataProvider;
@@ -0,0 +1,72 @@
1
+ export type FieldKind =
2
+ | 'text'
3
+ | 'textarea'
4
+ | 'number'
5
+ | 'date'
6
+ | 'boolean'
7
+ | 'select'
8
+ | 'multi'
9
+ | 'email'
10
+ | 'phone'
11
+ | 'file';
12
+
13
+ export interface InstrumentField {
14
+ key: string;
15
+ label: string;
16
+ kind: FieldKind;
17
+ section: keyof typeof sectionTitles;
18
+ required?: boolean;
19
+ system?: boolean;
20
+ createCapability?: string;
21
+ updateCapability?: string;
22
+ multiple?: boolean;
23
+ maxCount?: number;
24
+ accept?: string;
25
+ options?: Array<{ label: string; value: string }>;
26
+ }
27
+
28
+ const options = (pairs: Array<[string, string]>) =>
29
+ pairs.map(([label, value]) => ({ label, value }));
30
+
31
+ export const sectionTitles = {
32
+ basic: '基本信息',
33
+ asset: '资产信息',
34
+ usage: '使用与归属',
35
+ management: '管理与联系',
36
+ materials: '地址与资料',
37
+ } as const;
38
+
39
+ // 30 fields: id/revision are Data API system fields; the remaining 28 are
40
+ // declared by platform/data/instruments.ts.
41
+ export const instrumentFields: InstrumentField[] = [
42
+ { key: 'id', label: '记录 ID', kind: 'text', section: 'basic', system: true },
43
+ { key: 'revision', label: '修订版本', kind: 'number', section: 'basic', system: true },
44
+ { key: 'instrumentCode', label: '仪器编码', kind: 'text', section: 'basic', required: true, createCapability: 'app:instrument-center:instrument:field:instrument-code:create', updateCapability: 'app:instrument-center:instrument:field:instrument-code:update' },
45
+ { key: 'chineseName', label: '仪器中文名', kind: 'text', section: 'basic', required: true },
46
+ { key: 'englishName', label: '仪器英文名', kind: 'text', section: 'basic' },
47
+ { key: 'aliasName', label: '别名', kind: 'text', section: 'basic' },
48
+ { key: 'image', label: '仪器图片', kind: 'file', section: 'basic', maxCount: 1, accept: 'image/*' },
49
+ { key: 'specModel', label: '规格型号', kind: 'text', section: 'basic', required: true },
50
+ { key: 'categoryId', label: '仪器分类', kind: 'select', section: 'basic', required: true, options: options([['显微成像', 'analysis_microscope'], ['光谱分析', 'analysis_spectrum'], ['色谱质谱', 'analysis_ms']]) },
51
+ { key: 'instrumentSource', label: '仪器来源', kind: 'select', section: 'basic', options: options([['购置', 'purchase'], ['捐赠', 'donation'], ['调拨', 'transfer']]) },
52
+ { key: 'manufacturer', label: '制造商', kind: 'text', section: 'basic' },
53
+ { key: 'assetCode', label: '资产编码', kind: 'text', section: 'asset', required: true, createCapability: 'app:instrument-center:instrument:field:asset-code:create', updateCapability: 'app:instrument-center:instrument:field:asset-code:update' },
54
+ { key: 'assetValue', label: '资产价值(元)', kind: 'number', section: 'asset', createCapability: 'app:instrument-center:instrument:field:asset-value:create', updateCapability: 'app:instrument-center:instrument:field:asset-value:update' },
55
+ { key: 'enabledDate', label: '启用日期', kind: 'date', section: 'asset', required: true },
56
+ { key: 'usageStatus', label: '使用状况', kind: 'select', section: 'usage', required: true, options: options([['在用', 'active'], ['闲置', 'idle'], ['借出', 'lent']]) },
57
+ { key: 'instrumentStatus', label: '仪器状态', kind: 'select', section: 'usage', required: true, options: options([['正常', 'normal'], ['维修', 'maintenance'], ['停用', 'disabled'], ['报废', 'scrapped']]) },
58
+ { key: 'openToOutside', label: '是否对外开放', kind: 'boolean', section: 'usage', required: true },
59
+ { key: 'subjectAreas', label: '学科领域', kind: 'multi', section: 'usage', options: options([['材料', 'material'], ['生命科学', 'life'], ['化学', 'chemistry'], ['物理', 'physics']]) },
60
+ { key: 'platformProperty', label: '平台属性', kind: 'select', section: 'usage', options: options([['共享平台设备', 'shared'], ['课题组设备', 'research']]) },
61
+ { key: 'collegeId', label: '所属学院', kind: 'select', section: 'usage', required: true, createCapability: 'app:instrument-center:instrument:field:college-id:create', updateCapability: 'app:instrument-center:instrument:field:college-id:update', options: options([['先进材料学院', 'college-am'], ['生命健康学院', 'college-lh'], ['公共测试中心', 'college-pt']]) },
62
+ { key: 'manageDepartmentId', label: '管理部门', kind: 'select', section: 'management', required: true, options: options([['材料测试中心', 'dept-am-test'], ['生命平台中心', 'dept-lh-core'], ['公共测试中心', 'dept-public']]) },
63
+ { key: 'useDepartmentId', label: '使用部门', kind: 'select', section: 'management', required: true, options: options([['材料测试中心', 'dept-am-test'], ['生命平台中心', 'dept-lh-core'], ['公共测试中心', 'dept-public']]) },
64
+ { key: 'instrumentAdminIds', label: '仪器管理员', kind: 'multi', section: 'management', required: true, createCapability: 'app:instrument-center:instrument:field:instrument-admin-ids:create', updateCapability: 'app:instrument-center:instrument:field:instrument-admin-ids:update', options: options([['王老师', 'user-wang'], ['李老师', 'user-li'], ['陈老师', 'user-chen']]) },
65
+ { key: 'contactUserIds', label: '联系人', kind: 'multi', section: 'management', required: true, options: options([['王老师', 'user-wang'], ['李老师', 'user-li'], ['陈老师', 'user-chen']]) },
66
+ { key: 'contactPhone', label: '联系电话', kind: 'phone', section: 'management', required: true },
67
+ { key: 'contactEmail', label: '联系邮箱', kind: 'email', section: 'management' },
68
+ { key: 'locationId', label: '存放点', kind: 'select', section: 'materials', options: options([['A 区 101', 'A-101'], ['B 区 203', 'B-203']]) },
69
+ { key: 'address', label: '详细地址', kind: 'textarea', section: 'materials' },
70
+ { key: 'serviceContent', label: '服务内容与主要功能', kind: 'textarea', section: 'materials' },
71
+ { key: 'attachments', label: '仪器资料', kind: 'file', section: 'materials', multiple: true, maxCount: 8 },
72
+ ];
@@ -0,0 +1,55 @@
1
+ import type { DataFileRef } from 'openxiangda-contracts/browser';
2
+
3
+ export interface InstrumentRecord extends Record<string, unknown> {
4
+ id: string;
5
+ revision: number;
6
+ instrumentCode: string;
7
+ chineseName: string;
8
+ englishName?: string;
9
+ aliasName?: string;
10
+ image?: DataFileRef | null;
11
+ specModel: string;
12
+ categoryId: string;
13
+ instrumentSource?: string;
14
+ manufacturer?: string;
15
+ assetCode: string;
16
+ assetValue?: number;
17
+ enabledDate: string;
18
+ usageStatus: string;
19
+ instrumentStatus: string;
20
+ openToOutside: boolean;
21
+ subjectAreas?: string[];
22
+ platformProperty?: string;
23
+ collegeId: string;
24
+ manageDepartmentId: string;
25
+ useDepartmentId: string;
26
+ instrumentAdminIds: string[];
27
+ contactUserIds: string[];
28
+ contactPhone: string;
29
+ contactEmail?: string;
30
+ locationId?: string;
31
+ address?: string;
32
+ serviceContent?: string;
33
+ attachments?: DataFileRef[];
34
+ updatedAt?: string;
35
+ }
36
+
37
+ export interface InstrumentListQuery {
38
+ page: number;
39
+ pageSize: number;
40
+ keyword?: string;
41
+ collegeId?: string;
42
+ instrumentAdminId?: string;
43
+ instrumentStatus?: string;
44
+ openToOutside?: boolean;
45
+ enabledDate?: [string, string];
46
+ assetValue?: [number | undefined, number | undefined];
47
+ sort?: { field: string; order: 'asc' | 'desc' };
48
+ }
49
+
50
+ export const INSTRUMENT_CAPABILITIES = {
51
+ read: 'app:instrument-center:data:instruments:read',
52
+ create: 'app:instrument-center:data:instruments:create',
53
+ update: 'app:instrument-center:data:instruments:update',
54
+ delete: 'app:instrument-center:data:instruments:delete',
55
+ } as const;
@@ -0,0 +1,58 @@
1
+ import { Refine } from '@refinedev/core';
2
+ import { App as AntdApp, ConfigProvider } from 'antd';
3
+ import zhCN from 'antd/locale/zh_CN';
4
+ import React from 'react';
5
+ import ReactDOM from 'react-dom/client';
6
+ import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
7
+ import { instrumentProvider } from './data-provider';
8
+ import { InstrumentDetailPage } from './InstrumentDetailPage';
9
+ import { InstrumentFormPage } from './InstrumentFormPage';
10
+ import { InstrumentListPage } from './InstrumentListPage';
11
+ import { RuntimeBoundary } from './runtime';
12
+ import { applicationBasename } from './runtime-meta';
13
+ import './styles.css';
14
+
15
+ ReactDOM.createRoot(document.getElementById('root')!).render(
16
+ <React.StrictMode>
17
+ <ConfigProvider
18
+ locale={zhCN}
19
+ theme={{ token: { borderRadius: 6, colorPrimary: '#1677ff' } }}
20
+ >
21
+ <AntdApp>
22
+ <RuntimeBoundary>
23
+ <BrowserRouter basename={applicationBasename()}>
24
+ <Refine
25
+ dataProvider={instrumentProvider}
26
+ resources={[
27
+ {
28
+ name: 'instruments',
29
+ list: '/instruments',
30
+ create: '/instruments/new',
31
+ edit: '/instruments/:id/edit',
32
+ show: '/instruments/:id',
33
+ },
34
+ ]}
35
+ >
36
+ <Routes>
37
+ <Route path="/" element={<Navigate replace to="/instruments" />} />
38
+ <Route path="/instruments" element={<InstrumentListPage />} />
39
+ <Route
40
+ path="/instruments/new"
41
+ element={<InstrumentFormPage mode="create" />}
42
+ />
43
+ <Route
44
+ path="/instruments/:id/edit"
45
+ element={<InstrumentFormPage mode="edit" />}
46
+ />
47
+ <Route
48
+ path="/instruments/:id"
49
+ element={<InstrumentDetailPage />}
50
+ />
51
+ </Routes>
52
+ </Refine>
53
+ </BrowserRouter>
54
+ </RuntimeBoundary>
55
+ </AntdApp>
56
+ </ConfigProvider>
57
+ </React.StrictMode>
58
+ );