@oj-module/cli 0.2.53 → 0.2.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 (330) hide show
  1. package/devkit/SKILL.md +4 -4
  2. package/devkit/manual.md +79 -65
  3. package/package.json +2 -2
  4. package/shell-dist/assets/{index-e_Mp2mb5.js → index-CImwskAz.js} +24 -11
  5. package/shell-dist/assets/runtime.js +7 -4
  6. package/shell-dist/index.html +3 -3
  7. package/shell-dist/versions.json +3 -3
  8. package/src/__tests__/layout.test.ts +30 -1
  9. package/src/__tests__/templates-schema-columns.test.ts +99 -0
  10. package/src/__tests__/templates-tenant-scope.test.ts +3 -2
  11. package/src/alias-paths.ts +11 -12
  12. package/src/builtin.ts +10 -3
  13. package/src/config.ts +9 -0
  14. package/src/contract/__tests__/client-path.test.ts +30 -0
  15. package/src/contract/__tests__/emit-client.test.ts +20 -2
  16. package/src/contract/__tests__/handler-scan.test.ts +23 -0
  17. package/src/contract/__tests__/market-client.integration.test.ts +2 -2
  18. package/src/contract/__tests__/run-api.test.ts +2 -2
  19. package/src/contract/check.ts +11 -7
  20. package/src/contract/client-path.ts +30 -0
  21. package/src/contract/emit-client.ts +27 -6
  22. package/src/contract/handler-scan.ts +40 -3
  23. package/src/contract/run.ts +15 -5
  24. package/src/init.ts +29 -3
  25. package/src/layout.ts +72 -16
  26. package/src/lifecycle/__tests__/manifest-contract.test.ts +2 -2
  27. package/src/market/__tests__/bootstrap.test.ts +1 -1
  28. package/src/site.ts +1 -1
  29. package/templates/README.md +49 -43
  30. package/templates/init.include.yaml +31 -19
  31. package/templates/openapi.json +1488 -120
  32. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/users.ts +27 -0
  33. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/oplog.ts +4 -2
  34. package/templates/src/{_platform/api → app/api/_platform}/contacts/api.ts +29 -1
  35. package/templates/src/{_platform/api → app/api/_platform}/contacts/detail/api.ts +25 -1
  36. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.mysql.sql +7 -0
  37. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.pg.sql +4 -0
  38. package/templates/src/app/api/_platform/migrations/0017__users_employee_no.sql +8 -0
  39. package/templates/src/{_platform/api → app/api/_platform}/schema.yaml +5 -0
  40. package/templates/src/{_platform/api → app/api/_platform}/solutions/api.ts +60 -1
  41. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/replay.ts +9 -3
  42. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/tenant-admin.ts +18 -27
  43. package/templates/src/{auth/api → app/api/auth}/api.ts +59 -0
  44. package/templates/src/{auth/api → app/api/auth}/ldap-login/api.ts +37 -1
  45. package/templates/src/{auth/api → app/api/auth}/wecom/api.ts +23 -0
  46. package/templates/src/{notification/api → app/api/notification}/api.ts +17 -0
  47. package/templates/src/{notification/api → app/api/notification}/del/api.ts +2 -0
  48. package/templates/src/{system/api → app/api/system}/oplog/api.ts +5 -3
  49. package/templates/src/{system/api → app/api/system}/roles/api.ts +3 -0
  50. package/templates/src/{system/api → app/api/system}/roles/bindings/api.ts +3 -0
  51. package/templates/src/{system/api → app/api/system}/settings/api.ts +3 -0
  52. package/templates/src/{system/api → app/api/system}/users/forbidden/api.ts +3 -0
  53. package/templates/src/{system/api → app/api/system}/users/password/api.ts +3 -0
  54. package/templates/src/{system/api → app/api/system}/users/roles/api.ts +3 -0
  55. package/templates/src/app/api/web/get-async-routes/api.ts +23 -0
  56. package/templates/src/app/api/web/hello/api.ts +22 -0
  57. package/templates/src/{web/api → app/api/web}/user-info/api.ts +21 -1
  58. package/templates/src/{_platform/web → app/web/_platform}/__client/api.schemas.ts +99 -35
  59. package/templates/src/{_platform/web/__client/api.ts → app/web/_platform/__client/index.ts} +108 -61
  60. package/templates/src/{auth/web → app/web/auth}/__client/api.schemas.ts +133 -13
  61. package/templates/src/{auth/web/__client/api.ts → app/web/auth/__client/index.ts} +134 -33
  62. package/templates/src/app/web/entry.ts +77 -0
  63. package/templates/src/{home/web/__client/api.ts → app/web/home/__client/index.ts} +5 -2
  64. package/templates/src/app/web/home/routes.ts +41 -0
  65. package/templates/src/app/web/login/routes.ts +71 -0
  66. package/templates/src/{notification/web → app/web/notification}/__client/api.schemas.ts +14 -0
  67. package/templates/src/{notification/web/__client/api.ts → app/web/notification/__client/index.ts} +67 -20
  68. package/templates/src/{notification/web → app/web/notification}/pages/center/index.tsx +1 -1
  69. package/templates/src/app/web/notification/routes.ts +70 -0
  70. package/templates/src/{personal-center/web/__client/api.ts → app/web/personal-center/__client/index.ts} +5 -2
  71. package/templates/src/{personal-center/web → app/web/personal-center}/pages/my-profile/index.tsx +1 -1
  72. package/templates/src/app/web/personal-center/routes.ts +49 -0
  73. package/templates/src/{system/web → app/web/system}/__client/api.schemas.ts +29 -22
  74. package/templates/src/{system/web/__client/api.ts → app/web/system/__client/index.ts} +103 -58
  75. package/templates/src/{system/web → app/web/system}/locales/en-US.json +2 -3
  76. package/templates/src/{system/web → app/web/system}/locales/zh-CN.json +2 -3
  77. package/templates/src/{system/web → app/web/system}/pages/departments/index.tsx +2 -2
  78. package/templates/src/{system/web → app/web/system}/pages/login-methods/index.tsx +1 -1
  79. package/templates/src/{system/web → app/web/system}/pages/oplog/index.tsx +44 -19
  80. package/templates/src/{system/web → app/web/system}/pages/roles/index.tsx +5 -3
  81. package/templates/src/app/web/system/pages/roles/visible-roles.ts +14 -0
  82. package/templates/src/{system/web → app/web/system}/pages/settings/index.tsx +2 -2
  83. package/templates/src/app/web/system/pages/users/can-manage.ts +21 -0
  84. package/templates/src/{system/web → app/web/system}/pages/users/index.tsx +23 -15
  85. package/templates/src/{system/web → app/web/system}/pages/users/roles.tsx +1 -1
  86. package/templates/src/{system/web → app/web/system}/pages/wecom/index.tsx +12 -3
  87. package/templates/src/{system/web/entry.ts → app/web/system/routes.ts} +25 -36
  88. package/templates/src/app/web/web/__client/api.schemas.ts +36 -0
  89. package/templates/src/{web/web/__client/api.ts → app/web/web/__client/index.ts} +38 -11
  90. package/templates/src/oj-market/web/api/defaultClient.ts +1 -1
  91. package/templates/tests/auth-ldap.spec.ts +2 -2
  92. package/templates/tests/auth-session.spec.ts +3 -3
  93. package/templates/tests/auth-throttle.spec.ts +1 -1
  94. package/templates/tests/fixture.ts +6 -4
  95. package/templates/tests/guard.spec.ts +1 -1
  96. package/templates/tests/lifecycle/atomic-state.spec.ts +3 -3
  97. package/templates/tests/lifecycle/bootstrap.spec.ts +3 -3
  98. package/templates/tests/lifecycle/deploy-smoke.spec.ts +8 -8
  99. package/templates/tests/lifecycle/installer.spec.ts +10 -10
  100. package/templates/tests/lifecycle/market-api.spec.ts +14 -14
  101. package/templates/tests/lifecycle/migrate.spec.ts +6 -6
  102. package/templates/tests/lifecycle/preflight.spec.ts +3 -3
  103. package/templates/tests/lifecycle/register.spec.ts +1 -1
  104. package/templates/tests/lifecycle/semver.spec.ts +1 -1
  105. package/templates/tests/lifecycle/sha384.spec.ts +1 -1
  106. package/templates/tests/lifecycle/solutions-api.spec.ts +1 -1
  107. package/templates/tests/lifecycle/source-seq.spec.ts +1 -1
  108. package/templates/tests/lifecycle/steps-fs.spec.ts +9 -9
  109. package/templates/tests/lifecycle/text-edit.spec.ts +1 -1
  110. package/templates/tests/lifecycle/tgz-guard.spec.ts +2 -2
  111. package/templates/tests/lifecycle/trust.spec.ts +5 -5
  112. package/templates/tests/lifecycle/uninstall.spec.ts +7 -7
  113. package/templates/tests/lifecycle/verify-checks.spec.ts +5 -5
  114. package/templates/tests/lifecycle/verify-report.spec.ts +4 -4
  115. package/templates/tests/org-departments.spec.ts +10 -6
  116. package/templates/tests/org-read.spec.ts +7 -7
  117. package/templates/tests/query-utils.spec.ts +1 -1
  118. package/templates/tests/rbac-audit.spec.ts +1 -1
  119. package/templates/tests/rbac-contacts.spec.ts +2 -2
  120. package/templates/tests/rbac-guard.spec.ts +1 -1
  121. package/templates/tests/rbac-roles.spec.ts +10 -2
  122. package/templates/tests/rbac-seed.spec.ts +1 -1
  123. package/templates/tests/rbac-settings.spec.ts +77 -7
  124. package/templates/tests/rbac-solution-perms.spec.ts +1 -1
  125. package/templates/tests/rbac-tables.spec.ts +29 -0
  126. package/templates/tests/rbac-users.spec.ts +55 -3
  127. package/templates/tests/roles-page.spec.ts +40 -0
  128. package/templates/tests/seed-role-accounts.spec.ts +1 -1
  129. package/templates/tests/tenant-access-cache.spec.ts +1 -1
  130. package/templates/tests/tenant-forgery.spec.ts +5 -5
  131. package/templates/tests/tenant-isolation.spec.ts +5 -4
  132. package/templates/tests/username-namespace.spec.ts +3 -3
  133. package/templates/tests/users-page.spec.ts +39 -0
  134. package/templates/tests/wecom-admin.spec.ts +6 -6
  135. package/templates/tests/wecom-client-timeout.spec.ts +1 -1
  136. package/templates/tests/wecom-connector.spec.ts +9 -9
  137. package/templates/tests/wecom-corp.spec.ts +9 -9
  138. package/templates/tests/wecom-crypt.spec.ts +1 -1
  139. package/templates/tests/wecom-endpoints.spec.ts +2 -2
  140. package/templates/tests/wecom-landing.spec.ts +1 -1
  141. package/templates/tests/wecom-org-sync.spec.ts +7 -7
  142. package/templates/tests/wecom-sync.spec.ts +3 -3
  143. package/templates/tsconfig.paths.json +11 -11
  144. package/templates/vitest.config.ts +2 -2
  145. package/templates/web.config.ts +24 -15
  146. package/vendor/host-versions.json +3 -3
  147. package/templates/src/home/web/entry.ts +0 -49
  148. package/templates/src/login/web/entry.ts +0 -79
  149. package/templates/src/notification/web/entry.ts +0 -84
  150. package/templates/src/personal-center/web/entry.ts +0 -60
  151. package/templates/src/web/api/get-async-routes/api.ts +0 -8
  152. package/templates/src/web/api/hello/api.ts +0 -10
  153. package/templates/src/web/web/__client/api.schemas.ts +0 -8
  154. package/templates/src/{_platform/api → app/api/_platform}/README.md +0 -0
  155. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/atomic.ts +0 -0
  156. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/history.ts +0 -0
  157. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/installer.ts +0 -0
  158. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/limits.ts +0 -0
  159. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/lock.ts +0 -0
  160. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/semver.ts +0 -0
  161. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/sha384.ts +0 -0
  162. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/source-seq.ts +0 -0
  163. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/state.ts +0 -0
  164. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/deploy-web.ts +0 -0
  165. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/download.ts +0 -0
  166. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/extract.ts +0 -0
  167. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/migrate.ts +0 -0
  168. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/preflight.ts +0 -0
  169. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/register.ts +0 -0
  170. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/reload.ts +0 -0
  171. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/smoke.ts +0 -0
  172. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/steps/verify.ts +0 -0
  173. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/tasks.ts +0 -0
  174. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/text-edit.ts +0 -0
  175. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/tgz-guard.ts +0 -0
  176. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/trust.ts +0 -0
  177. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/types.ts +0 -0
  178. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/uninstall.ts +0 -0
  179. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/verify-checks.ts +0 -0
  180. package/templates/src/{_platform/api → app/api/_platform}/_lifecycle/verify-report.ts +0 -0
  181. package/templates/src/{_platform/api → app/api/_platform}/_shared/respond.ts +0 -0
  182. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/contacts.ts +0 -0
  183. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/org-read.ts +0 -0
  184. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/org.ts +0 -0
  185. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/roles.ts +0 -0
  186. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/settings.ts +0 -0
  187. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/domain/solution-perms.ts +0 -0
  188. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/access.ts +0 -0
  189. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/audit.ts +0 -0
  190. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/guard.ts +0 -0
  191. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/query.ts +0 -0
  192. package/templates/src/{_platform/api → app/api/_platform}/_shared/service/infra/rate.ts +0 -0
  193. package/templates/src/{_platform/api → app/api/_platform}/_shared/site-facade.ts +0 -0
  194. package/templates/src/{_platform/api → app/api/_platform}/fixtures/l1-test-user.sql +0 -0
  195. package/templates/src/{_platform/api → app/api/_platform}/manifest.yaml +0 -0
  196. package/templates/src/{_platform/api → app/api/_platform}/market/api.ts +0 -0
  197. package/templates/src/{_platform/api → app/api/_platform}/market/bootstrap.ts +0 -0
  198. package/templates/src/{_platform/api → app/api/_platform}/market/index-source.ts +0 -0
  199. package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.mysql.sql +0 -0
  200. package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.pg.sql +0 -0
  201. package/templates/src/{_platform/api → app/api/_platform}/migrations/0001__create_users.sql +0 -0
  202. package/templates/src/{_platform/api → app/api/_platform}/migrations/0002__add_users_avatar_base64.mysql.sql +0 -0
  203. package/templates/src/{_platform/api → app/api/_platform}/migrations/0002__add_users_avatar_base64.sql +0 -0
  204. package/templates/src/{_platform/api → app/api/_platform}/migrations/0003__users_source_display_name.mysql.sql +0 -0
  205. package/templates/src/{_platform/api → app/api/_platform}/migrations/0003__users_source_display_name.sql +0 -0
  206. package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.mysql.sql +0 -0
  207. package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.pg.sql +0 -0
  208. package/templates/src/{_platform/api → app/api/_platform}/migrations/0004__sys_rbac.sql +0 -0
  209. package/templates/src/{_platform/api → app/api/_platform}/migrations/0005__users_must_change.mysql.sql +0 -0
  210. package/templates/src/{_platform/api → app/api/_platform}/migrations/0005__users_must_change.sql +0 -0
  211. package/templates/src/{_platform/api → app/api/_platform}/migrations/0006__users_is_forbidden.mysql.sql +0 -0
  212. package/templates/src/{_platform/api → app/api/_platform}/migrations/0006__users_is_forbidden.sql +0 -0
  213. package/templates/src/{_platform/api → app/api/_platform}/migrations/0007__users_email.sql +0 -0
  214. package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.mysql.sql +0 -0
  215. package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.pg.sql +0 -0
  216. package/templates/src/{_platform/api → app/api/_platform}/migrations/0008__sys_refresh_token.sql +0 -0
  217. package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.mysql.sql +0 -0
  218. package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.pg.sql +0 -0
  219. package/templates/src/{_platform/api → app/api/_platform}/migrations/0009__sys_throttle.sql +0 -0
  220. package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.mysql.sql +0 -0
  221. package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.pg.sql +0 -0
  222. package/templates/src/{_platform/api → app/api/_platform}/migrations/0010__sys_solution_route.sql +0 -0
  223. package/templates/src/{_platform/api → app/api/_platform}/migrations/0011__sys_setting_group_label.mysql.sql +0 -0
  224. package/templates/src/{_platform/api → app/api/_platform}/migrations/0011__sys_setting_group_label.sql +0 -0
  225. package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.mysql.sql +0 -0
  226. package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.pg.sql +0 -0
  227. package/templates/src/{_platform/api → app/api/_platform}/migrations/0012__market_history.sql +0 -0
  228. package/templates/src/{_platform/api → app/api/_platform}/migrations/0013__users_profile.sql +0 -0
  229. package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.mysql.sql +0 -0
  230. package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.pg.sql +0 -0
  231. package/templates/src/{_platform/api → app/api/_platform}/migrations/0014__reg_source_seq.sql +0 -0
  232. package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.mysql.sql +0 -0
  233. package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.pg.sql +0 -0
  234. package/templates/src/{_platform/api → app/api/_platform}/migrations/0015__tenant.sql +0 -0
  235. package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.mysql.sql +0 -0
  236. package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.pg.sql +0 -0
  237. package/templates/src/{_platform/api → app/api/_platform}/migrations/0016__org.sql +0 -0
  238. package/templates/src/{_platform/api → app/api/_platform}/org/departments/api.ts +0 -0
  239. package/templates/src/{_platform/api → app/api/_platform}/org/is-member/api.ts +0 -0
  240. package/templates/src/{_platform/api → app/api/_platform}/org/members/api.ts +0 -0
  241. package/templates/src/{_platform/api → app/api/_platform}/org/picker/api.ts +0 -0
  242. package/templates/src/{_platform/api → app/api/_platform}/org/resolve/api.ts +0 -0
  243. package/templates/src/{_platform/api → app/api/_platform}/org/tree/api.ts +0 -0
  244. package/templates/src/{_platform/api → app/api/_platform}/org/user/api.ts +0 -0
  245. package/templates/src/{_platform/api → app/api/_platform}/seed.mysql.sql +0 -0
  246. package/templates/src/{_platform/api → app/api/_platform}/seed.pg.sql +0 -0
  247. package/templates/src/{_platform/api → app/api/_platform}/seed.sql +0 -0
  248. package/templates/src/{auth/api → app/api/auth}/_shared/ldap-login.ts +0 -0
  249. package/templates/src/{auth/api → app/api/auth}/_shared/ldap.ts +0 -0
  250. package/templates/src/{auth/api → app/api/auth}/_shared/login-throttle.ts +0 -0
  251. package/templates/src/{auth/api → app/api/auth}/_shared/session.ts +0 -0
  252. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/admin.ts +1 -1
  253. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/client.ts +0 -0
  254. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/config.ts +0 -0
  255. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/contacts.ts +0 -0
  256. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/crypt.ts +0 -0
  257. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/events.ts +1 -1
  258. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/login.ts +0 -0
  259. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/payload.ts +0 -0
  260. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/store.ts +0 -0
  261. package/templates/src/{auth/api → app/api/auth}/_shared/wecom/sync.ts +0 -0
  262. package/templates/src/{auth/api → app/api/auth}/manifest.yaml +0 -0
  263. package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.mysql.sql +0 -0
  264. package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.pg.sql +0 -0
  265. package/templates/src/{auth/api → app/api/auth}/migrations/0001__wecom.sql +0 -0
  266. package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.mysql.sql +0 -0
  267. package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.pg.sql +0 -0
  268. package/templates/src/{auth/api → app/api/auth}/migrations/0002__org_sync.sql +0 -0
  269. package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.mysql.sql +0 -0
  270. package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.pg.sql +0 -0
  271. package/templates/src/{auth/api → app/api/auth}/migrations/0003__credential_kind.sql +0 -0
  272. package/templates/src/{auth/api → app/api/auth}/schema.yaml +0 -0
  273. package/templates/src/{auth/api → app/api/auth}/wecom/admin/api.ts +0 -0
  274. package/templates/src/{auth/api → app/api/auth}/wecom/admin/tenant-admin/api.ts +1 -1
  275. package/templates/src/{auth/api → app/api/auth}/wecom/admin/tenant-admin/password/api.ts +1 -1
  276. /package/templates/src/{auth/api → app/api/auth}/wecom/sync/api.ts +0 -0
  277. /package/templates/src/{home/api → app/api/home}/line/api.ts +0 -0
  278. /package/templates/src/{home/api → app/api/home}/manifest.yaml +0 -0
  279. /package/templates/src/{home/api → app/api/home}/migrations/0001__create_home_tables.sql +0 -0
  280. /package/templates/src/{home/api → app/api/home}/migrations/0002__tenant.mysql.sql +0 -0
  281. /package/templates/src/{home/api → app/api/home}/migrations/0002__tenant.sql +0 -0
  282. /package/templates/src/{home/api → app/api/home}/pie/api.ts +0 -0
  283. /package/templates/src/{home/api → app/api/home}/schema.yaml +0 -0
  284. /package/templates/src/{home/api → app/api/home}/seed.sql +0 -0
  285. /package/templates/src/{notification/api → app/api/notification}/_shared/notify.ts +0 -0
  286. /package/templates/src/{notification/api → app/api/notification}/manifest.yaml +0 -0
  287. /package/templates/src/{notification/api → app/api/notification}/migrations/0001__create_notifications.sql +0 -0
  288. /package/templates/src/{notification/api → app/api/notification}/migrations/0002__notifications_user.sql +0 -0
  289. /package/templates/src/{notification/api → app/api/notification}/migrations/0003__notifications_broadcast.sql +0 -0
  290. /package/templates/src/{notification/api → app/api/notification}/migrations/0004__notifications_broadcast_hidden.sql +0 -0
  291. /package/templates/src/{notification/api → app/api/notification}/migrations/0005__tenant.mysql.sql +0 -0
  292. /package/templates/src/{notification/api → app/api/notification}/migrations/0005__tenant.sql +0 -0
  293. /package/templates/src/{notification/api → app/api/notification}/schema.yaml +0 -0
  294. /package/templates/src/{notification/api → app/api/notification}/seed.sql +0 -0
  295. /package/templates/src/{personal-center/api → app/api/personal-center}/manifest.yaml +0 -0
  296. /package/templates/src/{personal-center/api → app/api/personal-center}/profile/api.ts +0 -0
  297. /package/templates/src/{personal-center/api → app/api/personal-center}/upload/api.ts +0 -0
  298. /package/templates/src/{system/api → app/api/system}/departments/api.ts +0 -0
  299. /package/templates/src/{system/api → app/api/system}/departments/detach/api.ts +0 -0
  300. /package/templates/src/{system/api → app/api/system}/departments/members/api.ts +0 -0
  301. /package/templates/src/{system/api → app/api/system}/departments/move/api.ts +0 -0
  302. /package/templates/src/{system/api → app/api/system}/login-methods/api.ts +0 -0
  303. /package/templates/src/{system/api → app/api/system}/manifest.yaml +0 -0
  304. /package/templates/src/{system/api → app/api/system}/permissions/api.ts +0 -0
  305. /package/templates/src/{system/api → app/api/system}/users/api.ts +0 -0
  306. /package/templates/src/{system/api → app/api/system}/users/profile/api.ts +0 -0
  307. /package/templates/src/{web/api → app/api/web}/manifest.yaml +0 -0
  308. /package/templates/src/{home/web → app/web/home}/__client/api.schemas.ts +0 -0
  309. /package/templates/src/{home/web → app/web/home}/locales/en-US.json +0 -0
  310. /package/templates/src/{home/web → app/web/home}/locales/zh-CN.json +0 -0
  311. /package/templates/src/{home/web → app/web/home}/pages/components/bar-chart.tsx +0 -0
  312. /package/templates/src/{home/web → app/web/home}/pages/components/card-list.tsx +0 -0
  313. /package/templates/src/{home/web → app/web/home}/pages/components/line-chart.tsx +0 -0
  314. /package/templates/src/{home/web → app/web/home}/pages/components/pie-chart.tsx +0 -0
  315. /package/templates/src/{home/web → app/web/home}/pages/index.tsx +0 -0
  316. /package/templates/src/{login/web → app/web/login}/auth-provider.ts +0 -0
  317. /package/templates/src/{login/web → app/web/login}/locales/en-US.json +0 -0
  318. /package/templates/src/{login/web → app/web/login}/locales/zh-CN.json +0 -0
  319. /package/templates/src/{login/web → app/web/login}/pages/change-password.tsx +0 -0
  320. /package/templates/src/{login/web → app/web/login}/pages/login.tsx +0 -0
  321. /package/templates/src/{login/web → app/web/login}/wecom.ts +0 -0
  322. /package/templates/src/{notification/web → app/web/notification}/locales/en-US.json +0 -0
  323. /package/templates/src/{notification/web → app/web/notification}/locales/zh-CN.json +0 -0
  324. /package/templates/src/{personal-center/web → app/web/personal-center}/__client/api.schemas.ts +0 -0
  325. /package/templates/src/{personal-center/web → app/web/personal-center}/locales/en-US.json +0 -0
  326. /package/templates/src/{personal-center/web → app/web/personal-center}/locales/zh-CN.json +0 -0
  327. /package/templates/src/{system/web → app/web/system}/pages/departments/sync-bar.tsx +0 -0
  328. /package/templates/src/{system/web → app/web/system}/pages/departments/tree.ts +0 -0
  329. /package/templates/src/{system/web → app/web/system}/pages/errors.ts +0 -0
  330. /package/templates/src/{system/web → app/web/system}/pages/forbidden.tsx +0 -0
@@ -17,6 +17,9 @@ export const API_PREFIX = "/system";
17
17
  /** 认领身份前缀(契约 apiPrefix,字面等于模块目录名):跨模块认领护栏对比基准 */
18
18
  export const MODULE_PREFIX = "/system";
19
19
 
20
+ /** 认领方模块(web.config clientOwners 指定):跨模块认领护栏对比基准 */
21
+ export const MODULE_OWNER = "/app";
22
+
20
23
  /**
21
24
  * 模块 entry 的 onInit 里一行创建(构造即 install):完成 apiPrefix 登记 +
22
25
  * scoped request 绑定,返回绑好前缀的端点函数集合。一模块一 client;
@@ -28,12 +31,12 @@ export function createSystemClient(ctx: ModuleContext) {
28
31
  // 单槽、卸载互相耦合;跨模块需求应由本模块创建后经 provider 暴露。
29
32
  // 注意对比基准是 MODULE_PREFIX(目录名)而非 API_PREFIX——D-M8 映射时线上前缀
30
33
  // 属于别的命名空间,认领身份仍按目录名。
31
- if (import.meta.env.DEV && ctx.module.name !== MODULE_PREFIX.slice(1)) {
32
- console.warn(`[ojm-api] 模块 "${ctx.module.name}" 正在认领 ${MODULE_PREFIX} 的 client——跨模块认领会共享 request 单槽、卸载互相耦合;应由 "${MODULE_PREFIX.slice(1)}" 模块在自身 onInit 创建,跨模块需求经 provider 暴露。`);
34
+ if (import.meta.env.DEV && ctx.module.name !== MODULE_OWNER.slice(1)) {
35
+ console.warn(`[ojm-api] 模块 "${ctx.module.name}" 正在认领 ${MODULE_PREFIX} 的 client——跨模块认领会共享 request 单槽、卸载互相耦合;应由 "${MODULE_OWNER.slice(1)}" 模块在自身 onInit 创建,跨模块需求经 provider 暴露。`);
33
36
  }
34
37
  ctx.register.apiPrefix(API_PREFIX);
35
38
  bindRequest(ctx.utils.request);
36
- return { systemDepartmentsGet, systemDepartmentsPost, systemDepartmentsPut, systemDepartmentsDelete, systemDepartmentsMovePut, systemDepartmentsDetachPost, systemDepartmentsMembersGet, systemDepartmentsMembersPost, systemDepartmentsMembersPut, systemDepartmentsMembersDelete, systemLoginMethodsGet, systemOplogGet, systemOplogPost, systemPermissionsGet, systemRolesGet, systemRolesPost, systemRolesDelete, systemRolesBindingsPut, systemSettingsGet, systemSettingsPut, systemUsersGet, systemUsersPost, systemUsersDelete, systemUsersProfilePut, systemUsersForbiddenPut, systemUsersPasswordPut, systemUsersRolesPut };
39
+ return { systemDepartmentsGet, systemDepartmentsPost, systemDepartmentsPut, systemDepartmentsDelete, systemDepartmentsDetachPost, systemDepartmentsMembersGet, systemDepartmentsMembersPost, systemDepartmentsMembersPut, systemDepartmentsMembersDelete, systemDepartmentsMovePut, systemLoginMethodsGet, systemOplogGet, systemOplogPost, systemPermissionsGet, systemRolesGet, systemRolesPost, systemRolesDelete, systemRolesBindingsPut, systemSettingsGet, systemSettingsPut, systemUsersGet, systemUsersPost, systemUsersDelete, systemUsersForbiddenPut, systemUsersPasswordPut, systemUsersProfilePut, systemUsersRolesPut };
37
40
  }
38
41
 
39
42
  /**
@@ -160,31 +163,6 @@ export async function systemDepartmentsDelete(query: SystemDepartmentsDeleteQuer
160
163
  }
161
164
  }
162
165
 
163
- export type SystemDepartmentsMovePutQuery = z.input<(typeof schemas)["systemDepartmentsMovePut"]["query"]>;
164
- export type SystemDepartmentsMovePutBody = z.input<(typeof schemas)["systemDepartmentsMovePut"]["body"]>;
165
- export type SystemDepartmentsMovePutData = z.infer<(typeof schemas)["systemDepartmentsMovePut"]["data"]>;
166
-
167
- export async function systemDepartmentsMovePut(input: { query: SystemDepartmentsMovePutQuery, body: SystemDepartmentsMovePutBody }): Promise<SystemDepartmentsMovePutData> {
168
- const client = ensureReq();
169
- try {
170
- const env = await client.put(`system/departments/move`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemDepartmentsMovePutData>>();
171
- // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
172
- if (typeof env.code === "number" && env.code !== 0)
173
- throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
174
- const data = env.data as SystemDepartmentsMovePutData;
175
- if (import.meta.env.DEV) {
176
- const { schemas } = await import("./api.schemas");
177
- const r = schemas["systemDepartmentsMovePut"].data.safeParse(data);
178
- if (!r.success)
179
- throw new ContractApiError(-1, `[契约违例] systemDepartmentsMovePut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
180
- }
181
- return data;
182
- }
183
- catch (e) {
184
- throw await toApiError(e);
185
- }
186
- }
187
-
188
166
  export type SystemDepartmentsDetachPostQuery = z.input<(typeof schemas)["systemDepartmentsDetachPost"]["query"]>;
189
167
  export type SystemDepartmentsDetachPostData = z.infer<(typeof schemas)["systemDepartmentsDetachPost"]["data"]>;
190
168
 
@@ -307,6 +285,31 @@ export async function systemDepartmentsMembersDelete(query: SystemDepartmentsMem
307
285
  }
308
286
  }
309
287
 
288
+ export type SystemDepartmentsMovePutQuery = z.input<(typeof schemas)["systemDepartmentsMovePut"]["query"]>;
289
+ export type SystemDepartmentsMovePutBody = z.input<(typeof schemas)["systemDepartmentsMovePut"]["body"]>;
290
+ export type SystemDepartmentsMovePutData = z.infer<(typeof schemas)["systemDepartmentsMovePut"]["data"]>;
291
+
292
+ export async function systemDepartmentsMovePut(input: { query: SystemDepartmentsMovePutQuery, body: SystemDepartmentsMovePutBody }): Promise<SystemDepartmentsMovePutData> {
293
+ const client = ensureReq();
294
+ try {
295
+ const env = await client.put(`system/departments/move`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemDepartmentsMovePutData>>();
296
+ // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
297
+ if (typeof env.code === "number" && env.code !== 0)
298
+ throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
299
+ const data = env.data as SystemDepartmentsMovePutData;
300
+ if (import.meta.env.DEV) {
301
+ const { schemas } = await import("./api.schemas");
302
+ const r = schemas["systemDepartmentsMovePut"].data.safeParse(data);
303
+ if (!r.success)
304
+ throw new ContractApiError(-1, `[契约违例] systemDepartmentsMovePut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
305
+ }
306
+ return data;
307
+ }
308
+ catch (e) {
309
+ throw await toApiError(e);
310
+ }
311
+ }
312
+
310
313
  export type SystemLoginMethodsGetData = z.infer<(typeof schemas)["systemLoginMethodsGet"]["data"]>;
311
314
 
312
315
  export async function systemLoginMethodsGet(): Promise<SystemLoginMethodsGetData> {
@@ -449,15 +452,22 @@ export async function systemRolesPost(body: SystemRolesPostBody): Promise<System
449
452
  }
450
453
 
451
454
  export type SystemRolesDeleteQuery = z.input<(typeof schemas)["systemRolesDelete"]["query"]>;
455
+ export type SystemRolesDeleteData = z.infer<(typeof schemas)["systemRolesDelete"]["data"]>;
452
456
 
453
- export async function systemRolesDelete(query: SystemRolesDeleteQuery): Promise<unknown> {
457
+ export async function systemRolesDelete(query: SystemRolesDeleteQuery): Promise<SystemRolesDeleteData> {
454
458
  const client = ensureReq();
455
459
  try {
456
- const env = await client.delete(`system/roles`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<unknown>>();
460
+ const env = await client.delete(`system/roles`, { searchParams: query as Record<string, string | number | boolean> }).json<OjEnvelope<SystemRolesDeleteData>>();
457
461
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
458
462
  if (typeof env.code === "number" && env.code !== 0)
459
463
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
460
- const data = env.data as unknown;
464
+ const data = env.data as SystemRolesDeleteData;
465
+ if (import.meta.env.DEV) {
466
+ const { schemas } = await import("./api.schemas");
467
+ const r = schemas["systemRolesDelete"].data.safeParse(data);
468
+ if (!r.success)
469
+ throw new ContractApiError(-1, `[契约违例] systemRolesDelete 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
470
+ }
461
471
  return data;
462
472
  }
463
473
  catch (e) {
@@ -467,15 +477,22 @@ export async function systemRolesDelete(query: SystemRolesDeleteQuery): Promise<
467
477
 
468
478
  export type SystemRolesBindingsPutQuery = z.input<(typeof schemas)["systemRolesBindingsPut"]["query"]>;
469
479
  export type SystemRolesBindingsPutBody = z.input<(typeof schemas)["systemRolesBindingsPut"]["body"]>;
480
+ export type SystemRolesBindingsPutData = z.infer<(typeof schemas)["systemRolesBindingsPut"]["data"]>;
470
481
 
471
- export async function systemRolesBindingsPut(input: { query: SystemRolesBindingsPutQuery, body: SystemRolesBindingsPutBody }): Promise<unknown> {
482
+ export async function systemRolesBindingsPut(input: { query: SystemRolesBindingsPutQuery, body: SystemRolesBindingsPutBody }): Promise<SystemRolesBindingsPutData> {
472
483
  const client = ensureReq();
473
484
  try {
474
- const env = await client.put(`system/roles/bindings`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<unknown>>();
485
+ const env = await client.put(`system/roles/bindings`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemRolesBindingsPutData>>();
475
486
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
476
487
  if (typeof env.code === "number" && env.code !== 0)
477
488
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
478
- const data = env.data as unknown;
489
+ const data = env.data as SystemRolesBindingsPutData;
490
+ if (import.meta.env.DEV) {
491
+ const { schemas } = await import("./api.schemas");
492
+ const r = schemas["systemRolesBindingsPut"].data.safeParse(data);
493
+ if (!r.success)
494
+ throw new ContractApiError(-1, `[契约违例] systemRolesBindingsPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
495
+ }
479
496
  return data;
480
497
  }
481
498
  catch (e) {
@@ -507,15 +524,22 @@ export async function systemSettingsGet(): Promise<SystemSettingsGetData> {
507
524
  }
508
525
 
509
526
  export type SystemSettingsPutBody = z.input<(typeof schemas)["systemSettingsPut"]["body"]>;
527
+ export type SystemSettingsPutData = z.infer<(typeof schemas)["systemSettingsPut"]["data"]>;
510
528
 
511
- export async function systemSettingsPut(body: SystemSettingsPutBody): Promise<unknown> {
529
+ export async function systemSettingsPut(body: SystemSettingsPutBody): Promise<SystemSettingsPutData> {
512
530
  const client = ensureReq();
513
531
  try {
514
- const env = await client.put(`system/settings`, { json: body }).json<OjEnvelope<unknown>>();
532
+ const env = await client.put(`system/settings`, { json: body }).json<OjEnvelope<SystemSettingsPutData>>();
515
533
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
516
534
  if (typeof env.code === "number" && env.code !== 0)
517
535
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
518
- const data = env.data as unknown;
536
+ const data = env.data as SystemSettingsPutData;
537
+ if (import.meta.env.DEV) {
538
+ const { schemas } = await import("./api.schemas");
539
+ const r = schemas["systemSettingsPut"].data.safeParse(data);
540
+ if (!r.success)
541
+ throw new ContractApiError(-1, `[契约违例] systemSettingsPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
542
+ }
519
543
  return data;
520
544
  }
521
545
  catch (e) {
@@ -594,23 +618,23 @@ export async function systemUsersDelete(query: SystemUsersDeleteQuery): Promise<
594
618
  }
595
619
  }
596
620
 
597
- export type SystemUsersProfilePutQuery = z.input<(typeof schemas)["systemUsersProfilePut"]["query"]>;
598
- export type SystemUsersProfilePutBody = z.input<(typeof schemas)["systemUsersProfilePut"]["body"]>;
599
- export type SystemUsersProfilePutData = z.infer<(typeof schemas)["systemUsersProfilePut"]["data"]>;
621
+ export type SystemUsersForbiddenPutQuery = z.input<(typeof schemas)["systemUsersForbiddenPut"]["query"]>;
622
+ export type SystemUsersForbiddenPutBody = z.input<(typeof schemas)["systemUsersForbiddenPut"]["body"]>;
623
+ export type SystemUsersForbiddenPutData = z.infer<(typeof schemas)["systemUsersForbiddenPut"]["data"]>;
600
624
 
601
- export async function systemUsersProfilePut(input: { query: SystemUsersProfilePutQuery, body: SystemUsersProfilePutBody }): Promise<SystemUsersProfilePutData> {
625
+ export async function systemUsersForbiddenPut(input: { query: SystemUsersForbiddenPutQuery, body: SystemUsersForbiddenPutBody }): Promise<SystemUsersForbiddenPutData> {
602
626
  const client = ensureReq();
603
627
  try {
604
- const env = await client.put(`system/users/profile`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemUsersProfilePutData>>();
628
+ const env = await client.put(`system/users/forbidden`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemUsersForbiddenPutData>>();
605
629
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
606
630
  if (typeof env.code === "number" && env.code !== 0)
607
631
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
608
- const data = env.data as SystemUsersProfilePutData;
632
+ const data = env.data as SystemUsersForbiddenPutData;
609
633
  if (import.meta.env.DEV) {
610
634
  const { schemas } = await import("./api.schemas");
611
- const r = schemas["systemUsersProfilePut"].data.safeParse(data);
635
+ const r = schemas["systemUsersForbiddenPut"].data.safeParse(data);
612
636
  if (!r.success)
613
- throw new ContractApiError(-1, `[契约违例] systemUsersProfilePut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
637
+ throw new ContractApiError(-1, `[契约违例] systemUsersForbiddenPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
614
638
  }
615
639
  return data;
616
640
  }
@@ -619,17 +643,24 @@ export async function systemUsersProfilePut(input: { query: SystemUsersProfilePu
619
643
  }
620
644
  }
621
645
 
622
- export type SystemUsersForbiddenPutQuery = z.input<(typeof schemas)["systemUsersForbiddenPut"]["query"]>;
623
- export type SystemUsersForbiddenPutBody = z.input<(typeof schemas)["systemUsersForbiddenPut"]["body"]>;
646
+ export type SystemUsersPasswordPutQuery = z.input<(typeof schemas)["systemUsersPasswordPut"]["query"]>;
647
+ export type SystemUsersPasswordPutBody = z.input<(typeof schemas)["systemUsersPasswordPut"]["body"]>;
648
+ export type SystemUsersPasswordPutData = z.infer<(typeof schemas)["systemUsersPasswordPut"]["data"]>;
624
649
 
625
- export async function systemUsersForbiddenPut(input: { query: SystemUsersForbiddenPutQuery, body: SystemUsersForbiddenPutBody }): Promise<unknown> {
650
+ export async function systemUsersPasswordPut(input: { query: SystemUsersPasswordPutQuery, body: SystemUsersPasswordPutBody }): Promise<SystemUsersPasswordPutData> {
626
651
  const client = ensureReq();
627
652
  try {
628
- const env = await client.put(`system/users/forbidden`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<unknown>>();
653
+ const env = await client.put(`system/users/password`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemUsersPasswordPutData>>();
629
654
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
630
655
  if (typeof env.code === "number" && env.code !== 0)
631
656
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
632
- const data = env.data as unknown;
657
+ const data = env.data as SystemUsersPasswordPutData;
658
+ if (import.meta.env.DEV) {
659
+ const { schemas } = await import("./api.schemas");
660
+ const r = schemas["systemUsersPasswordPut"].data.safeParse(data);
661
+ if (!r.success)
662
+ throw new ContractApiError(-1, `[契约违例] systemUsersPasswordPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
663
+ }
633
664
  return data;
634
665
  }
635
666
  catch (e) {
@@ -637,17 +668,24 @@ export async function systemUsersForbiddenPut(input: { query: SystemUsersForbidd
637
668
  }
638
669
  }
639
670
 
640
- export type SystemUsersPasswordPutQuery = z.input<(typeof schemas)["systemUsersPasswordPut"]["query"]>;
641
- export type SystemUsersPasswordPutBody = z.input<(typeof schemas)["systemUsersPasswordPut"]["body"]>;
671
+ export type SystemUsersProfilePutQuery = z.input<(typeof schemas)["systemUsersProfilePut"]["query"]>;
672
+ export type SystemUsersProfilePutBody = z.input<(typeof schemas)["systemUsersProfilePut"]["body"]>;
673
+ export type SystemUsersProfilePutData = z.infer<(typeof schemas)["systemUsersProfilePut"]["data"]>;
642
674
 
643
- export async function systemUsersPasswordPut(input: { query: SystemUsersPasswordPutQuery, body: SystemUsersPasswordPutBody }): Promise<unknown> {
675
+ export async function systemUsersProfilePut(input: { query: SystemUsersProfilePutQuery, body: SystemUsersProfilePutBody }): Promise<SystemUsersProfilePutData> {
644
676
  const client = ensureReq();
645
677
  try {
646
- const env = await client.put(`system/users/password`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<unknown>>();
678
+ const env = await client.put(`system/users/profile`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemUsersProfilePutData>>();
647
679
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
648
680
  if (typeof env.code === "number" && env.code !== 0)
649
681
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
650
- const data = env.data as unknown;
682
+ const data = env.data as SystemUsersProfilePutData;
683
+ if (import.meta.env.DEV) {
684
+ const { schemas } = await import("./api.schemas");
685
+ const r = schemas["systemUsersProfilePut"].data.safeParse(data);
686
+ if (!r.success)
687
+ throw new ContractApiError(-1, `[契约违例] systemUsersProfilePut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
688
+ }
651
689
  return data;
652
690
  }
653
691
  catch (e) {
@@ -657,15 +695,22 @@ export async function systemUsersPasswordPut(input: { query: SystemUsersPassword
657
695
 
658
696
  export type SystemUsersRolesPutQuery = z.input<(typeof schemas)["systemUsersRolesPut"]["query"]>;
659
697
  export type SystemUsersRolesPutBody = z.input<(typeof schemas)["systemUsersRolesPut"]["body"]>;
698
+ export type SystemUsersRolesPutData = z.infer<(typeof schemas)["systemUsersRolesPut"]["data"]>;
660
699
 
661
- export async function systemUsersRolesPut(input: { query: SystemUsersRolesPutQuery, body: SystemUsersRolesPutBody }): Promise<unknown> {
700
+ export async function systemUsersRolesPut(input: { query: SystemUsersRolesPutQuery, body: SystemUsersRolesPutBody }): Promise<SystemUsersRolesPutData> {
662
701
  const client = ensureReq();
663
702
  try {
664
- const env = await client.put(`system/users/roles`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<unknown>>();
703
+ const env = await client.put(`system/users/roles`, { searchParams: input.query as Record<string, string | number | boolean>, json: input.body }).json<OjEnvelope<SystemUsersRolesPutData>>();
665
704
  // 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
666
705
  if (typeof env.code === "number" && env.code !== 0)
667
706
  throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
668
- const data = env.data as unknown;
707
+ const data = env.data as SystemUsersRolesPutData;
708
+ if (import.meta.env.DEV) {
709
+ const { schemas } = await import("./api.schemas");
710
+ const r = schemas["systemUsersRolesPut"].data.safeParse(data);
711
+ if (!r.success)
712
+ throw new ContractApiError(-1, `[契约违例] systemUsersRolesPut 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
713
+ }
669
714
  return data;
670
715
  }
671
716
  catch (e) {
@@ -99,10 +99,9 @@
99
99
  },
100
100
  "oplog": {
101
101
  "filterAction": "Action",
102
- "filterUsername": "Username (prefix)",
102
+ "filterUsername": "Username (contains)",
103
103
  "filterModule": "Module",
104
- "filterLimit": "Limit",
105
- "truncated": "Results truncated by limit; narrow your filters",
104
+ "total": "{{total}} total",
106
105
  "columns": {
107
106
  "time": "Time",
108
107
  "username": "Username",
@@ -99,10 +99,9 @@
99
99
  },
100
100
  "oplog": {
101
101
  "filterAction": "操作 (action)",
102
- "filterUsername": "用户名(前缀匹配)",
102
+ "filterUsername": "用户名(包含匹配)",
103
103
  "filterModule": "模块",
104
- "filterLimit": "条数上限",
105
- "truncated": "结果已按上限截断,请缩小过滤范围",
104
+ "total": "共 {{total}} 条",
106
105
  "columns": {
107
106
  "time": "时间",
108
107
  "username": "用户名",
@@ -1,4 +1,4 @@
1
- import type { SystemDepartmentsGetData, SystemDepartmentsMembersGetData } from "../../__client/api";
1
+ import type { SystemDepartmentsGetData, SystemDepartmentsMembersGetData } from "../../__client";
2
2
 
3
3
  import { PlusOutlined } from "@ant-design/icons";
4
4
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -32,7 +32,7 @@ import {
32
32
  systemDepartmentsPost,
33
33
  systemDepartmentsPut,
34
34
  systemUsersGet,
35
- } from "../../__client/api";
35
+ } from "../../__client";
36
36
  import { explainError, isForbidden } from "../errors";
37
37
  import { ForbiddenPanel } from "../forbidden";
38
38
  import { WecomSyncBar } from "./sync-bar";
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
2
2
  import { Card, Descriptions, Tag } from "antd";
3
3
  import { useTranslation } from "react-i18next";
4
4
 
5
- import { systemLoginMethodsGet } from "../../__client/api";
5
+ import { systemLoginMethodsGet } from "../../__client";
6
6
 
7
7
  const METHOD_KEY: Record<string, string> = {
8
8
  local: "system:loginMethodsPage.methodLocal",
@@ -1,10 +1,10 @@
1
1
  import { useQuery } from "@tanstack/react-query";
2
- import { Alert, Button, Form, Input, InputNumber, Space, Table, Tooltip } from "antd";
2
+ import { Button, Form, Input, Space, Table, Tooltip } from "antd";
3
3
  import dayjs from "dayjs";
4
4
  import { useState } from "react";
5
5
  import { useTranslation } from "react-i18next";
6
6
 
7
- import { systemOplogGet } from "../../__client/api";
7
+ import { systemOplogGet } from "../../__client";
8
8
  import { isForbidden } from "../errors";
9
9
  import { ForbiddenPanel } from "../forbidden";
10
10
 
@@ -12,20 +12,26 @@ interface OplogFilters {
12
12
  action?: string
13
13
  username?: string
14
14
  module?: string
15
- limit?: number
16
15
  }
17
16
 
17
+ const DEFAULT_PAGE_SIZE = 20;
18
+ const PAGE_SIZE_OPTIONS = [20, 50, 100];
19
+
18
20
  /**
19
- * 操作日志(AC1-29/D13):action 精确 / username 前缀 / module 精确,limit 钳 200。
20
- * truncated=true 表示结果被截断(服务端 paged),顶部提示缩小过滤范围。
21
+ * 操作日志(AC1-29/D13):action 精确 / username 包含 / module 精确。
22
+ *
23
+ * 分页交给服务端:`page` + `limit`(每页条数,钳 200),响应带 `total`,页面按 total 出页码。
24
+ * 不再有「结果被截断」的横幅——记录再多也能通过页码翻到,不必靠缩小过滤范围。
21
25
  */
22
26
  export default function OplogPage() {
23
27
  const { t } = useTranslation();
24
28
  const [filters, setFilters] = useState<OplogFilters>({});
29
+ const [page, setPage] = useState(1);
30
+ const [size, setSize] = useState(DEFAULT_PAGE_SIZE);
25
31
 
26
32
  const query = useQuery({
27
- queryKey: ["system", "oplog", filters],
28
- queryFn: () => systemOplogGet(filters),
33
+ queryKey: ["system", "oplog", filters, page, size],
34
+ queryFn: () => systemOplogGet({ ...filters, page, limit: size }),
29
35
  });
30
36
 
31
37
  if (query.error && isForbidden(query.error))
@@ -50,12 +56,14 @@ export default function OplogPage() {
50
56
  <Space direction="vertical" style={{ width: "100%", paddingTop: 16 }}>
51
57
  <Form
52
58
  layout="inline"
53
- onFinish={values => setFilters({
54
- action: values.action || undefined,
55
- username: values.username || undefined,
56
- module: values.module || undefined,
57
- limit: values.limit || undefined,
58
- })}
59
+ onFinish={(values) => {
60
+ setFilters({
61
+ action: values.action || undefined,
62
+ username: values.username || undefined,
63
+ module: values.module || undefined,
64
+ });
65
+ setPage(1);
66
+ }}
59
67
  >
60
68
  <Form.Item name="action" label={t("system:oplog.filterAction")}>
61
69
  <Input allowClear />
@@ -66,25 +74,42 @@ export default function OplogPage() {
66
74
  <Form.Item name="module" label={t("system:oplog.filterModule")}>
67
75
  <Input allowClear />
68
76
  </Form.Item>
69
- <Form.Item name="limit" label={t("system:oplog.filterLimit")} initialValue={50}>
70
- <InputNumber min={1} max={200} />
71
- </Form.Item>
72
77
  <Form.Item>
73
78
  <Space>
74
79
  <Button type="primary" htmlType="submit">{t("system:common.search")}</Button>
75
- <Button onClick={() => { setFilters({}); }}>
80
+ <Button
81
+ onClick={() => {
82
+ setFilters({});
83
+ setPage(1);
84
+ }}
85
+ >
76
86
  {t("system:common.reset")}
77
87
  </Button>
78
88
  </Space>
79
89
  </Form.Item>
80
90
  </Form>
81
- {query.data?.truncated ? <Alert type="warning" showIcon message={t("system:oplog.truncated")} /> : null}
82
91
  <Table
83
92
  rowKey="id"
84
93
  loading={query.isLoading}
85
94
  dataSource={query.data?.list ?? []}
86
95
  columns={columns}
87
- pagination={{ hideOnSinglePage: true, pageSize: 50 }}
96
+ pagination={{
97
+ current: page,
98
+ pageSize: size,
99
+ total: query.data?.total ?? 0,
100
+ showSizeChanger: true,
101
+ pageSizeOptions: PAGE_SIZE_OPTIONS,
102
+ showTotal: total => t("system:oplog.total", { total }),
103
+ onChange: (nextPage, nextSize) => {
104
+ // 改每页条数后原页码可能越界:统一回第 1 页
105
+ if (nextSize !== size) {
106
+ setSize(nextSize);
107
+ setPage(1);
108
+ return;
109
+ }
110
+ setPage(nextPage);
111
+ },
112
+ }}
88
113
  />
89
114
  </Space>
90
115
  );
@@ -1,12 +1,13 @@
1
- import type { SystemPermissionsGetData, SystemRolesGetData } from "../../__client/api";
1
+ import type { SystemPermissionsGetData, SystemRolesGetData } from "../../__client";
2
2
 
3
3
  import { useQuery } from "@tanstack/react-query";
4
4
  import { Alert, Space, Table, Tabs, Tag } from "antd";
5
5
  import { useTranslation } from "react-i18next";
6
6
 
7
- import { systemPermissionsGet, systemRolesGet } from "../../__client/api";
7
+ import { systemPermissionsGet, systemRolesGet } from "../../__client";
8
8
  import { isForbidden } from "../errors";
9
9
  import { ForbiddenPanel } from "../forbidden";
10
+ import { visibleRoles } from "./visible-roles";
10
11
 
11
12
  type RoleRow = SystemRolesGetData[number];
12
13
  type PermRow = SystemPermissionsGetData[number];
@@ -19,6 +20,7 @@ export default function RolesPage() {
19
20
  const { t } = useTranslation();
20
21
  const rolesQuery = useQuery({ queryKey: ["system", "roles"], queryFn: systemRolesGet });
21
22
  const permsQuery = useQuery({ queryKey: ["system", "permissions"], queryFn: systemPermissionsGet });
23
+ const rows = visibleRoles(rolesQuery.data ?? []);
22
24
 
23
25
  if ((rolesQuery.error && isForbidden(rolesQuery.error)) || (permsQuery.error && isForbidden(permsQuery.error)))
24
26
  return <ForbiddenPanel t={t} error={rolesQuery.error ?? permsQuery.error} />;
@@ -75,7 +77,7 @@ export default function RolesPage() {
75
77
  children: (
76
78
  <>
77
79
  <Alert style={{ marginBottom: 12 }} type="info" showIcon message={t("system:rolesPage.readonlyHint")} />
78
- <Table rowKey="name" loading={rolesQuery.isLoading} dataSource={rolesQuery.data ?? []} columns={roleColumns} pagination={{ hideOnSinglePage: true }} />
80
+ <Table rowKey="name" loading={rolesQuery.isLoading} dataSource={rows} columns={roleColumns} pagination={{ hideOnSinglePage: true }} />
79
81
  </>
80
82
  ),
81
83
  },
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 角色展示页的可见范围(纯函数,便于用例直接钉住,不必起 React)。
3
+ *
4
+ * superadmin 是平台级角色(跨租户),不属于「站点可分配的角色目录」;列在角色页只会让人
5
+ * 误以为能改它的绑定,故展示层隐去。
6
+ *
7
+ * **只在这一层过滤**:后端 listRoles() 必须原样返回 superadmin——成员资格页
8
+ * (users/roles.tsx)还要拿它作为可授予项(仅 superadmin 可勾选,见那里的 disabled 逻辑)。
9
+ */
10
+ export const HIDDEN_ROLE = "superadmin";
11
+
12
+ export function visibleRoles<T extends { name: string }>(roles: readonly T[]): T[] {
13
+ return roles.filter(r => r.name !== HIDDEN_ROLE);
14
+ }
@@ -1,13 +1,13 @@
1
1
  import type { TFunction } from "i18next";
2
2
 
3
- import type { SystemSettingsGetData } from "../../__client/api";
3
+ import type { SystemSettingsGetData } from "../../__client";
4
4
 
5
5
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
6
6
  import { App, Button, Card, Input, Space, Switch, Tooltip } from "antd";
7
7
  import { useState } from "react";
8
8
  import { useTranslation } from "react-i18next";
9
9
 
10
- import { systemSettingsGet, systemSettingsPut } from "../../__client/api";
10
+ import { systemSettingsGet, systemSettingsPut } from "../../__client";
11
11
  import { explainError, isForbidden } from "../errors";
12
12
  import { ForbiddenPanel } from "../forbidden";
13
13
 
@@ -0,0 +1,21 @@
1
+ /**
2
+ * /system/users 行级操作门禁:只有 superadmin 能改 superadmin 账号。
3
+ *
4
+ * 与后端 `domain/users.ts::assertCanManageTarget` 同一条红线(只有 superadmin 能改
5
+ * superadmin 的口令/状态/资料/角色)。前端这层只为「不给会 403 的按钮」——灰色可点却不
6
+ * 生效比报错更糟——真正的防线在服务端:admin 直打接口同样会被 403 挡住。
7
+ *
8
+ * 单独成文件是为了可测:vitest 跑在 node 环境,没有 jsdom,整页 render 不了。
9
+ */
10
+ export const PLATFORM_ROLE = "superadmin";
11
+
12
+ /**
13
+ * @param myRoles 当前登录者的角色
14
+ * @param targetRoles 行上用户的角色
15
+ * @returns 是否允许对目标账号执行管理操作(改密/启停/改资料/成员资格)
16
+ */
17
+ export function canManageUser(myRoles: readonly string[], targetRoles: readonly string[]): boolean {
18
+ if (!targetRoles.includes(PLATFORM_ROLE))
19
+ return true;
20
+ return myRoles.includes(PLATFORM_ROLE);
21
+ }
@@ -1,4 +1,4 @@
1
- import type { SystemUsersGetData } from "../../__client/api";
1
+ import type { SystemUsersGetData } from "../../__client";
2
2
 
3
3
  import { PlusOutlined } from "@ant-design/icons";
4
4
  import { useUserStore } from "@oj-module/runtime";
@@ -9,16 +9,19 @@ import { useTranslation } from "react-i18next";
9
9
 
10
10
  import { useNavigate } from "react-router";
11
11
 
12
- import { systemDepartmentsGet, systemUsersForbiddenPut, systemUsersGet, systemUsersPasswordPut, systemUsersPost } from "../../__client/api";
12
+ import { systemDepartmentsGet, systemUsersForbiddenPut, systemUsersGet, systemUsersPasswordPut, systemUsersPost } from "../../__client";
13
13
  import { buildTreeData, subtreeIds } from "../departments/tree";
14
14
  import { explainError, isForbidden } from "../errors";
15
15
  import { ForbiddenPanel } from "../forbidden";
16
+ import { canManageUser } from "./can-manage";
16
17
 
17
18
  type UserRow = SystemUsersGetData[number];
18
19
 
19
20
  /**
20
21
  * 用户管理(C1):GET /system/users 列表 + 建号 / 禁用切换 / 重置口令。
21
22
  * 本人行禁用一切自操作(后端 assertNotSelf 400 兜底,前端不给会失败的按钮,R3);
23
+ * superadmin 行也禁用一切操作,除非当前登录者是 superadmin(红线:只有 superadmin 能改
24
+ * superadmin——后端 assertCanManageTarget 403 兜底,前端把按钮置灰);
22
25
  * 成员资格入口:superadmin 与 **本租户 admin** 均可见(授予 admin 已下放到租户内,
23
26
  * 但授予 superadmin 仍只许 superadmin——见 domain/users.ts::assertMembershipAllowed)。
24
27
  * 部门列标出主部门,顶部可按部门筛选(含子部门)——筛选在前端做,名单本来就是一页全量。
@@ -117,8 +120,10 @@ export default function UsersPage() {
117
120
  title={t("system:users.forbidden")}
118
121
  description={`${row.username} → ${forbidden ? "false" : "true"}`}
119
122
  onConfirm={() => forbiddenMut.mutate({ uid: row.id, forbidden: !forbidden })}
123
+ // 置灰的 Switch 不会触发 Popconfirm,这里只是把「为什么点不了」挡在确认框之前
124
+ disabled={String(row.id) === me || !canManageUser(myRoles, row.roles)}
120
125
  >
121
- <Switch size="small" checked={forbidden} disabled={String(row.id) === me} />
126
+ <Switch size="small" checked={forbidden} disabled={String(row.id) === me || !canManageUser(myRoles, row.roles)} />
122
127
  </Popconfirm>
123
128
  {row.must_change ? <Tag color="orange">{t("system:users.firstLoginHint")}</Tag> : null}
124
129
  </Space>
@@ -127,20 +132,23 @@ export default function UsersPage() {
127
132
  {
128
133
  title: t("system:users.columns.actions"),
129
134
  key: "actions",
130
- render: (_: unknown, row: UserRow) => (
131
- <Space>
132
- <Button size="small" disabled={String(row.id) === me} onClick={() => setPwdTarget(row)}>
133
- {t("system:users.resetPwd")}
134
- </Button>
135
+ render: (_: unknown, row: UserRow) => {
136
+ const manageable = canManageUser(myRoles, row.roles);
137
+ return (
138
+ <Space>
139
+ <Button size="small" disabled={String(row.id) === me || !manageable} onClick={() => setPwdTarget(row)}>
140
+ {t("system:users.resetPwd")}
141
+ </Button>
135
142
  {canManageMembership
136
143
  ? (
137
- <Button size="small" type="link" onClick={() => navigate(`/system/users/${row.id}/roles`)}>
138
- {t("system:users.membership")}
139
- </Button>
140
- )
141
- : null}
142
- </Space>
143
- ),
144
+ <Button size="small" type="link" disabled={!manageable} onClick={() => navigate(`/system/users/${row.id}/roles`)}>
145
+ {t("system:users.membership")}
146
+ </Button>
147
+ )
148
+ : null}
149
+ </Space>
150
+ );
151
+ },
144
152
  },
145
153
  ];
146
154
 
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
6
6
 
7
7
  import { useNavigate, useParams } from "react-router";
8
8
 
9
- import { systemRolesGet, systemUsersGet, systemUsersRolesPut } from "../../__client/api";
9
+ import { systemRolesGet, systemUsersGet, systemUsersRolesPut } from "../../__client";
10
10
  import { explainError, isForbidden } from "../errors";
11
11
  import { ForbiddenPanel } from "../forbidden";
12
12